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 秒的限时决定了只能脚本化。
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.txt,1274 行、CRLF 行尾。 - 词表里不只有普通英文单词,还混着数字串和带符号的词(
121212、654321、8675309、666666、html:)等)。所以题面里出现888888这种条目是完全正常的,它本身就是一个合法条目。
$ 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.txtwordlist.txt: ASCII text, with CRLF line terminators1274 wordlist.txt$ head -6 wordlist.txthtml:)1212121313131231236543218675309打乱只改变字符顺序、不改变字符频次,所以每个乱序词 w 与原文的排序后字符串(把字符排序后拼接)完全相等。建索引时用排序后的串当 key,一次查表即可;重复字母(aremedr、rirmemto)也不会出错。
索引按排序后的字符保存一个候选。下面的 fallback 仅处理索引未命中,不会消除同一多重集对应多个词的歧义。
限时 30 秒,但真正的时间开销在解析上(字符串处理),网络只占两次往返:GET 实例页 + POST 答案。
完整脚本如下。脚本把 cookie 从 HTS_COOKIE 环境变量读取,依赖的 HTTP 与页面解析逻辑也一并包含在内;wordlist.txt 放在脚本同目录。
#!/usr/bin/env python3"""Solve HackThisSite Programming Mission 1."""
import htmlimport osimport refrom pathlib import Path
import requests
BASE = "https://www.hackthissite.org"LEVEL_URL = f"{BASE}/missions/prog/1/"WORDLIST = Path(__file__).with_name("wordlist.txt")USER_AGENT = ( "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
def session():101 collapsed lines
cookie = os.environ.get("HTS_COOKIE", "").strip().strip("'\"") if not cookie: raise SystemExit("HTS_COOKIE is not set")
client = requests.Session() client.headers.update( { "User-Agent": USER_AGENT, "Cookie": cookie, "Referer": f"{BASE}/missions/programming/", "Accept-Language": "en-US,en;q=0.9", } ) return client
def body_text(source): source = re.sub(r"<script.*?</script>", "", source, flags=re.S) source = re.sub(r"<br\s*/?>", "\n", source) source = re.sub(r"</(?:p|div|tr)>", "\n", source) source = re.sub(r"<[^>]+>", " ", source) source = html.unescape(source) source = re.sub(r"[ \t]+", " ", source) return re.sub(r"\n\s*\n+", "\n", source)
def load_wordlist(path): table = {} with path.open("r", encoding="utf-8", errors="replace") as fh: for line in fh: word = line.strip() if word: table.setdefault("".join(sorted(word)), word) return table
def parse_scrambled(page_text): marker = "List of scrambled words:" end_marker = "Answer:" if marker not in page_text or end_marker not in page_text: raise ValueError("could not find the scrambled-word section") chunk = page_text.split(marker, 1)[1].split(end_marker, 1)[0] return [token for token in re.split(r"[\s,]+", chunk) if token]
def unscramble(words, table): result = [] for word in words: key = "".join(sorted(word)) candidates = [value for value in table.values() if "".join(sorted(value)) == key] if not candidates: result.append(word) else: result.append(candidates[0]) return result
def submit(client, answer): response = client.post( f"{LEVEL_URL}index.php", data={"solution": answer, "submitbutton": "submit"}, headers={"Referer": LEVEL_URL}, timeout=30, ) response.raise_for_status() text = body_text(response.text).lower() success_markers = ( "congratulation", "you have completed", "completed this", "successfully", "correct", "well done", "mission accomplished", "complete!", ) failure_markers = ( "wrong", "incorrect", "not correct", "try again", "failed", "too late", "time is up", "sorry", ) success = any(marker in text for marker in success_markers) failure = any(marker in text for marker in failure_markers) if success and not failure: return True if failure and not success: return False return None
def main(): client = session() response = client.get(LEVEL_URL, timeout=20) response.raise_for_status() words = parse_scrambled(body_text(response.text)) table = load_wordlist(WORDLIST) answer = ",".join(unscramble(words, table)) print("scrambled:", words) print("answer :", answer) print("verdict :", submit(client, answer))
if __name__ == "__main__": main()运行方式:
$ cd <hts-workspace>/challenges/hts-prog/1$ export HTS_COOKIE='<mission-cookie>'$ uv run --with requests python solve.pyscrambled: ['amtnar', 'plradnot', 'chyeok', 'ubeadtht', 'aremedr', 'getayaw', 'udsettn', 'n1j6o3h', 'cuatain', 'rirmemto']answer : mantra,portland,hockey,butthead,dreamer,gateway,student,john316,nautica,mortimerverdict : True