[ASCIS 2020 Final Writeup] mojarra_war — JSF ViewState deserialize

tuo4n8
tradahacking
Published in
7 min readDec 5, 2020

--

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:

Index page

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àm saveTeamDetailsInDB chỉ đi qua hàm SecurityUtils.SQL_filter mà không thực hiện prepare.
ascis.db.operations.saveTeamDetailsInDB()
  • Ở class TeamBean có chứa method toString có thể dùng để trigger deserialize (1 điểm đáng nghi)
ascis.TeamBean
  • 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()
ascis.SecurityUtils.SQL_filter()
  • 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:
ascis.TeamBean.saveTeamDetails() -> sử dụng hàm saveTeamDetailsInDB
mojarra_war\admin\createTeam.xhtml -> chức năng createTeam

=> Đế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

web.xml
ascis.AdminFilter
  • Trick bypass filter chỉ việc sử dụng Host Header Injection
Host: peterjson.pw:13337
createTeam.xhtml

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 💲🤑💲

Dump flag

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:

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ó)
lib
  • 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)
web.xml
  • 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 SHA1DES 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 AES để tăng độ an toàn hơn.

=> Tóm gọn việc cần làm để khai thác bug này:

  1. Tìm key đang lưu trữ trên server.
  2. Build gadget để RCE thông qua class TeamBean , reflection thay đổi biến template và tiến hành trigger toString.
  3. Encrypt lại gadget theo chuẩn encrypt của mojarra.
  4. 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.

CVE-2018–14371
Đọc key qua CVE-2018–14371

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 để trigger toString và sử dụng reflection để thay đổi các biến temaplate 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.

Encode without encrypt

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.

encode with security layer
  • Quá trình nhận viewState sẽ được xử lí ởcom.sun.faces.renderkit.ServerSideStateHelper#getState()
doGetState()
  • Quá trình writeState sẽ được xử lí ở com.sun.faces.renderkit.ServerSideStateHelper#writeState()
doWriteState()
  • Tiến hành trace tiếp đến hàm encrypt Object: com.sun.faces.renderkit.ByteArrayGuard
default encryption
AES256+HMAC256
  • 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.
ByteArrayGuard Encrypt gadget TeamBean
Backconnect

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

--

--