[ASCIS 2020 Final Writeup] mojarra_war — JSF ViewState deserialize
Vào một buổi trưa nắng nóng khi dịch bệnh tràn lan, mình có nhận được tin nhắn thử “xoạc “ 1 bài CTF đã ra trong kì Final ASCIS 2020. Lúc đầu tải đề mình chỉ định xem cho vui, nhưng khi lướt sơ qua thấy đề khá thực tế và đó giờ cũng ít chall liên quan đến JAVA, nên quyết định dựng và làm thử. 🐱💻
1. Setup:
Link tải: https://drive.google.com/file/d/1gqSvfp0uyU9f4ogRCDL8W91yRtowTH2t/view
Cách cài đặt và setup đã có sẵn trong sourcecode (README.txt)
Mình sử dụng Bytecode-Viewer để decompile và xem code của file war, mọi người có thể tham khảo 1 số cách khác thuận tiện hơn với mình.
Link access: IP:31337/mojarra_war
2. Summary:
Challage web được xây dựng bằng Mojarra (một dạng implement của Java Server Faces — JSF). Ứng dụng bao gồm các chức năng:
- Sửa, xóa team: không thực hiện phân quyền
- Thêm team: chỉ dành cho người dùng admin.
(Luồng query xử lí chính ở package ascis.db.operations
)
Challage sẽ có 2 flag (đã đc hint khi bạn vừa vào chall ở trang index).
3. Analysis & audit source code
Đối với những bài có sourcecode sẵn như thế này, thì mình thường lướt sơ và hiểu toàn bộ cấu trúc của source trước khi làm. Sau khi xem toàn bộ source thì mình phát hiện một số điều đáng nghi.
- SQL Injection: Nhìn kĩ toàn bộ
ascis.db.operations
đều chống sqli bằng cách preapare, tuy nhiên ở hàmsaveTeamDetailsInDB
chỉ đi qua hàmSecurityUtils.SQL_filter
mà không thực hiện prepare.
- Ở class
TeamBean
có chứa methodtoString
có thể dùng để trigger deserialize (1 điểm đáng nghi)
- Dựa theo hỏi đáp với tác giả thì bài này sẽ có 2 bug: 1 dễ và 1 khó để lấy flag, từ đó mình tập trung vào 2 lỗi trên luôn cho đỡ tốn thời gian.
4. Exploit
1. SQL Injection Insert
- Ở đây chỉ sử dụng formatstring và đi qua hàm fillter đơn giản để chống SQLi
String insertTeam_query = String.format("insert into ascis_final_team (team_name, team_country, team_secret_status, team_secret_message) values ('VNSEC', 'VN', 0, '%s')", newTeamObj.getTeam_secret_message()
- Nhìn sơ chỗ này chỉ cần vào được chức năng chạy câu query này, và bypass hàm filter kia. (Ở đây chỉ không cho dùng một số kí tự như comment, union, ascii,…)
- Đầu tiên cần trace ngược xem chức năng nào sử dụng hàm SQL lỗi này:
=> Đến đây thì đọc web.xml xem cách vào chức năng này. Đây là chức năng /admin và bị filter bởi ascis.AdminFilter
- Trick bypass filter chỉ việc sử dụng Host Header Injection
Host: peterjson.pw:13337
Do số hiển thị ra chỉ có 20 team, nên để output được query ta phải xóa 2 team đi (hoặc xóa hết cho dễ nhìn)
Payload: x’),((select database()),’1',1,’1
Tới đây thì đơn giản (do trong setup.sql không có bảng flag nên mọi người có thể tự thêm để giải nốt nhé) => ez money 💲🤑💲
2. JSF ViewState Deserialize to RCE
Thực ra, ban đầu mình chỉ đoán bug 2 là deserialize mà chưa nghĩ sẽ qua viewstate. Nhưng khi xem source kĩ 1 lần nữa thì ko có dấu hiệu của việc bug khác và đường vào để trigger class TeamBean
(đề cập phía trên). Tất nhiên cũng không phải rãnh rỗi mà tác giả sử dụng JSF xây dựng chall này => Bug này sẽ liên quan đến JSF, mà attack surface phổ biến của JSF là deserialize qua viewstate.
Nói sơ về viewstate, thì đây là 1 feature của nhiều framework hiện nay. Nó giúp websever lưu giữ các trạng thái view của trang, theo tùy framework mà viewstate có các định dạng riêng của nó. Đây vừa là 1 tính năng cũng vừa là 1 điểm yếu để khai thác.
Một số bài viết bạn nên đọc trước khi exploit lỗi này:
- JSF ViewState upside-down => Phân tích kĩ về các cấu trúc viewstate
- Misconfigured JSF ViewStates can lead to severe RCE vulnerabilities
- HTB: Arkham =>Một bài CTF cũng về viewstate JSF
Phân tích các dữ kiện đang có:
- javax.faces-2.3.4.jar (lúc đầu mình k nhìn version nên k tìm hết được CVE đang có của nó)
- Viewstate ở đây thực hiện lưu ở Client ( Đối với Mojarra, lỗi này chỉ có thể xảy ra khi sử dụng lưu ở client)
- Vậy điều kiện cuối cùng để khai thác lỗi này là phải biết được key dùng encrypt/decrypt trên server.
- Việc nắm bắt phiên bản Mojarra đang chạy cũng khá quan trọng, vì mỗi phiên bản là một cách mã hóa khác nhau (đọc thêm tại đây). Các phiên bản cũ của Mojarra sẽ sử dụng SHA1 và DES cho việc mã hóa và kiểm tra tính toàn vẹn. Tuy nhiên, các phiên bản mới thì đã thay thếsang SHA256 và AES để tăng độ an toàn hơn.
=> Tóm gọn việc cần làm để khai thác bug này:
- Tìm key đang lưu trữ trên server.
- Build gadget để RCE thông qua class
TeamBean
, reflection thay đổi biếntemplate
và tiến hành triggertoString
. - Encrypt lại gadget theo chuẩn encrypt của mojarra.
- Thay thế payload vào
javax.faces.ViewState
trong request POST bất kì. 💥💥
Exploit :
1. Tìm key đang lưu trữ trên server 🔑
Để đọc được key trên server, mình tiến hành search các CVE đang có của Mojarra . Ban đầu do không chú ý phiên bản nên mình tìm đc 1 CVE cũ và được tác giả cho cái link để ngăm cứu.
do là môi trường dựng lại nên key sẽ trùng với web.xml trong file war nhé 🤣🤣
2. Build gadget: TeaamBeanGadget.java ⛓
- Chain ở đây đơn giản không quá đánh đố, chỉ cần sử dụng
BadAttributeValueExpException
để triggertoString
và sử dụng reflection để thay đổi các biếntemaplate
thành payload eval vàteam_secret_status
thành true
ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TeamBean.toString()
3. Encrypt lại payload theo chuẩn của mojarra 🔐
Đây là bước khó chịu nhất trong các bước :)) ban đầu mình chơi ngu thử viết lại bằng python nhưng chạy không được nên quyết định copy cả lib encryption của mojarra cho nhanh 🤷♂️🤷♂️
Cấu trúc của viewstate JSF:
Đối với các phiên bản cũ, viewstate sẽ không encrypt, đơn giản chỉ GZIP object rồi tiến hành base64.
Tuy nhiên, đối với các phiên bản mới, JSF đã implement thêm HMAC + mã hóa sau khi GZIP xong.
- Quá trình nhận viewState sẽ được xử lí ở
com.sun.faces.renderkit.ServerSideStateHelper#getState()
- Quá trình writeState sẽ được xử lí ở
com.sun.faces.renderkit.ServerSideStateHelper#writeState()
- Tiến hành trace tiếp đến hàm encrypt Object:
com.sun.faces.renderkit.ByteArrayGuard
- Vậy đúng như cấu trúc mô hình viewstate ở trên, cơ bản ở đây là copy lại code để nó chạy trơn tru thôi. Code dùng để encrypt cuối cùng:
4. Thay thế payload vào
javax.faces.ViewState
trong request POST bất kì
- Mình bỏ tất cả package liên quan vào ysoserial rồi chạy
[+]: TeaamBeanGadget
[+]: ByteArrayGuard
- Chạy hàm main của ByteArrayGuard để encrypt payload
- Thay thể Payload viewstate trong request gửi lên.
5. Tổng kết
- Một challenge java khá hay và thực tế trong đời sống công ăn việc làm. Chúc mừng cuộc thi thành công tốt đẹp ❤
- Thanks tác giả PeterJson đã gửi link cho mình trải nghiệm 1 bài chung kết SVATTT