Writeup web03 whitehat grand prix 2018: Java SSRF + Java Deserialization to SQL injection
Whitehat grand prix 2018 vừa rồi mình có tham gia ra đề về phần attack-defense. Chủ ý ban đầu của BTC là ra đề mà trong đó sẽ có các lỗ hổng bảo mật khác nhau để đa dạng cho quá trình att-def, tuy nhiên thực tế khi diễn ra thì mình thấy các đội tìm bug để attack ít hơn nhiều so với việc chỉ cần monitor, chờ một team siêng năng nhất attack, tìm được payload và replay 😐.
Do không đội nào tìm ra được bug/ hướng exploit chính trong challenge web03 nên mình viết writeup này, hi vọng sẽ có các kiến thức thú vị vì thường Java cũng không được sử dụng nhiều trong việc coding các web challenge. Bài này ý tưởng ban đầu là Java deserialization to Sqli nhưng khi BKAV yêu cầu thêm lỗ hổng thì mình đã cho thêm bug về java SSRF.
0x01 Java SSRF
Mô tả nhanh qua về lỗi này. Ở trang login, view source người chơi sẽ có được một số thông tin
Tại chức năng đổi avatar, người chơi có thể đổi avatar từ một image url bất kỳ
fuzzing tại chức năng này với giá trị bất kỳ ta có thể thấy thông tin lỗi được ghi log, bao gồm cả thông tin đường dẫn thư mục web trên server. Và giá trị url không cho phép bắt đầu bằng các protocol thường dùng trong SSRF attack như file, gopher,…
Ở SSRF bug này có 2 payload để exploit lấy flag, đó là:
jar:file:/{Webroot_path}/backup/backup.zip!/secret.jsp
classpath:../../backup/backup.zip
Trong đó việc sử dụng classpath là cách unintended mà có lẽ đội P4 đến từ Poland đã tìm ra đầu tiên (và các đội sau monitor có được 😊 ). Class java.net.URL cũng không có mô tả về handler này là măc định được hỗ trợ và thực tế mình cũng đã test lại trên local thì lỗi “unknown protocol: classpath”. Cũng theo mô tả, để có thể sử dụng được thêm các customized handler khác như classpath thì coder cần khai báo một handler với class name <package>.<protocol>.Handler
Trong đó <protocol> là ‘classpath’. Trong source code của mình không tạo thêm handler này tuy nhiên trong quá trình thi vẫn sử dụng được, có thể do môi trường, phiên bản JDK hoặc một lý do gì đó mình sẽ check lại. Về cơ bản thì sử dụng jar: hay classpath: đều hợp lệ, là các protocol handler không được biết đến nhiều như các protocol khác hay được sử dụng trong SSRF attack như file://, gopher://,…
(ví dụ về việc implement classpath: https://stackoverflow.com/questions/861500/url-to-load-resources-from-the-classpath-in-java)
0x02 Java Deserialization
Lỗi này thì mình có để ý 1 cá nhân trong P4 team sau khi đã pass qua được phần SSRF thì ngâm cứu tiếp nhưng có vẻ không ra. Khó khăn của phần này là đọc hiểu java source code và hiểu về java deserialization, các code flow của gadgets. Phần source code không nhiều và đủ thấy khó có hướng exploit nào khác ngoài phần deserialization.
Sau khi có secret.jsp, người chơi sẽ có được link để download war file của ứng dụng. Mở các classes trong JD-gui và bắt đầu đọc code. Một số note:
- Ứng dụng có CSRF Filter để check token, bằng cách deserialization giá trị tham số csrf_token gửi từ client. Hướng exploit lỗi deserialization trực tiếp qua code flow của CSRF Filter là không có:
(Nếu các team sử dụng blackbox và thử các exploit lỗi java deserialization từ công cụ ysoserial thì sẽ không có kết quả gì vì mình không hề sử dụng các lib hay phiên bản Java mắc lỗi).
- Method getList của class Token có cộng chuỗi trong sql query với tham số logger.getEvent() (logger, user_id là các variables trong Token class, có các setter, getter):
- Method getList() này được invoke tại Class TokenManager:
- Method values() được invoke tại logManager.jsp, mục đích để lấy list các token để hiển thị lên jsp
Code flow chỗ này nhìn có vẻ hợp lý, nhưng nếu ai tinh ý có thể tự hỏi tại sao việc lấy list các token trong DB lại phải khai báo class TokenManager implement serializable, rồi khai báo các biến bao gồm map, token, logger trong class này, rồi trong method values() lại gọi this.token.getList().
Để ý thêm phần các lib, các lib này bị đổi tên. Bình thường thì khi coding, nhiều java coder sử dụng Maven thay vì phải download các lib về trước. Còn nếu download sẵn các lib về thì cũng chẳng mấy ai đi đôi tên các lib này.
Mở trong JD-Guide, search một số từ khóa liên quan đến các gadget chain đã public như hashcode(), getKey(), getValue(),…(cái này thì bạn phải đọc/hiểu về flow các gadget chain từ AnnotationInvocationHandler.invoke(), java.util.HashMap.hash(),… để biết được các method nào cần focus vào). Đồng thời class đó phải implement serializable interface.
Các code trong các lib này ko nhiều nên việc tìm kiếm cũng sẽ không mất quá nhiều thời gian để có được 1 class HashCodeMaker trong org.apache.commons.lang3.builder chính là class ta còn thiếu để có được một Gadget hoàn chỉnh:
Gadget chain:
Nếu như ai đã biết về java và hiểu về java deserialization thì mình tin là việc tìm được Gadget chain này là không khó khăn.
0x03 SQL Injection
Như phân tích trên, chúng ta sẽ exploit lỗi SQLi nhờ Java deserialization. Việc khai thác Sqli thực hiện qua câu lệnh insert. Và chú ý rằng insert log này cần có user_id, do vậy khi build payload cần set đúng user_id của mình, nếu không thì khi exploit, kết quả sẽ hiển thị trên giao diện logmanager của một user khác 😊. (Đây chính là lý do nếu như 1 team nào đó exploit bug này trong quá trình thi thì sẽ có lợi thế lớn do các team khác có monitor — replay các payload cũng sẽ không thể nhận kết quả gì)
Tuy nhiên thực tế là trong source code được deploy trên server, mình đã đặt thêm filter để chặn một số keyword khai thác sqli đối với giá trị của logger.getEvent():
Khi thực hiện SQLi có các keywork match với filter trên, log sẽ có thông báo:
Đến đây thì ta đã biết phải “select flag from 9st0rm_s3cr3t”nhưng cần bypass filter. Ta không thể sử dụng các ký tự comment, không thể dùng ascii, if, case,…
Bỏ qua bước thinking và testing rồi thinking và testing với mysql query, payload mà mình sử dụng để gán cho logger.getEvent() là:
1' ^ (SELECT CONV(HEX(SUBSTRING(flag,3,1)),16,10) FROM 9st0rm_s3cr3t) ^ ‘1
Và user_id chính là id account hiện tại. Phần này check code thì đơn giản sẽ thấy mã số Event mỗi thông tin log được hiển thị trong logManager sẽ có format là userId-loggerId:
Query lúc này sẽ thành:
Insert into 9st0rm_logger (id, user_id, event) values (default,1, ‘1’ ^ (SELECT CONV(HEX(SUBSTRING(flag,1,1)),16,10) FROM 9st0rm_s3cr3t) ^ ‘1’)
Đây là query hoàn toàn hợp lệ. Sử dụng conv(hex()) để thay cho ascii và ‘1’ ^ number = number. Kết quả ta sẽ lấy được mã ascii của ký tự đầu tiên của flag.
Thực hiện build customized payload để test sqli timebase sử dụng công cụ ysoserial:
https://github.com/buxu/ctf/blob/master/whitehat2018_grand_prix_web03_payload
Thực hiện build lại với logger.setEvent(“1' ^ (SELECT CONV(HEX(SUBSTRING(flag,1,1)),16,10) FROM 9st0rm_s3cr3t) ^ ‘1“);
Sau khi send request với payload này, phần logmanager sẽ hiển thị mã ascii của ký tự đầu tiên của flag:
Đến đây thì đã ez rồi, substring lần lượt các vị trí để lấy toàn bộ flag.
Thanks for reading!