+3

JSON web tokens (JWT) attack - Tấn công JWT (phần 4)

III. Phân tích các phương pháp tấn công JWT và biện pháp ngăn chặn (tiếp)

4. Tấn công tham số jwk trong self-signed JWTs

Các server sử dụng JWT chứa tham số jwk xác thực người dùng thường lưu trữ danh sách hữu hạn các public keys hợp lệ trong một đường dẫn, ví dụ:

image.png

Tuy nhiên, có thể trong quá trình xây dựng ứng dụng, lập trình viên cần thực hiện kiểm thử chương trình nên đã cho phép server xác thực bằng public key được nhúng vào jwt bằng tham số jwk trong headers. Tới khi ứng dụng được sử dụng trong thực tế, tính năng này có thể đã bị "quên" loại bỏ! Dẫn đến kẻ tấn công có thể tự tạo một public key và nhúng vào tham số jwk. Cùng xem xét kỹ thuật tấn công này tại bài lab JWT authentication bypass via jwk header injection.

Sau khi đăng nhập thành công, sử dụng chương trình trong phần 2 quan sát giá trị headers:

image.png

Chú ý giá trị kid trong headers, dự đoán trang web sử dụng kid thực hiện xác thực bằng danh sách public keys được lưu trữ tại server. Tham số alg cho biết thuật toán sử dụng ở đây là RSA.

Chúng ta có thể tự xây dựng một RSA key theo format jwk để nhúng vào phần headers. Công cụ Burp Suite có một extension hữu ích là JSON Web Tokens:

image.png

image.png

Tiếp theo, chúng ta cần sinh lại JWT có phần headers theo định dạng mới như sau:

{
    "kid": "4b1efcfe-3193-4b39-af62-c9f9f8aef09c",
    "typ": "JWT",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "4b1efcfe-3193-4b39-af62-c9f9f8aef09c",
        "n": "wHElDzK7BtJaFi23i5Z7dpp9XxUiRqLH2ulSYEljMsIQ9T_AWM0wQNBrAfF0DemL9V5_0-n3e6ax4cLRLcz_DQ"
    }
}

Các bạn có thể sinh thủ công hoặc sử dụng chính extension JSON Web Tokens.

  • Bước 11: Chuyển request chứa JWT cần chỉnh sửa sang Repeater.
  • Bước 22: Click tùy chọn JSON Web Token

image.png

  • Bước 33: Sinh key RSA như trên.
  • Bước 44: Chọn Attack > Embedded JWK tại góc trái phía dưới phần Request

image.png

  • Bước 55: Tại cửa sổ mở ra, chọn id tương ứng với key vừa tạo và nhấn OK.

image.png

  • Bước 66: Lúc này có thể nhận thấy giá trị phần headers của JWT đã thay đổi. Thay đổi tùy ý giá trị sub trong phần body để mạo danh người dùng:

image.png

  • Bước 77: Cuối cùng chọn Sign > OK

image.png

Mạo danh tài khoản administrator thành công:

image.png

Để ngăn chặn việc tấn công Injecting self-signed JWTs thông qua tham số jwk, chúng ta không nên chấp nhận public key được chỉ định bởi người dùng.

5. Tấn công tham số jku trong self-signed JWTs

Việc sử dụng public key được nhúng trong jwt có thể chứa nhiều rủi ro, bởi vậy một số ứng dụng sử dụng tham số jku nhằm xác định một URL tham chiếu tới một bộ khóa công khai được đặt ở server.

Tuy nhiên, việc triển khai không đúng cách có thể tạo ra lỗ hổng bảo mật nghiêm trọng. Một cuộc tấn công tham số jku trong self-signed JWTs thường xảy ra một kẻ tấn công giả mạo JWT bằng cách thay đổi giá trị của jku để trỏ đến một URL mà kẻ tấn công kiểm soát. Khi truy cập tới URL này, ứng dụng sẽ lấy và sử dụng các public keys do kẻ tấn công tạo ra.

Ví dụ về kỹ thuật tấn công được thực hiện trong bài lab JWT authentication bypass via jku header injection

Sau khi đăng nhập tài khoản, sử dụng extension JWT Editor Keys, chúng ta sẽ kiểm tra server có chấp nhận tham số jku hay không. Thực hiện thêm tham số jku trong phần Header JWT với giá trị sinh từ Collaborator client:

image.png

Sau khi gửi request, response trả về status code 302302, hệ thống chuyển hướng chúng ta tới trang login, đồng thời kiểm tra tại mục Collaborator nhận thấy có kết quả tương tác trả về:

image.png

Điều này chứng tỏ ứng dụng xác thực danh tính người dùng bằng cách truy cập tới một URL chứa danh sách các public keys, kiểm tra ánh xạ qua giá trị tham số kid trong JWT. Tuy nhiên, khi tham số jku tồn tại trong JWT, giá trị URL này được ghi đè, dẫn đến ứng dụng sẽ truy cập tới jku trong JWT.

Do sự cài đặt sai sót này, chúng ta có thể tự dựng một trang web chứa danh sách các public keys, từ đó giả mạo tham số jku để ứng dụng truy cập tới trang web giả mạo đó. Trong trường hợp này bài lab đã cung cấp một explit server cho công việc đó.

image.png

Sử dụng JWT Editor Keys sinh một bộ khóa RSA dạng JWK.

image.png

