Skip to content

Hướng tiếp cận Cryptography trong Pentesting

Trong cái thế giới của pentester, ta thường gặp những người lạ với mặt mũi chẳng thân thiện, khó nhìn dạng dạng như:

[1] http://iis7.com/ScriptResource.axd?d=nfTSVpD0_IBwmYy8ATRw-OaZgSpDcUHd5XOotuxk3skAAAAAAAAAAAAAAAAAAAAA0
[2] https://private_domain.com/dl_file_encrypt?link=%2F%2Bewlk6RqFFWFScSyyf%2BGyHYEY%2FDjeHlqjgQz4huZTZhBXNkBZihGjIgbLCq1A6jp9%2BMKTjB7hxDJeK00dUdbgO56FdNU33%2FXazZLF2rqvXpqBqcLPc%2FTVPj
[3] {"card_value":50000,"card_code":"Vd8jkArK7PulAeNUlVme/muQxncH9lvaNAbCDz74tArZO/BuNB40mSGzjqGaIbjOkaOGtBmW8Xgj+y2hHeQFvfkwT/xiEsmBwdDEyIKQOvVGt4WbsjU258cLRCP7/EBa4yuLgxm7i2Hob17w7DoqrwoCmZxy4S1UfpNrdjmtk0ki7h9lPG07t5dZ5D0bbMD5qrtqhah2iycaYpw1b96gAhwnruyWjMuKcdYHyWlTDn3jNgQ/X6vd9UkAWjxe9vnQVcrj7kY5RK5u3q09yMlkveT1+uSitSN+SbTlx7jTGt1hyhcjTnWyDdALJbY2vny5Z0liwXEaqEnnXD1yyZgmQ==","card_serial":"xx0022982xxxxx","content":null}
….

mà ta tạm gọi đó là các encrypted strings, encrypted params.
Với việc mã hóa các tham số như trên, cũng có thể là cách giúp cho hệ thống được an toàn hơn.
Ở góc độ pentester, hướng tiếp cần đầu tiên với các entry point như trên là xác định phương thức mã hóa được sử dụng là dạng gì, sau đó tìm cách giải mã hoặc break các phương thức mã hóa, giải mã.

Lướt qua một chút về các dạng mã hóa, hàm băm phổ biến:

  • Base64, base64_url, hex: mục đích để từ stream byte về dạng printable.
  • XOR (khá là Diễm xưa)
  • Tự define encryption(), decryption() -> việc làm này mang tính rủi ro cao.
  • AES, RSA
  • Hash (MD5, SHA1, SHA2 , HMAC, scrypt, bcrypt, PBKDF2, argon2 … )

Ứng với đó là một số attack vector phổ biến:

  • XOR: Bit flipping, ManyTimePad, Crib Drag…
  • AES: Padding Oracle (CBC mode), Counter mode
    (Như rằng entry point [2] trên là LFI (version Crypto) do phía backend xài AES-CBC mode + hệ thống xử lý exception không tốt)
  • RSA: Factor được modulus, hoặc common modulus … [tuy nhiên mấy dạng này thường chỉ tồn tại trong CTF (Mã hóa sử dụng trong [3] là RSA, dựa trên độ dài bản mã (khá lớn) và bản rõ (là thẻ nạp độ dài max chỉ 15 byte), đây là dữ liệu mà server response về App mobile, app thực hiện giải mã phía client, tuy nhiên source bị Obfus, nên khi biết RSA được dùng ở đây, ta sẽ đi tìm private key được hashcode đâu đó trong resource bằng cách grep theo pattern đặc trưng của private key]
  • Hash length extension: là một lỗ hổng trên 10 năm tuổi tuy nhiên đến tận bây giờ nhiều khi “Trên đường đời tấp nập, ta vô tình đã chạm mặt vào em”, hay thỉnh thoảng vẫn có xuất hiện trong một số cuộc thi CTF
  • Hash collision (đây kiểu như cái scope quá rộng nên xin được trân trọng trình bày trong một bài viết riêng)

Nội dung tiếp theo của bài viết xin được trình bày về hướng tiếp cận Cryptography trong một case pentest, và trên target đó ta thu lượm được 04 entry point tiềm năng như sau:

[#1] https://labvuln.com/rs_passwd?tk=2f7e4e541ecc2d5bfb062101cc45386c9e1d0eba5bfaa294dc531b0adb02faa1

[#2] https://labvuln.com/payment?tk=911a9921cb5d9a18403efcd784aaed7a258d91d8ecffc08fa519b1f51d9951b13a5461996c5717e4dbe5882891 
 
[#3] https://labvuln.com/rs_passwd?tk=84f02d074d97c35de19a71992c0e92fb:9ee21dd820f38ef903469c3d606f6ea0277eb8dd7f94278c3051f83e175b5f4273315e56f77888bf6abc7c69dde1a0e2463c57bc8b1fc0448f7e77c8a3f7d795

[#4] https://labvuln.com/verify/?tk=ImFoaWhpX2Rvbmdvay1wYWRkaW5ncGFkZGluZ2dnZ0BnbWFpbC5jb21ffF8zNTAxIg:1gyfqJ:Msq_VwZRABDu9rliwFxReuQrhWE

Lưu ý: Các tham số đã được thay đổi giá trị, nhưng vẫn giữa đúng format (mục đích private cho labvuln)

Phân tích:

Entry point #1:
Là chức năng reset passwd, tham số (ở đây là token) có độ dài là 64 ký tự hexa (32 byte),
sau một hồi reg nhiều account (email) với độ dài ngắn khắc nhau, thấy token có lengh không đổi, tạm kết luận
token ở đây là dạng hashing (SHA256).
Tức là với request [1], server sẽ lấy giá trị tk query trong database, kiểm tra tính hợp lệ và thực hiện các tác vụ khác
,thử inject SQLi nhưng “No hope” –> “chạy ngay đi” mà tìm entry point khác.

Entry point #3:
Ta có thể thấy đây là chức năng reset passwd tài khoản của người dùng, token gồm 2 phần split bởi “:”, phần đầu gồm 16 bytes, phần sau có độ dài là bội của 16
tk={16byte}:{16byte * x}
Khi reg nhiều email với lenght khác nhau (lúc đó reg email ngắn nhất, trung bình, và dài nhất của gmail) thì có thể thấy độ dài phần sau của token thay đổi.
Có thể tạm kết luận: Token ở đây là dạng mã hóa khối, khả năng cao là AES, 16 bytes đầu là IV (Initialization vector), 16*x bytes sau là content sau khi đã mã hóa.
AES có khá nhiều mode , trong có có AES-CBC mode bị ảnh hưởng bởi tấn công Padding Oracle

Một dấu hiệu nhận biết Padding Oracle là chúng ta thay đổi 255 giá trị còn lại của byte cuối, gửi request lên server và quan sát các http response. Cần có các yếu tố như exception, timing, http status codes (30x, 500), hoặc điểm khác biệt giữa các response để biết được trường hợp nào padding chính xác, trường hợp nào padding sai.

Sau một hồi thay đổi IV và block cuối, chả thấy hiện tượng đặc biệt gì xảy ra -> Hồng Kong 2 :)))
Mãi đến gần thời điểm viết bài viết này, trong một buổi đi cafe thân mật với đồng nghiệp, hắn mới nói rằng, từng khai thác thành công PO ở entry point đó rồi :)))
Rút ra kinh nghiệm là đồng nghiệp nên iu thương và hiểu nhau 😀

Entry point #4:

Là chức năng verify tài khoản hoặc change password ở một subdomain khác của hệ thống.
Với cái pattern đặc biệt của token như vậy, có thể nhận diện đây là dạng Django signing.
Lỗ hổng nếu có xẩy ra ở entry point này sẽ là dạng Account Takover(ATO), khi attacker generate được token hợp lệ ứng với email của victim.

Để generate được token hợp lệ thì ngoài SALT còn phải có SECRET_KEY (trong environment variable DJANGO_SETTINGS_MODULE or settings.configure()) điều này chỉ xảy ra khi RCE được server 🙂
Tuy nhiên trong nhiều trường hợp sau khi RCE server, sysadmin fix lỗi, nhưng không thay đổi các thông số trên sẽ dẫn đến attacker/bug-hunter attack/submit thêm một lần nữa, và ở đây cũng như vậy.
(Khá là giống với nhiều case CVE-2017-9248, sau khi RCE được sẽ leak được ValidationKey, MachineKey. Nếu sysadmin không thay đổi các credentials đó thì vẫn có thể bị RCE lần nữa thông qua Deserialize viewstate )

Entry point #2 – cũng là nội dung trọng tâm của bài viết này.

https://labvuln.com/payment?tk=911a9921cb5d9a18403efcd784aaed7a258d91d8ecffc08fa519b1f51d9951b13a5461996c5717e4dbe5882891

Token (sau khi decode_hex) có độ dài không phải bộ của 16, chỗ này có thể đoán rằng backend cũng dùng mã hóa AES (chưa rõ mode nào)
Thời điểm thực hiện pentest thì entry point #2 giải quyết khá đơn giản, vì lão bạn già của mình với kỹ năng Reconnaissance bá vãi đã tìm được phần source-code liên quan tới phương thức mã hóa, giải mã được dev push lên Github 🙂
(backend của server dùng python + nodejs, db thì có cả Postgres SQL, Mongodb và nhiều microservices khác)
Nội dung như sau:

let tk = '911a9921cb5d9a18403efcd784aaed7a258d91d8ecffc08fa519b1f51d9951b13a5461996c5717e4dbe5882891'
console.log(tk.decryptAES())
-> Output: {"order_id":131337,"expired_date":1551915060}

