Link tại đây.
Bài cho 1 form login.
Sau đó login với guest
Kiểm tra cookie:
jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.OnuZnYMdetcg7AWGV6WURn8CFSfas6AQej4V9M13nsk
Sau đó decode JWT này tại jwt.io. Kết quả:
Xuất hiện Invalid Signature
tất nhiên signature
không đúng. Thuật toán đang sử dụng ở đây là HS256
, ta thử chuyển nó sang none
và xóa phần Signature
của nó đồng thời sửa guest
thành admin
. Có thể làm thủ công hoặc dùng extension JSON Web Tokens
trên Burp Suite:
JWT mới: eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImFkbWluIn0.
Kết quả:
Như vậy là ứng dụng sử dụng key để verify
JWT nhưng cũng bỏ qua nó khi không dùng thuật toán ("alg": "none"
)
Flag:
S1gn4tuR3_v3r1f1c4t10N_1S_1MP0Rt4n7
Link tại đây
Vào bài thì tại /hello
trả về thông báo: Let's play a small game, I bet you cannot access to my super secret admin section. Make a GET request to /token and use the token you'll get to try to access /admin with a POST request.
Như vậy:
/token
chứa token (sử dụng method GET)/admin
nơi verify token (sử dụng method POST)Vào /token
:
Token nhận được:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiZ3Vlc3QifQ.4kBPNf7Y6BrtP-Y3A-vQXPY9jAh_d0E6L4IUjL65CvmEjgdTZyr2ag-TM-glH6EYKGgO3dBYbhblaPQsbeClcw
Decode tại jwt.io :
Nó cho ta biết thuật toán sử dụng là HS512
(đây là mã hóa đối xứng dùng mộtsecret key
để mã hóa cũng và giải mã ) có payload "role": "guest"
và mục tiêu là đổi guest
sang admin
.
Thử lấy token đó verify ở /admin
:
Thử đối thuật toán sang none
như bài trên nhưng tất nhiên là không được. Nhìn lại tiêu đề là Weak secret nghĩ ngay đến việc brute-force. Ta sử dụng hashcat
cùng với wordlist là rockyou.txt
:
Kết quả:
secret key
là lol
. Bây giờ verify lại jwt:
JWT mới:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4ifQ.y9GHxQbH70x_S8F_VPAjra_S-nQ9MsRnuvwWFGoIyKXKk8xCcMpYljN190KcV1qV6qLFTNrvg4Gwyv29OCjAWA
Flag:
PleaseUseAStrongSecretNextTime
Link tại đây
Bài cho source:
Có 2 endpoints:
Oke bây giờ vào /login
: Nó sẽ yêu cầu json chứa username
và password
đều có giá trị là admin
sẽ được token:
Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODU5MzU0NzcsIm5iZiI6MTY4NTkzNTQ3NywianRpIjoiZGIwZmQwYjAtZjM1Zi00MjEzLWI5MTEtZWRmZmNhODdlN2Q2IiwiZXhwIjoxNjg1OTM1NjU3LCJpZGVudGl0eSI6ImFkbWluIiwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.aqfOmz1hmOdxdz3C5Fg3OksrfQ0ZnYUedP-s9g_CzVg
Thử dùng nó trong /admin
qua header Authorization: Bearer <token>
:
Token is revoked
. Đúng vậy vì sau khi tạo thì token sẽ được add vào backlist
:
Và nếu token dùng trong header Authorization
không trong blacklist
thì mới hiện flag:
Token vừa tạo xong đã bị cho vào blacklist
thì sao bypass được ???
Thử decode nó:
Bản chất việc revoke token giống như việc xóa bỏ nó ngay sau khi xác thực thành công. Code trong bài này sẽ so sánh xem có token nào y hệt trong blacklist không, vậy có thể biến đổi token để nó khác ban đầu mà nội dung không thay đổi? Nhìn lại cấu trúc JWT:
Đó là cả 3 phần đều được encode bằng Base64
, điều quan tâm ở đây là Base64
sẽ padding thêm dấu =
nếu thiếu ký tự và nó cũng sẽ bỏ nó nếu đủ. Vậy chỉ cần thêm dấu =
vào token thì đc token mới không nằm trong blacklist
nhưng nội dung vẫn vậy. Ta sẽ thêm dấu =
vào cuối (signature
) vì nếu thêm vào header
hay payload
sẽ thay đổi signature
.
Kết quả:
Flag:
Do_n0t_r3v0ke_3nc0d3dTokenz_Mam3ne-Us3_th3_JTI_f1eld
Link tại đây
Xem source thì thấy có endpoint /admin
và có jwt trả về từ server trong cookie và nó dùng để verify vào endpoit đó:
Decode JWT:
Đầu tiên mình thử làm theo như mấy bài trên nhưng tất nhiên đều không được. Xem lại đầu bài có nhắc đến (K)ind (I)dentification (D)ance
dễ dàng biết được bài này liên quan đến kid
.
kid
(Key ID): Cung cấp id
mà server có thể sử dụng để xác định secret key
trong trường hợp có nhiều secret key
để lựa chọn. Nó nằm trong phần header
của JWT. Trong nhiều trường hợp nó không chỉ là id
mà còn là một đường dẫn đến 1 file chứa secret key
và server sẽ lấy nội dung của file đó để verify. Cụ thể ở đây khi bạn thay đổi giá trị kid
thành abc
:
Như vậy là server sẽ lấy nội dung trong file abc
của thư mục keys
làm khóa và ở đây file abc
không được tìm thấy. Vậy nếu chúng ta biết được nội dung của 1 file rồi lấy nó làm khóa (secret key
) thì sẽ verify thành công. Tuy nhiên điều đó có thể khó khả thi vì làm được khi file nào đó bị leak ra.
Đơn giản nhất là lợi dụng /dev/null
trên linux, đây là file rỗng và kid
trỏ đến đây thì sẽ key
lấy được là null. Liên tưởng đến lỗ hỗng LFI
để kid
trỏ đến /dev/null
bằng payload ../../../../
và sign nó bằng giá trị null
là AA==
(dạng base64). Tuy nhiên khi kid
có giá trị là ../../../../../../../dev/null
thì kết quả là keys/dev/null
Có vẻ chưa ../
đã bị replace. Ta bypass cái này bằng ....//....//....//....//....//....//....//dev/null
Lấy JWT mới set lại cookie, kết quả:
Flag:
RM{Uns3cUr3_f1l3_H4ndl1nG!!}
Link tại đây
Như phần Statement thì có 3 endpoints:
/key
lấy public key
(dùng method GET)/auth
tạo token (dùng method POST)/admin
verify để lấy flag (dùng method POST)Đầu tiền vào /key
để lấy public key
, format lại được:
Phần /auth
yêu cầu: You have to provide 'username=YOURNAME' in your POST request
nên mình username=chuong
(không nhận giá trị admin
) và nhận được token:
Sau đó dùng token này vào /admin
qua header Authorization: Bearer <token>
:
Decode nó xem thử:
JWT sử dụng thuật toán RS256. Đây là thuật toán asymmetric (bất đối xứng), trong đó private key được sử dụng để sign JWT và public key được sử dụng để verify signature của JWT.
Vậy hướng khai thác ở đây là gì? Sau một lúc tìm hiểu về phần này thì đó là thay đổi thuật toán. HS256 chỉ có 1 secret key để sign cũng như verify trong khi RS256 như mình đã nói sử dụng 2 key như trên. Vậy nếu chuyển từ RS256 sang HS256 thì sao? Thì HS256 sẽ dùng public key để verify (thứ mà bài đã cho).
Việc bây giờ là từ public key đó tạo signature cho JWT.
Mình sẽ dùng jwt_tool.py để tạo bằng:
public.key
là file chứa public key mà bài cho. Và nhớ sửa giá trị username
thành admin
.
Kết quả:
Flag:
HardcodeYourAlgoBro
Link tại đây
Oke bắt đầu vào:
Có chỗ login bằng JWT:
Tuy nhiên không có chỗ tạo token nên mình lấy cái placeholder
để verify xem sao:
Có vé phải login bằng user
là Neo
, decode thấy phần payload
chứa message
có vẻ không liên quan nên nên mình bỏ đi cho gọn, và tất nhiên thì verify vẫn thất bại rồi:
Như tiêu đề của bài là Header Injection. Mình đã tìm hiểu sơ qua về phần này và sẽ tổng quát lại nó như sau:
Như ta biết thì phần header
của JWT có 1 số tham số hay gặp như alg
, typ
, ngoài ra còn 1 số nữa dễ bị khai thác:
kid
: mình đã nói ở bài JWT - Unsecure File Signature
jwk
(JSON Web Key): Cung cấp một đối tượng JSON được đại diện cho khóa.jku
(JSON Web Key Set URL): Cung cấp một URL mà từ đó các server có thể tìm nạp một bộ khóa chứa khóa chính xác.Ở bài này do mình đã làm rồi và xác định là khai thác dựa vào jwk
nên mình sẽ nói về nó luôn.
Ví dụ về jwk
:
Nó đại diện cho public key của RSA
, kid
này cũng định nghĩa như trên, n
, e
là tham số liên quan về RSA
(liên quan đến việc tính toán của thuật toán này).
Tham số jkw
trong bài có thể kiểm soát từ người dùng mà server lại không xấc thực nó => bị tấn công. Ý tưởng ở đây là tạo private key sau đó nhúng jwk
đó vào header
.
Đầu tiên mình sẽ tạo RSA key bằng extension JWT Editor Keys
của Burp Suite
: chọn New RSA Key
-> Generate
(không cần quan tâm giá trị bit vì nó sẽ được điều chỉnh lại).
Tiếp theo dùng extension JSON Web Token
để nhúng nó vào:
Attack
-> Embedded JWK
-> chon khóa rồi Ok
.
JWT tạo được:
eyJraWQiOiJiZDFlMDZmYy03YTBhLTQ5MWUtYjU1MC02YWYxMTQ4MTRjMmIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6ImJkMWUwNmZjLTdhMGEtNDkxZS1iNTUwLTZhZjExNDgxNGMyYiIsIm4iOiJsd2lUNWhUOVhPOHptaVRoRW5BX3A0aWtqblUxZkJyV2tYVWNTRVRwT19DNWZEeHl1dmVIV1A1T1NpdG4tbjNXME9SSUFWMHlTeW9ZeVhEZnFSMFloZGFiZk16WHR2cnB1b1BucXlPRGpMUHBvR1lJSFV4MGhHekdNajlPbFFMQl9pNTF6ZENGWF9JNXF5bEhPUndiNXhKcUNMcUhCUU5JakFPUjRPM1czSF8wdGZhaGhtT29fblVLRFFob3NMblFNc2N1bGZYUDZvVmFfOHZpWVZKNDAwSTdVemxqYzRTSElzb0lUUnZFR0FjTVRaRFZXd3o2NmZ2QTlJZ3FLa3V1YnVVV293OU5yTE9wTDdxM1RaYU12UzJ3NWI3TnQwMTlOaDA3bnRXcDE4RjlQRG9NT0dWVVdXVTJ0UXlMTVRXX0dQMnp1aDRyMXFFSVNVMXhGWGU5MFEifX0.eyJ1c2VyIjoiTmVvIiwiaWF0IjoxNjc2NjM3ODMyfQ.M0n_hBVOnzi-j751yiqzUmhVCPLxhrVZX4lK6Tfyc34Nm_-3gTYeKOwOtecb8CbYgjz6nWnbVQIIKleeSsVNcIlfYH8e1n2wJ6JAyNvqCCHHA5W7dzWX9hnZ0tiRSLNurNAutp-ghYjTfQouTZc4MyLAwx00YqsQtiCKQQqBXXOXq8A5-vhdPWp7czxy8LT1C5GqM7dWBsyeFZMHa5a-l4wVKmHvKZunG0PWxr1p6xPsZ5DTgPWyexPITjreiGJoEKCXmr5beUAnzu2Q29YA6s_XLNoUUSp6B6O5W_iGPPez8cSLZhmnK-knOqa7Wof6kK3Q33e6d4CBtgnyEtV4_w
Giờ verify nó, kết quả:
Flag:
RM{N3v3r_All0w_UnTrusTed_K3ys}
Link tại đây
Xem qua dễ dàng tìm được endpoint /admin
. Và có được JWT, và tất nhiên không phải admin rồi:
Decode JWT:
Nhận thấy có "jku": "jwk.json"
nghĩ ngay đến việc inject vào jku
này rồi. Nhưng sau một hồi fuzz thì mình không tìm được gì trên trang web, có vẻ jwk.json
được lưu vào đâu đó không biết được. Tuy nhiên khi thay đổi jku
thì:
Như vậy address phải bắt đầu là http://localhost
Mình nghĩ này đến các ký thuật SSRF nhưng bằng nhiều cách vẫn không được. 1 tháng sau thì mình gặp được bài này :3
Như vậy là tạo 1 site bắt đầu với localhost (của mình sẽ là http://localhostchuong.000webhostapp.com
) bằng 000webhost.com và có jwk.json
chứa thông tin key tạo qua burp(tham khảo bài trên):
Key tạo bằng burp:
Sẽ được:
Và giờ chỉ việc sign bằng key có sẵn:
Kết quả:
Flag:
RM{4lw4y5_ch3ck_th3_JKU_C0rr3ctly}
Mình có vẽ 1 mindmap nhỏ về JWT Attacks mà mình tìm hiểu được ^^
Một số nguồn tham khảo thêm:
Một số tools: