主要内容学习自 B站 重庆橙子科技 SSTI 模板注入。
我力推橙子科技的所有 CTF 课程!
由于在模板注入时,我们能够注入的地方,终究只是一个变量,而不是 Python 语句。所以直接注入 Python 语句是不能执行的,需要一些特殊操作让变量类型转换为函数或方法。这个转换过程,就涉及到了 Python 中类的继承关系以及魔术方法的使用。在这之前,我们应该先理解面向对象技术。
一、继承关系
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系。—菜鸟教程 Python 基础教程
在 python 中,类之间是会有继承关系的,也就是派生类(子类)与基类(父类)的关系,这可以理解为父子关系。在这个父子关系中的最高级,就是 object
。也就是说,object
是祖宗类。
一般来说,SSTI 构造 payload 的思想,就是要通过各个数据类型 Numbers(数字)String(字符串)List(列表)Tuple(元组)Dictionary(字典)
这些子类,一直往上找到 object
,然后再通过找 object
类可以利用的子类。可以利用的子类,就是这个子类的方法(popen()
eval()
等方法)或属性可以利用。
二、魔术方法
在 python 中,魔术方法是一种两边以双下划线 __
包裹的特殊方法,利用这些方法,我们可以实现类的寻找,初始化对象的成员,以及最后的利用。
常用魔术方法及作用
单下划线、双下划线、头尾双下划线说明:
__foo__: 定义的是特殊方法,一般是系统定义名字 ,类似__init__()
之类的。
_foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于from module import *
__foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。—菜鸟教程 Python 基础教程
__class__
: 以字符串形式返回当前对象所属类
__base__
: 以字符串形式返回当前类的基类
__bases__
: 以元组()形式返回当前类的所有基类
__mro__
: 以元组()形式返回从当前类到 object 类的所有类,顺序为从子类逐级往上。 __mro__[1]
或者 __mro__[-1]
都可以得到 object 类。
__subclasses__()
: 以列表[]形式返回当前类的下一级所有子类
__init__
: 构造函数,当类被实例化时可以用它来快捷的初始化一些属性。SSTI 中可以用它获取选定子类的初始化方法。
__globals__
: __globals__在函数后使用,以字典形式返回当前位置的所有全局变量,包括所有导入的变量。当其在 __init__
后使用时,即获取初始化方法的全局变量字典。这些变量可能是模块、方法、变量。
通过以上魔术方法,再配合一些系统命令,就可以构造出基本的 payload 了。
一个示例:
name={{''.__class__.__mro__[-1].__subclasses__()[199].__init__.__globals__['os'].popen("ls -l /opt").read()}}
另一个示例:
name={{"".__class__.__base__.__subclasses__()[139].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ifconfig').read()")}}
payload 前的 ''
表示一个空字符串,类似的,还可以使用:"" 字符串
() 元组
[] 列表
{} 字典
。它们都是数据类型的实例对象。
为什么是[199]、[139]?
因为这他们含有执行系统命令需要的函数 eval() popen()等,关于这个的更多细节,将在下节提到。
事实上,ssti 的 payload 利用方法,构造形式很多,这涉及到很多对 python 的理解。所以我得抓紧补补 python 了。
参考
Flask基础及模板注入漏洞(SSTI)
SSTI模板注入总结
Python 面向对象 | 菜鸟教程
2分钟让你明白什么是面向对象编程
ssti小总结
By QING