Vậy ở đây mở ra cho ta hướng khai thác IDOR, SQLi
Ex: {“order_id”:131337′ Postgres SQLi lead to RCE }

Một thời gian rất dài sau (hệ thống đã fix lỗi bằng cách xóa repo trên Git, thay đổi AES_PASSWORD, AES_IV nhưng không thay đổi AES-256-CTR mode), vào thời gian 02/09 rảnh rỗi mình có check lại các email thì tình cờ nhận được cái entry point sau:
https://labvuln.com/feedback/?token=a0ebaa1ebcd83d7de6420f2ccd19082813296b697ff059aa7feda8a53cf1cf829cdc2b92
do thời điểm trước mình đã thực hiện rất nhiều nghiệp vụ trên hệ thống, ví dụ như thanh toán nhiều dịch vụ. Entry point trên là link để khách hàng đánh giá trải nghiệm dịch vụ của họ. Token của feedback này cùng dạng với token trong module thanh toán.

Attack AES Counter(CTR) mode

Có thể tưởng tượng đơn giản CTR mode chẳng qua là dạng Stream Cipher, trong trường hợp này là stream key luôn là cố định.

token=a0ebaa1ebcd83d7de6420f2ccd19082813296b697ff059aa7feda8a53cf1cf829cdc2b92 (36 bytes)

Graybox: Hoàn cảnh ở đây là ta đã có lợi thế là biết nhiều thông tin từ hệ thống, ví dụ như biết cấu trúc của bản rõ là dạng json có dạng:
{“order_id”:integer,”expired_date”:time_stamp}
hoặc
{“id”:integer,”time”:1551915060} … same same

Đã biết rằng id hệ thống không phải là random, hash, mà là số nguyên (range nhỏ) và tăng dần, nghĩ đến việc Bit-flipping để thay đổi 1 or nhiều byte của id -> nếu hệ thống bị IDOR (Insecure Direct Object Reference) -> mình sẽ truy cập vào feedback của một order khác.

Thử nghiệm Bit-flipping 1 byte bằng cách brute-force theo index của id (max chỉ có 10*36 = 360 trường hợp) và thử với các token mới tạo ra ta thu được:

  • Thông báo lỗi từ hệ thống.
  • Không còn thông báo lỗi, và hệ thống báo đã quá thời gian đánh giá
    ( quá thời gian sẽ nghĩ đến việc tiếp tục Bit-flipping time_stamp nếu hệ thống tham chiếu trực tiếp đến time_stamp trên token)

Blackbox: Trong trường hợp mà nhận được entry point này mà blackbox hoàn toàn thì cũng là một bài toán khó, tuy nhiên tư duy tiếp cận vẫn mình vẫn sẽ thử trường hợp case CBC, CTR kể trên.

Sau khi tiếp cận bằng phương pháp khá là dummy kể trên, thì mình tiếp tục fuzzing để tìm cách làm nhanh và hiệu quả hơn, bằng cách thực hiện thêm nhiều chức năng thanh toán trên hệ thống thu được thêm nhiều token khác nữa. Nhìn hình miêu tả sau mọi người có thể hiểu được cách làm:


(case trở về bài CTF mảng Crypto 50đ về ManyTimePad – do key được dùng lại nhiều lần và đoán được nhiều thông tin của plaintext)

Như vậy có thể PoC được luận điểm: cách sử dụng CTR mode của hệ thống là không an toàn, ngoài ra ta có thể control (giải mã và mã hóa thành công) được max là 48 bytes (độ dài của token dài nhất) và sẽ là đầu vào cho các hướng khai thác IDOR, SQLi về sau tại các entry point sử dụng token như trên.

Tổng kết lại:

  1. Ở góc độ developer: Crypto là một lĩnh vực khó, do đó ta nên sử dụng các phương thức, thư viện mã hóa một cách an toàn, không nên làm những việc dạng “thiết kế lại cái bánh xe
  2. Ở phía pentester: đa phần là blackbox với server, nên không còn cách nào khác ngoài “tay to phát triển”, thử tất cả các trường hợp có thể xảy ra, cố gắng coding nhiều để giảm thời gian manual, và quan trọng không bỏ sót một test case trong checklist. (Persistent + Advance -> lead to Success! )

Cảm ơn các ae xã hội @PL, @qd và đồng đội trong team đã giúp đỡ kỹ thuật trong quá trình khai thác và góp ý để hoàn thành bài viết này.
Rất mong nhận được sự góp ý về chuyên môn của các bạn đọc 😀
P/S: Bài viết tiếp theo trong cùng chủ đề sẽ trình bày một số case study trong đó kết hợp sử dụng kỹ thuật
side-channel attack (timing, error, exception)

226 lượt xem