0

API testing (phần 2)

IV. Server-side parameter pollution

1. Internal API routing

Một cách routing phổ biến hiện nay là sử dụng internal API: ứng dụng client gửi một yêu cầu đến server với các tham số cơ bản, sau đó server xử lý yêu cầu này và tạo một yêu cầu khác đến internal API (API nội bộ) để lấy thông tin cần thiết trả về cho client. Cụ thể các bước với ví dụ như sau:

Request từ client đến server

Khi người dùng tìm kiếm một người dùng cụ thể (ví dụ peter), trình duyệt sẽ gửi yêu cầu tới server, thường là một API public trên server:

GET /userSearch?name=peter&back=/home

Yêu cầu này chỉ chứa các tham số cần thiết cho frontend, chẳng hạn name=peter để tìm kiếm người dùng và back=/home để xác định vị trí người dùng sẽ quay lại sau khi hoàn tất thao tác.

Server xử lý yêu cầu và sử dụng internal API

Sau khi nhận yêu cầu từ client, server sẽ xác định và lọc các tham số cần thiết cho backend. Tiếp theo tạo một yêu cầu mới đến API nội bộ để lấy dữ liệu phù hợp với logic kinh doanh của ứng dụng:

GET /users/search?name=peter&publicProfile=true

Các tham số có thể bao gồm cả các trường thông tin mà client không yêu cầu nhưng backend cần để xử lý đúng yêu cầu. Do đó, nếu logic hoạt động không chặt chẽ, quá trình này có thể tồn tại lỗ hổng parameter pollution - attacker có thể inject và thay đổi giá trị các tham số mà internal API sẽ sử dụng. Chúng ta xem xét dạng lỗ hổng này trong một số bối cảnh:

  • Query string parameter pollution
  • Data formats parameter pollution
  • REST paths parameter pollution

2. Query string parameter pollution

Để thấy rõ về các dấu hiệu server trả về và hướng suy luận về cách khai thác parameter pollution trong query string, chúng ta sẽ cùng phân tích bài lab Exploiting server-side parameter pollution in a query string.

Bài lab không cung cấp tài khoản bất kỳ, quan sát nhận thấy chức năng quên mật khẩu yêu cầu nhập username, có API /forgot-password

image.png image.png

Từ API có thể dễ dàng tìm thấy tài khoản admin có username là administrator:

image.png

Trước hết, chúng ta có thể kiểm tra cách server nhận và xử lý các tham số truyền lên, thử truyền thêm một tham số không tồn tại x=y theo hai cách.

Không mã hóa ký tự &:

image.png

Mã hóa URL encoded ký tự &:

image.png

Từ đây có thể suy luận rằng server có thể đã sử dụng giá trị của tham số username và yêu cầu một internal API nào đó, chẳng hạn /private/api?username=. API này đã nhận cả tham số x không cần thiết nên trả về thông báo lỗi Parameter is not supported. (parameter không được supported chính là x)

Tiếp theo, chúng ta có thể kiểm tra cách server nhận giá trị tham số nào nếu xuất hiện hai tham số y hệt nhau:

username=administrator%26username=not_exist

image.png

username=not_exist%26username=administrator

image.png

Từ hai response có thể thấy khi client gửi hai tham số y hệt nhau, server sẽ nhận giá trị của tham số đầu tiên. Như vậy, giả sử khi server gọi tới internal API và theo sau tham số username còn có một tham số ẩn khác:

/private/api?username=<input từ người dung>&unknown_param=???

Thì chúng ta có thể ghi đè được giá trị của tham số unknow_param này.

Thực hiện kiểm tra internal API có tham số ẩn theo sau hay không bằng cách dùng ký tự # "cắt" chuỗi tham số. Truyền giá trị username=administrator%23, internal API sẽ có dạng:

/private/api?username=administrator#&unknown_param=???

Ký tự # có nhiệm vụ "cắt bỏ" toàn bộ ký tự theo sau nó, với "mong muốn" khi mất đi tham số ẩn unknown_param server sẽ báo lỗi, thật vậy:

image.png

Với thông báo Field not specified., có thể dự đoán tham số ẩn ở đây là field, gửi data username=administrator%26field=123 kiểm chứng cho điều đó:

image.png