Tại exploit server, đưa các tham số kty, e, kid, n vào phần body, đặt tên đường dẫn tùy ý, chẳng hạn /public-key.json. Khi truy cập tới /public-key.json có kết quả:

image.png

Trong phần Header JWT, thêm tham số jku có giá trị là đường dẫn URL exploit server, đồng thời thay đổi giá trị kid ánh xạ với bộ khóa trong exploit server. Thay đổi tham số sub trong phần Payload thành administrator. Chọn Sign với bộ key chúng ta đã sinh.

image.png

Giả mạo tài khoản administrator thành công:

image.png

Để ngăn chặn kỹ thuật tấn công này, khi giải mã JWT, server nên kiểm tra các tham số trong JWT và đảm bảo rằng chúng tuân thủ các quy tắc và giới hạn đã được định nghĩa trước. Đặc biệt, không cho phép người dùng thêm tham số jku trong JWT. Ví dụ hàm kiểm tra:

// Hàm kiểm tra và giải mã JWT
function verifyAndDecodeJWT(token) {
  try {
    const decoded = jwt.verify(token, 'JWT_SECRET_KEY');

    // Kiểm tra các quy tắc và giới hạn khác
    if (decoded.jku) {
      throw new Error('Invalid JWT: "jku" parameter is not allowed.');
    }

    // Các xử lý khác khi JWT hợp lệ
    // ...

    return decoded;
  } catch (error) {
    console.error('JWT verification failed:', error.message);
    return null;
  }
}

6. Tấn công tham số kid trong self-signed JWTs

Ngoài việc được sử dụng để xác định khóa công khai (public key) hoặc khóa bí mật (private key) trong xác minh chữ ký của JWT, kid có thể được chỉ định giá trị đường dẫn trỏ đến tệp chứa thông tin khóa xác minh. Kẻ tấn công có thể nhắm vào tham số này với kỹ thuật Directory traversal, khiến trang web tìm tới các tệp tùy ý trong server. Ví dụ đoạn mã PHP sau đây mô phỏng việc xác thực và xử lý JWT trên máy chủ chưa an toàn:

// Lấy JWT từ request
$jwt = $_GET['token'];

// Giải mã JWT
$decoded = jwt_decode($jwt);

// Lấy giá trị của kid từ JWT
$kid = $decoded['kid'];

// Kiểm tra và lấy khóa xác minh tương ứng với kid
$verificationKey = getVerificationKey($kid);

// Xác minh chữ ký của JWT bằng khóa xác minh
$isValid = verifySignature($jwt, $verificationKey);

if ($isValid) {
    echo "JWT is valid!";
} else {
    echo "JWT is invalid!";
}

// Hàm giả lập giải mã JWT
function jwt_decode($jwt) {
    // Giả sử JWT được giải mã thành mảng dữ liệu
    return json_decode(base64_decode($jwt), true);
}

// Hàm giả lập lấy khóa xác minh từ kid
function getVerificationKey($kid) {
    // Lấy khóa xác minh từ kid (lỗ hổng: không kiểm tra kid)
    $verificationKey = file_get_contents($kid);
    return $verificationKey;
}

// Hàm giả lập xác minh chữ ký JWT
function verifySignature($jwt, $verificationKey) {
    // Giả sử thực hiện xác minh chữ ký
    // Trong trường hợp này, chúng ta không kiểm tra tính hợp lệ của khóa xác minh (lỗ hổng)
    return true;
}

Trong đoạn mã trên, ứng dụng không kiểm tra tính hợp lệ của kid khi lấy khóa xác minh, dẫn đến kẻ tấn công có thể sử dụng tham số kid để truy cập vào các tệp tin tùy ý trên hệ thống và làm khóa xác minh. Một trong những phương pháp đơn giản nhất để tận dụng lỗ hổng này là sử dụng đường dẫn tệp tin /dev/null, một tệp tin trống rỗng có mặt trên hầu hết các hệ thống Linux. Khi server đọc nó sẽ trả về một chuỗi rỗng. Do đó, kẻ tấn công có thể ký JWT bằng một chuỗi rỗng dẫn tới trang web xác minh JWT chứa chữ ký hợp lệ.

Bạn đọc có thể luyện tập kỹ thuật tấn công này trong bài lab JWT authentication bypass via kid header path traversal.

Như vậy, trước khi sử dụng kid, hãy đảm bảo rằng tính hợp lệ của nó. Có thể sử dụng một danh sách hợp lệ đã biết trước hoặc xác minh kid với một nguồn tin cậy khác như cơ sở dữ liệu. Đồng thời, chỉ cung cấp quyền truy cập tối thiểu cần thiết cho các tệp tin và tài nguyên. Chương trình ví dụ:

// Danh sách các "kid" hợp lệ
const validKeys = ['key1', 'key2', 'key3'];

// Hàm kiểm tra tính hợp lệ của JWT và "kid"
function verifyJWT(token, kid) {
  try {
    // Kiểm tra tính hợp lệ của "kid"
    if (!validKeys.includes(kid)) {
      throw new Error('Invalid "kid"');
    }

    // Xác minh JWT và lấy khóa xác minh
    const decoded = jwt.verify(token, getVerificationKey(kid));

    // Thực hiện xử lý sau khi xác minh thành công
    // ...
    
    return decoded;
  } catch (error) {
    console.error('JWT verification error:', error.message);
    return null;
  }
}

Các 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í