HackThisSite - ExtBasic Mission 05

Challenge

ExtBasic Mission 05 — fix the broken line in a shell script that is supposed to patch a PHP page. 修正 Sam 用来修补 PHP 页面的 shell 脚本里被写错的那一行。

关卡给出一个 PHP 页面:本该调用 safeeval() 包装函数的地方,写成了裸的 eval()

1
2
3
4
5
6
7
8
9
<?php
include ('safe.inc.php');
if ($access=="allowed") {
eval($_GET['cmd']);
if (!empty($_GET['cmd2'])) {
eval($_GET['cmd2']);
}
}
?>

Sam 写了个 shell 脚本,用 sedeval 批量改写成 safeeval

1
2
3
4
5
6
#!/bin/sh
rm OK
sed -E "s/eval/safeeval/" <exec.php >tmp && touch OK
if [ -f OK ]; then
rm exec.php && mv tmp exec.php
fi

页面说明 Sam 的系统是 freeBSD 6.9,顶部横幅另外提醒:不要写 sed -r,那是 Linux/GNU 专属,BSD 上要用 sed -E

任务是修正 shell 脚本里写错的那一行(保持相同空格)。

Solution

横幅专门提醒 -r-E 的区别,说明出题人预期玩家会先怀疑 sed 标志。脚本里已经是 sed -E,在 FreeBSD 上语法合法。这一行的问题不在方言,而在替换命令本身。

s/eval/safeeval/ 没有写全局标志 g

  • 不带 g:sed 对每一行只替换第一个匹配;
  • g:替换行内所有匹配。

exec.php 里有不止一处 eval($_GET['cmd']$_GET['cmd2'] 各一处),要一次全改掉,替换命令必须是 s/eval/safeeval/g。替换串 safeeval 本身含有 eval,但 sed 命中一次后从替换文本之后继续扫描,不会把刚写进去的 safeeval 再改一遍,所以加 g 不会破坏结果。

题目要求使用相同的空格,即整行照抄,只在正确的位置补上 g

1
sed -E "s/eval/safeeval/g" <exec.php >tmp && touch OK

脚本其余部分不动,其写回保护依然成立:rm OK 清掉上一次的成功标记,&& touch OK 只在 sed 成功时创建标记,if [ -f OK ] 成立才执行 rm exec.php && mv tmp exec.php,避免半成品覆盖原文件。

formkey 每次加载页面都会变,取页面和提交必须在同一次运行里完成,脚本从环境变量读 cookie。

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
import os
import re

import requests

BASE = "https://www.hackthissite.org"
LEVEL = BASE + "/missions/playit/extbasic/5/"
TEMPLATE = BASE + "/missions/extbasic/template.php"
UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
ANSWER = 'sed -E "s/eval/safeeval/g" <exec.php >tmp && touch OK'

def main():
session = requests.Session()
session.cookies.set("HackThisSite", os.environ["HTS_COOKIE"].split("=", 1)[1])
headers = {"User-Agent": UA}

page = session.get(LEVEL, headers=headers, timeout=30).text
formkey = re.search(r'name="formkey"\s+value="([^"]+)"', page).group(1)
resp = session.post(TEMPLATE,
data={"formkey": formkey, "lvl": "5", "pass": ANSWER},
headers={**headers, "Referer": LEVEL}, timeout=30)
print(resp.status_code)

if __name__ == "__main__":
main()

Key points

  • sed 的 g 控制行内全部替换还是只替换首个匹配,是字符串改写式过滤/修复里最常见的静默缺陷
  • -E(BSD)与 -r(GNU)不可互换,跨平台脚本要按目标 uname 选方言
  • sed 替换从左向右、不重叠扫描:s/eval/safeeval/g 不会二次命中刚生成的 safeeval
  • rm OK / touch OK 标记配合 &&,让写回原文件只在 sed 成功时发生
sed -E s/eval/safeeval/g <exec.php >tmp && touch OK