HackThisSite - Programming Mission 1

Challenge

Level 1 — Unscramble the words

找出被打乱顺序的原始单词,单词是从官方 wordlist 里随机挑的,30 秒内把原始单词按列表顺序用逗号分隔提交。

Find the original (unscrambled) words, which were randomly taken from a wordlist. Send a comma separated list of the original words, in the same order as in the list below. You have 30 seconds time to send the solution.

每次访问实例页都会重新生成一组乱序单词,30 秒的限时决定了只能脚本化。状态:verified(服务端返回 Good Job, ***, You have successfully completed this mission)。

Solution

  • 实例页 https://www.hackthissite.org/missions/prog/1/ 里,乱序词逐个用 <br /> 分隔,前面是 List of scrambled words:,后面是 Answer:,解析时按这两句切段最省事。
  • 词表是公开的固定文件:https://www.hackthissite.org/missions/prog/1/wordlist.zip(不需要登录态,匿名 curl 也能下)。解开后是 wordlist.txt1274 行、CRLF 行尾
  • 词表里不只有普通英文单词,还混着数字串和带符号的词(1212126543218675309666666html:) 等)。所以题面里出现 888888 这种条目是完全正常的,它本身就是一个合法条目。
1
2
3
4
5
6
7
8
9
10
11
$ curl -s -o wordlist.zip https://www.hackthissite.org/missions/prog/1/wordlist.zip && unzip -o wordlist.zip >/dev/null
$ file wordlist.txt && wc -l wordlist.txt
wordlist.txt: ASCII text, with CRLF line terminators
1274 wordlist.txt
$ head -6 wordlist.txt
html:)
121212
131313
123123
654321
8675309

打乱只改变字符顺序、不改变字符频次,所以每个乱序词 w 与原文的排序后字符串(把字符排序后拼接)完全相等。建索引时用排序后的串当 key,一次查表即可;重复字母(aremedrrirmemto)也不会出错。

词表可能存在同一多重集对应多个词的极端情况,这里额外保底:命中不到时按长度过滤再比对排序串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def load_wordlist(path="wordlist.txt"):
table = {}
with open(path, "r", encoding="utf-8", errors="replace") as fh:
for line in fh:
w = line.strip() # CRLF 由 strip 一并处理
if not w:
continue
table.setdefault("".join(sorted(w)), w)
return table

def unscramble(words, table):
out = []
for w in words:
key = "".join(sorted(w))
out.append(table.get(key, w)) # 查不到就原样回填,便于排查
return out

限时 30 秒,但真正的时间开销在解析上(字符串处理),网络只占两次往返:GET 实例页 + POST 答案。

1
2
3
4
5
$ cd <hts-workspace> && export HTS_COOKIE='<mission-cookie>'
$ uv run python challenges/hts-prog/1/solve.py
scrambled: ['amtnar', 'plradnot', 'chyeok', 'ubeadtht', 'aremedr', 'getayaw', 'udsettn', 'n1j6o3h', 'cuatain', 'rirmemto']
answer : mantra,portland,hockey,butthead,dreamer,gateway,student,john316,nautica,mortimer
verdict : True

一次真实的解析/匹配输出:

1
2
3
4
5
6
scrambled: amtnar plradnot chyeok ubeadtht aremedr getayaw udsettn n1j6o3h cuatain rirmemto
matched : amtnar->mantra plradnot->portland chyeok->hockey ubeadtht->butthead
aremedr->dreamer getayaw->gateway udsettn->student n1j6o3h->john316
cuatain->nautica rirmemto->mortimer
submit : POST /missions/prog/1/index.php solution=<逗号分隔列表>
response : Good Job, ***, You have successfully completed this mission

提交表单字段是 solutionsubmitbutton 只是按钮文案),成功响应里含 successfully completed this mission

  • 顺序不能变:题面要求按列表顺序提交,所以解析出来的顺序必须原样保留(不要排序、不要去重)。
  • 数字条目照抄:像 888888 这种条目在词表里就是它自己,匹配后回填原值即可;如果按必须是英文单词过滤,反而会把它丢掉导致整行答案错位。
  • 行尾:词表是 CRLF,读行时统一 strip(),否则 key 会多一个 \r,全部匹配失败。
  • 不应使用 set 建索引:重复字母的乱序词会被压缩(例如 aabbabab 只有排序串相等),必须是排序后字符串而不是字符集合。

Vulnerabilities

关卡把答案空间完全暴露给客户端:词表是公开可下载的静态文件,乱序只是字符置换,服务端只比对最终字符串没有任何时间/次数之外的防护。结论很直接:任何在客户端做推导的校验都等价于把答案交给攻击者,30 秒限时也只是把人工操作挡在门外,对脚本毫无难度(解析 + 查表在毫秒级完成)。修复方向:把词表放在服务端、客户端只提交结果哈希并加随机 nonce;若必须本地校验,至少不应提供完整词表。

mantra,portland,hockey,butthead,dreamer,gateway,student,john316,nautica,mortimer