Lab này thực sự phải suy luận và "đoán" khá nhiều, tới bước này chúng ta cần phải tiếp tục tìm được giá trị hợp lệ của tham số ẩn field này. Sử dụng tính năng Intruder fuzzing với wordlist là giá trị của các param (chẳng hạn như https://github.com/whiteknight7/wordlist/blob/main/fuzz-lfi-params-list.txt)

image.png

Thu được một số giá trị hợp lệ như username, email:

image.png

Từ response có thể thấy response trả về dữ liệu theo tham số field.

Nhưng hai giá trị này chưa có thông tin chúng ta cần. Một chức năng reset password thường sẽ hoạt động gồm bước sinh ra token gửi đến người dùng, server dựa vào token này xác định chủ sở hữu của tài khoản. Chúng ta mong muốn tìm kiếm được giá trị này. Ngoài cách brute force, chúng ta có thể tìm kiếm thông tin qua các file javascript. Quan sát Burpsuite HTTP History chúng ta có file forgotPassword.js đáng chú ý:

image.png

Trong đó hàm forgotPwdReady có chứa giá trị reset_tokenresetToken có thể là giá trị cần tìm:

image.png

Thay vào payload nhận token dùng để đặt lại mật khẩu, chúng ta có thể chiếm đoạt tài khoản người dùng bất kỳ khi biết username của họ:

image.png

Bên cạnh phương thức tìm kiếm thông tin từ file javascript, chúng ta cũng có thể sinh ra các giá trị theo từ khóa, chẳng hạn với chương trình python sau cho phép sinh ra các giá trị dựa vào danh sách từ khóa người dùng cung cấp:

import sys
from itertools import permutations, product

def generate_keyword_combinations(keywords):
    special_chars = ['', '-', '_']
    combinations = set()

    for perm in permutations(keywords):
        for chars in product(special_chars, repeat=len(keywords) - 1):
            combined = perm[0]
            for word, char in zip(perm[1:], chars):
                combined += char + word
            combinations.add(combined)

    for perm in permutations(keywords):
        camel_case = perm[0].lower() + ''.join(word.capitalize() for word in perm[1:])
        combinations.add(camel_case)

    return sorted(combinations)

if __name__ == "__main__":
    keywords = sys.argv[1:]
    if not keywords:
        print("Please provide keywords separated by spaces.")
    else:
        output = generate_keyword_combinations(keywords)
        for item in output:
            print(item)

image.png

Với cách thức này chúng ta cũng có thể tìm ra giá trị reset-token, kết quả sẽ tùy thuộc vào độ phủ của chương trình với các trường hợp có thể xảy ra và danh sách từ khóa cung cấp từ người dùng.

Bài tập dành cho bạn đọc: Hãy tinh chỉnh chương trình trên để "phủ" được nhiều trường hợp hơn, từ đó sinh ra các wordlist lớn hơn.

3. Data formats parameter pollution

Data formats parameter pollution khác với query string parameter pollution khi các tham số được truyền vào internal API với định dạng sử dụng trong gói tin POST, PATCH, ... thường là định dạng JSON. Chúng ta cần có một cách xây dựng payload khác. Chúng ta xem xét hai dạng phổ biến.

Dạng 1: x-www-form-urlencoded \rightarrow json

Gói tin từ client tới server:

POST /myaccount
...

name=peter

Gói tin internal API:

PATCH /users/7312/update
...

{"name":"peter"}

Để thêm tham số ẩn access_level, có thể xây dựng gói tin chứa payload:

POST /myaccount
...

name=peter","access_level":"administrator

Kết quả gói tin internal API trở thành:

PATCH /users/7312/update
...

{name="peter","access_level":"administrator"}

Dạng 2: json \rightarrow json

Gói tin từ client tới server:

POST /myaccount
...

{"name": "peter"}

Gói tin internal API:

PATCH /users/7312/update
...

{"name":"peter"}

Để thêm tham số ẩn access_level, có thể xây dựng gói tin chứa payload:

POST /myaccount
...

{"name": "peter\",\"access_level\":\"administrator"}

Kết quả gói tin internal API trở thành:

PATCH /users/7312/update
...

{name="peter","access_level":"administrator"}

4. REST paths parameter pollution

Khi internal API sử dụng dạng RESTful API, các giá trị tham số được đưa trực tiếp vào REST path của URL thay vì sử dụng query string (tham số đi kèm sau ký tự ?). Ví dụ, client gửi yêu cầu để xem thông tin hồ sơ người dùng có id=1, gói tin có nội dung:

GET /profile?id=1
Host example.com

Interal RESTful API sẽ yêu cầu một gói tin khác như sau:

GET /api/profile/1/info
Host internal.example.com

Trong trường hợp tấn công trực tiếp lỗ hổng IDOR (tham khảo thêm tại https://viblo.asia/p/access-control-vulnerability-lo-hong-kiem-soat-truy-cap-phan-1-3RlL5YrzLbB) ở tham số id thất bại, chúng ta có thể tiếp cận theo hướng khai thác REST paths parameter pollution với ý tưởng "lùi thư mục".

Chẳng hạn với payload id=1%2f..%2f2 để đọc thông tin hồ sơ người dùng có id=2. Internal API trở thành /api/profile/1/../2/info chính là /api/profile/2/info.

Bạn đọc có thể luyện tập kỹ thuật khai thác này trong bài lab Exploiting server-side parameter pollution in a REST URL

Tài liệu tham khảo


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í