由一道CTF引发的技术思考(wafbypass)
1099509721598427 WEB安全 417浏览 · 2025-04-10 03:26

引言

在最近举行的XYCTF 2025,有一道关于bottle注入的题目,但是waf的绕过手段,却非常的意外,此篇文章,会对其进行详细的分析以及解释

前置

咱们先来看题目代码:

Python
复制代码
题目代码是非常简单的,就是在attack路由中,会把我们传入的payload进行渲染,然而,我们可以发现的是,payload的长度是被限制了,并且过滤掉了open函数,以及\\

这里我们不妨想一想,如果没有限制,即没有过滤,那正常的payload

正常payload
Python
复制代码
那其实正常的payload,就是要用open去读取其下的文件,但是这里open被禁用,我们该如何绕呢? 正常的思维,大家可能会利用其他函数。例如:

但是,在bottle的框架中,include用于在模板中嵌入其他模板文件。rebase也是一样。是读取不了服务器上的文件的。所以引入我们此篇文章的重点

斜体字符绕过

这里主要是指:一个字符的斜体字符集,即指的是Decomposition后为同一个字符的字符集字符编码特性

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

同样,数学斜体字符ℴ(U+2134)在进行 NFC 或 NFD 规范化后,也会等价于普通字符 o。这是由于 Unicode 规范化的规则,它旨在消除字符编码的歧义性,确保不同平台和应用之间的字符表示一致性。在这种规则下,虽然斜体字符和普通字符在视觉上有明显区别,但从字符编码和规范化的角度来看,它们在某些情况下会被视为等同的字符。

比如:
image.png


其实用简单的话来说,在传入此类字符以后,在规范性分解以后,会把其指向同一个字符,例如 虽然 𝑎 看起来是斜体的 a,但它们在 Unicode 中被分配了不同的码点。这些字符的 规范化分解(Canonical Decomposition) 可能指向同一个基础字符--'a'

这也就是为什么我们能够绕过其中的限制,但是为什么在此能够被绕过呢?

原理

我们先看bottle.template()

分析如下:

args:

args[0]:模板标识符,可以是 模板名称(如 'index')、模板内容字符串 或 模板对象。

args[1:]:多个字典参数,合并到 kwargs 中作为模板变量。

kwargs:直接传递的模板变量,优先级高于字典参数。

模板适配器 (adapter):决定使用的模板引擎,默认为 SimpleTemplate。

模板查找路径 (lookup):默认从全局 TEMPLATE_PATH 加载模板文件。

缓存键 (tplid):基于 lookup 对象的内存地址和模板标识符,确保不同查找路径的模板独立缓存。

缓存条件:若处于 DEBUG 模式或模板未缓存,则重新加载。

模板内容检测:通过检查换行符或特定符号({, %, $),判断 tpl 是否为模板字符串。

文件系统加载:若 tpl 是纯字符串且无特殊符号,视为文件名,从 lookup 路径查找。



其实主要是模板适配器 (adapter):决定使用的模板引擎,默认为 SimpleTemplate。 我们在跟进渲染入口:render()函数

这了update一些变量以后,主要是来到此: self.execute(stdout, env) 再次跟进函数execute

在这里:

这了即是模板的code的执行。 可以看到在exec的全局变量里定义了一个_escape和_printlist函数。

再次跟进co

即是compile了self.code 再次跟进code

可以看到:source, encoding = touni(source), 'utf8'

我们的source经过了一次函数touni()

将任意输入 s 转换为 Unicode 字符串,并且这是针对全体str,所以在我们上述的“斜体字符集”并不影响



通过上述的跟踪,我们可以得到:我们的输入,不论在不在{{}}里,经过唯一的编码检查就是对source的touni(),但是由于全局变量中的unicode在python3下是全体str,这就导致了我们可以输入斜体字符,并且通过转换 Unicode 字符串的时候,斜体字符会被映射到同一常见字符上,这就导致了绕过。

º (U+00BA)

删除掉前面的%C2

我们能发现:

image.png


能够成功读取 当然了,本篇文章讨论的斜体字,大家也可以去考虑其他字符,例如艺术字,原理是一样的,都是会映射字符到常见字符集中。

例如此下代码:

和上述解法是一样的。



结语

除了斜体字符外,还有其他类型的字符可能被用于绕过检测。

艺术字符如ℴ(U+2134)、ᵒ(U+1D452)等,它们同样具有独特的视觉外观和 Unicode 编码。这些艺术字符在经过规范化处理后,也可能与普通字符产生混淆。例如,在某些字体渲染环境下,ℴ与普通字符 o 几乎难以区分,而在 Unicode 规范化过程中,它们可能被转换为相同的字符形式。这就使得攻击者有可能利用这些艺术字符构造恶意载荷,绕过基于字符匹配的检测机制。当 WAF 检测输入中是否包含敏感关键词时,由于艺术字符在原始输入中的特殊编码形式,WAF 可能无法准确识别其潜在的威胁,从而导致检测失败。

符号变形也是一种潜在的绕过方式。以 º(U+00BA)为例,在 URL 编码截断的情况下,它可能被解析为 a。这是因为在 URL 编码和解码的过程中,如果编码字符串被意外截断或处理不当,符号的原始意义可能会发生改变。利用这一特性,将恶意代码中的关键字符替换为类似 º 这样的符号,并通过精心构造 URL 编码,使得在传输和处理过程中,这些符号被错误解析为合法字符,从而绕过 WAF 的检测。当 WAF 对 URL 进行解析和检测时,由于截断后的符号被错误解析,WAF 无法识别出其中隐藏的恶意代码,使得攻击者能够成功实施攻击。

0 条评论
某人
表情
可输入 255
目录