Python-Flask(Jinja2)注入漏洞原理复现

基础知识

这里简单介绍一下Flsak:

Flask是一个使用 Python编写的轻量级 Web 应用框架。其 WSGI工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。

模板引擎说白了 人家提前给你一个网页的模板 里面一些展示数据的地方让你自己控制去放,然后显示出来。

这里就用python导入flask的包

图片[1]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

app=Flask(name)的含义:

第一部分,初始化:所有的Flask都必须创建程序实例,web服务器使用wsgi协议,把客户端所有的请求都转发给这个程序实例程序实例是Flask的对象,一般情况下用如下方法实例化Flask类只有一个必须指定的参数,即程序主模块或者包的名字,__name__是系统变量,该变量指的是本py文件的文件名

route的含义

route装饰器的作用是将函数与url绑定起来。这里的作用就是当访问首页的的时候,flask会返回hello word123到这个页面上:

客户端发送url给web服务器,web服务器将url转发给flask程序实例,程序实例

# 需要知道对于每一个url请求启动那一部分代码,所以保存了一个url和python函数的映射关系。

#处理url和函数之间关系的程序,称为路由

#在flask中,定义路由最简便的方式,是使用程序实例的app.route装饰器,把装饰的函数注册为路由

图片[2]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

那如果想指定别的目录页面呢

图片[3]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

比如我们指定一个/nihao/的url

当客户端请求这个url时候 就会返回 woshinidie 在这个页面:

图片[4]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

这下基本就清楚了 Flask整体的工作流程了 我们再来理一下整体流程:

  1. 浏览器将请求给web服务器,web服务器将请求给app ,
  2. app收到请求,通过路由找到对应的视图函数,然后将请求处理,得到一个响应response
  3. 然后app将响应返回给web服务器,
  4. web服务器返回给浏览器,
  5. 浏览器展示给用户观看,流程完毕。

那么漏洞是怎么产生的,在学习了很多漏洞 我发现漏洞的本质都是开发者信任了用户的输入的数据,代入或嵌套到程序的某一部分。 这个漏洞也是这样。

我们刚才展示的都是静态网页,所以并不存在用户的数据跟模板进行交互,那么如果这个程序中的数据是客户端传来的呢?

图片[5]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

render_template_string 可以理解为就是渲染一个字符串到页面

看代码我们会发现 code接受get传参过来的值,然后html这个字符串%s 被code控制,那也就是说 id传的参数会到html 然后再被渲染显示到页面,我们试一下:

图片[6]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

确实是这样

那么我们如果传过去的是一个xss代码呢,比如<script> alert(1)</script>

那么 html是不是会变成这样:

html = '''
        <h3><script> alert(1)</script></h3>
    '''

然后再渲染到页面上是不是就触发了xss呢:

图片[7]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

成功触发了, 这就是最简单的模板注入。

不过他既然叫模板注入,现在看来 好像跟模板都没点关系。就是常规的xss啊有点

接下来才是进入正题:

SSTI基础测试

不正确的使用flask中的render_template_string方法会引发SSTI

在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。

也就是说在这套模板中 当程序解析碰见{{}} 它会把这个里面包裹的东西当作变量去的去执行。

来个简单的测试 如果我们传参?id={{2*2}}

那么html会变成这样:

html = '''
<h3>{{2*2}}</h3>
'''

2*2被当作变量去执行那么就是4了。

图片[8]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

如果我们输入{{config}} 他就会把config去当作变量名去处理

图片[9]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

然后显示出这个flask的环境变量。

接下来 我们要了解几个东西:

**class**  返回类型所属的对象(类)
**mro**    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
**base**   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
**subclasses**   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
**init**  类的初始化方法
**globals**  对包含函数全局变量的字典的引用

一个一个来说:

我们先传入一个字符串 并返回他是属于什么类

{{’aaa’.class}}

图片[10]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

属于字符串类。

我们 再来看看str类继承的父类有那些

{{’aaa’.class}}是str类 看他父类就后门再加上 . __ mro__

图片[11]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

找到了万物之父类 object ,而str 是在第0为 object是在第1位。

那么现在就是 ‘aaa’.__ class__.mro[1] 就表示object类了 我们看看他子类都有那些。再后面加一个__subclasses__()

图片[12]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

就返回了他的所有子类 很多,他这么多子类 肯定是有类可以执行一些系统命令的 比如 os._wrap_close

图片[13]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

我们找到后确定他的位置是多少。最后确定他是在123位

‘aaa’.__ class__.mro[1].subclasses()[123] 就指向这个类 我们想要调用它就要进行初始化 实例化相当于

于是:’aaa’.__ class__.mro[1].subclasses()[123].init 此时这个类已经实例化了 我们可以调用他自己的一些函数方法。

我们知道这个类的popen()函数可以执行系统命令。

图片[14]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

但是如果我们直接

‘aaa’.__ class__.mro[1].subclasses()[123].init .[’popen’] 是执行不了这个函数的,通俗来说就是他找不到你说的这个popen ,而加上__globals__后 他就能看到这个类所有的函数 相当于一个 可以去执行,相当于把这个类给全局变量了,任何地方都能调用它,于是:

‘aaa’.__ class__.mro[1].subclasses()[123].init .globals’popen’.read() 就可以执行系统命令了 查看ip 不要忘记最后再调用read函数让他处理一下才可读:

图片[15]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

这就能调用系统命令去了 。

这就是这个漏洞大致的原理利用,接下里说说怎么防御:

其实也很简单 我们传参都是 {{}} 在这里面放payload 那么如果我提前放好了呢?

图片[16]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客
图片[17]-Python-Flask(Jinja2)注入漏洞原理复现-Drton1博客

攻击就失效了。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 共1条

请登录后发表评论