HackThisSite - Extended Basic Mission 4

Challenge

A program written in a made-up language called F.ake; work out its output when the user types 6,7.

第四关给出一段用自造语言 F.ake 写的程序,要求算出用户输入 6,7 时它的输出。

关卡页把程序正文放在 <pre> 里,上面一行注明 {user types 6,7}

1
2
3
4
5
{user types 6,7}
BEGIN F.ake
var int as in
int var as in
out var int

Solution

用带登录态的会话取关卡页(HackThisSite 是短期凭据,这里写成占位符):

1
2
$ curl -s -b 'HackThisSite=<mission-cookie>' \
'https://www.hackthissite.org/missions/playit/extbasic/4/'

页面里的提交表单:

1
2
3
4
5
<form action="/missions/extbasic/template.php" method="post">
<input type="hidden" name="formkey" value="J1vgO9lFLqnaYvHFPQZkoeoDg7ru1brsf4o2QPVWUG" />
<input type="hidden" name="lvl" value="4" />
<input type="text" name="pass" /><input type="submit" value="check" />
</form>

formkey 每次加载关卡页都会变(实测连续几次分别拿到 J1vgO9lFLqnaYvHFPQZkoeoDg7ru1brsf4o2QPVWUG8pLDPkPxWkPr5yGy1IzLZS6uw6IFfa9YD1UvthGX93f0aA1lmFy7IuEU5uKG1eWJpZbDez1lG7AZb4T),所以要先抓页面再立刻提交。

四行程序逐行读:

  • BEGIN F.ake:程序入口,对应其它语言的 mainF.ake 就是这门语言的名字。
  • var int as in:从标准输入读一个值,声明成名为 var、类型为 int 的变量。第一次输入是 6,所以 var = 6as in 是取自输入。
  • int var as in:同一套写法的第二次读入,这次名字是 int、类型 token 是 var(一个动态/未定类型,含义类似 JavaScript 的 var)。第二次输入是 7,所以 int = 7
  • out var int:按书写顺序输出后面列出的变量。

关键是把每行的两个 token 按名字在前、类型在后的次序读:var int 是叫 varintint var 是叫 int 的动态变量。varint 在这里都只是变量名,跟它们像不像类型关键字无关,这也是这门语言故意制造的混淆。

var = 6int = 7out var int 依次打印两个变量 → 67

out var int 是把两个变量的值分别输出再拼接,不是把 67 做算术相加(那才是 13)。

POST 到 /missions/extbasic/template.php,三个字段 formkeylvlpass。第一次不带 Referer 直接提交,服务端拒绝:

1
2
3
4
5
6
<strong>
<font size="2">Invalid Referer</font>
</strong>
<p align="center">
<font size="1">
Invalid referer. The requested URL /missions/extbasic/template.php will not be loaded.

和 JavaScript 系列一样,服务端要求 Referer 指向关卡页。补上 -e(curl 的 Referer)后提交被接受,响应里给出指向下一关的 go on 链接:

1
<a href='/missions/playit/extbasic/5'><img src='http://hackthissite.org/missions/GoOn.gif' alt='go on' title='go on' border='0' /></a>

提交后 profile 的 Extbasic: 列表由 (1) (2) (3) (5) 变为 (1) (2) (3) (4) (5)

完整求解脚本(先取 formkey,再带 Referer 提交,最后检查下一关链接):

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/env python3
"""HackThisSite Extended Basic 4 (playit) live solver.

Level 4 hands you a program written in a made-up language called ``F.ake``:

BEGIN F.ake
var int as in
int var as in
out var int

The user types ``6,7``. ``var int as in`` declares an ``int`` named ``var``
and binds it to the first input (6); ``int var as in`` declares a dynamically
typed variable named ``int`` and binds it to the second input (7); ``out var
int`` prints both names in order -> 67.

The submission MUST carry a Referer pointing at the level page, otherwise
template.php answers ``Invalid Referer`` and the attempt does not count. The
session cookie is read from the ``HTS_COOKIE`` env var and never persisted.
"""
import os
import re
import subprocess
import sys

UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36")
LEVEL = "https://www.hackthissite.org/missions/playit/extbasic/4/"
SUBMIT = "https://www.hackthissite.org/missions/extbasic/template.php"
ANSWER = "67"

CK = os.environ["HTS_COOKIE"]

def get(url):
return subprocess.run(
["curl", "-sL", "-m", "30", "-b", CK, "-A", UA, url],
check=True, capture_output=True, text=True,
encoding="utf-8", errors="replace").stdout

def post(url, data, referer):
return subprocess.run(
["curl", "-sL", "-m", "30", "-b", CK, "-A", UA, "-e", referer,
"--data", data, url],
check=True, capture_output=True, text=True,
encoding="utf-8", errors="replace").stdout

def main(answer):
page = get(LEVEL)
formkey = re.search(r'name="formkey" value="([^"]+)"', page).group(1)
lvl = re.search(r'name="lvl" value="([^"]+)"', page).group(1)
print("formkey=%s lvl=%s" % (formkey, lvl))
resp = post(SUBMIT, "formkey=%s&lvl=%s&pass=%s" % (formkey, lvl, answer),
LEVEL)
if "/missions/playit/extbasic/5" in resp:
print("[+] accepted - server handed out the go-on link to level 5")
else:
print("[-] no go-on marker in response")

if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else ANSWER)

运行输出:

1
2
3
$ cd <hts-workspace> && uv run python challenges/hts-playit/extbasic-4/solve.py
formkey=vn6xpG4ebxVtxNBt3r4e6AAsaXXKHjjavEzVnj8 lvl=4
[+] accepted - server handed out the go-on link to level 5

Key points

  • F.ake 的声明形式是 name type as in(名字在前、类型在后),不能按关键字直觉读;var / int 是变量名,不是类型名。
  • out var int 只是依次输出两个变量,输出是字符串 67,不是算术和 13
  • formkey 是每次加载生成的,必须抓页面 → 立即提交,不能复用旧值。
  • 提交必须带 Referer: /missions/playit/extbasic/4/,否则返回 Invalid Referer 且不计完成。
67