(Python)PyYAML反序列化漏洞

基本概念

(引用百度)YAML是“YAML不是一種標記語言”的外語縮寫;但爲了強調這種語言以數據做爲中心,而不是以置標語言爲重點,而用返璞詞重新命名。它是一種直觀的能夠被電腦識別的數據序列化格式,是一個可讀性高並且容易被人類閱讀,容易和腳本語言交互,用來表達資料序列的編程語言。

PyYAML是Python中YAML語言的編輯器和解釋器。

安裝:pip install PyYAML

兩個函數:

yaml.dump():將一個Python對象序列化生成爲yaml文檔。

yaml.load():將一個yaml文檔反序列化爲一個Python對象。

簡單的用例:

可以看到,User對象經過yaml序列化之後內容爲一行字符串,簡單解釋一下:“!!pythonobject”爲yaml標籤,yaml.load()會識別該標籤並調用相應的方法執行反序列化操作;冒號後面的“__main__”爲py文件名,這裏爲本文件的意思;“User”爲序列化的對象類型,後面緊跟的大括號即爲該對象的屬性及其屬性值。

更詳細的說明可參考官方文檔:https://pyyaml.org/wiki/PyYAMLDocumentation

 

Demo

這裏編寫簡單的Demo,一個py文件用於將惡意類序列化爲字符串保存到yaml文件中,另一個py文件用於反序列化yaml文件內容爲惡意類對象從而達到利用反序列化漏洞的目的。

yaml_test.py

先創建一個poc對象再調用yaml.dump()將其序列化爲一個字符串,其中第10行代碼爲將默認的“__main__”替換爲該文件名“yaml_test”,目的是爲了後面yaml.load()反序列化該字符串的時候會根據yaml文件中的指引去讀取yaml_ test.py中的poc這個類,否則無法正確執行:

 

yaml_test2.py

直接yaml.load()讀取目標yaml文件,由!!python/object標籤解析其中的名爲yaml_test的module中的poc類,最後執行了該類對象的__init__()方法從而執行了命令:

 

漏洞根源分析

到$PYTHON_HOME/lib/site-packages/yaml/constructor.py中查看3個特殊Python標籤的源碼。

!!python/object標籤:

!!python/object/new標籤:

!!python/object/apply標籤:

可以看到,!!python/object/new標籤的代碼實現其實就是!!python/object/apply標籤的代碼實現,只是最後newobj參數值不同而已。這3個Python標籤中都是調用了make_python_instance()函數,查看該函數:

可以看到,在該函數是會根據參數來動態創建新的Python類對象或通過引用module的類創建對象,從而可以執行任意命令。

 

通用payload

只要存在yaml.load()且參數可控,則可以利用yaml反序列化漏洞,payload列舉如下,當然不止如下:

附上測試代碼:

import yaml

payload = '!!python/object/apply:subprocess.check_output [[calc.exe]]'
#payload = '!!python/object/apply:subprocess.check_output ["calc.exe"]'
#payload = '!!python/object/apply:subprocess.check_output [["calc.exe"]]'
#payload = '!!python/object/apply:os.system ["calc.exe"]'
#payload = '!!python/object/new:subprocess.check_output [["calc.exe"]]'
#payload = '!!python/object/new:os.system ["calc.exe"]'


yaml.load(payload)

 

一個疑問點?

爲什麼“!!python/object”標籤不好使,明明Demo用的是這個標籤,但通用payload中無法執行該payload?看了一些網上的文章也沒有分析原因,其實查看官方文檔就知道怎麼回事了:

可以看到,!!python/object標籤的使用格式和另外兩個根本就是兩碼事,其接收參數是使用大括號{}而非中括號[],且並沒有對參數args進行接收。也就是說,!!python/object標籤只針對於對象類進行使用。

 

檢測方法

全局搜索Python代碼中是否包含“import yaml”,若包含則進一步排查是否調用yaml.load()且參數是可控的。

 

防禦方法

使用安全函數yaml.safe_load()替代yaml.load()即可。

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