Deserialize to memshell in Tomcat

Sau khi hiểu cơ bản cách triển khai memshell thông qua JSP, ta tiếp tục tìm hiểu đến kỹ thuật leo memshell thông qua lỗ hổng deserialize.

1. Preface

Hiện tại theo như mình tìm hiểu thì ta chỉ có thể leo lên memshell thông qua deser của một số chain dùng sink TemplatesImpl như CommonsBeanutils1, CC2, CC3, CC4 vì TemplatesImpl cho phép ta load được byte code của class bất kỳ vào quá trình Runtime -> Dễ triển khai memshell hơn, do đó trong phần này mình chỉ note về deser2memshell với sink TemplatesImpl. Do đó để hiểu rõ hơn mình khuyên các bạn nên nắm rõ cách sink TemplatesImpl hoạt động với một số chain như CommonsBeanutils1 hoặc CC3 (Tham khảo chi tiết bài này)

Môi trường lab trong bài này sẽ là jdk8u66 với commons-collections 3.2.1, ta sẽ demo leo memshell với chain CC3

2. Setup labs

Ta sẽ có một trang web servlet đơn giản để demo như sau

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;


@WebServlet("/")
public class DeserLab extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        byte[] data = Base64.getDecoder().decode(req.getParameter("data"));
        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        try {
            System.out.println(objectInputStream.readObject());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
}

Mình dùng chain CC3 như sau để exploit

3. Phân tích

A. Vấn đề

Nhìn lại các payload load memshell bằng JSP ta sẽ nhận ra để setup malicious Filter/Servlet/Listener ta đều cần dùng đến object requestcó sẵn trong file jsp. Object này là instance của HttpServletRequest. Đối với file JSP thì ta có thể gọi đến object này dễ dàng. Tuy nhiên khi deser lại là một chuyện hoàn toàn khác.

Vấn đề lớn nhất khi muốn leo từ deser là ta không thể gọi trực tiếp đến HttpServletRequest . Do mỗi HttpServletRequest sẽ đại diện cho mỗi request đến từ client nên ta không thể khai báo trực tiếp khi deser được mà phải tìm cách dump từ Runtime.

Để giải quyết các vấn đề trên ta có nhiều phương pháp khác nhau. Tuy nhiên ở bài này mình sẽ tập trung vào phương pháp của @kingkk (bài gốc)

Ngoài ra cũng còn nhiều phương pháp khác mà các bạn có thể tự tham khảo để triển khai. Ví dụ như:

B. Phân thích và khai thác

Note: Để set debug cũng như gen payload exploit cho đơn giản thì ta có thể embed tomcat core vào project (version phải cùng với version tomcat đang sử dụng - mình đang dùng tomcat 9.0.91 nên sẽ embed version 9.0.91 luôn)

Ý tưởng của phương pháp này là sẽ tìm cách get được HttpServletRequest thông qua một property nào đó trong quá trình Runtime gọi đến các components của Tomcat.

Inject ThreadLocal

Như đã biết từ bài trước thì trong quá trình Tomcat handle các Filter thì sẽ gọi đến ApplicationContext.internalDoFilter để thực thi Filter

Sau khi thực hiện doFilter xong thì hàm này còn tiếp tục set giá trị request và response vào lastServicedRequestlastServicedResponse nếu như ApplicationDispatcher.WRAP_SAME_OBJECT là true

Vậy thì property này là gì? Xem vào phần define property ở đầu class ta có được:

2 property này sẽ là 2 ThreadLocal (khái niệm ThreadLocal) hold request và response khi gọi đến Filter. Tuy nhiên giá trị ApplicationDispatcher.WRAP_SAME_OBJECT mặc định sẽ là false, do đó lastServicedRequestlastServicedResponse sẽ được set là null.

Trong quá trình Runtime request và response sẽ được gán vào 2 property này nếu như ApplicationDispatcher.WRAP_SAME_OBJECT là true. Lợi dụng hành vi này ta sẽ setup ApplicationDispatcher.WRAP_SAME_OBJECT thành true để Tomcat tự động set request và resposne vào lastServicedRequestlastServicedResponse. Sau đó ta gán một filter độc hại bằng cách gọi đến lastServicedRequest trong quá trình deserialize

Vì đây là 2 property static final nên ta sẽ dùng Reflection theo cách này để setup giá trị

Khi thay đổi giá trị thành công ta có thể gọi đến ServletContext như sau

Từ ServletContext ta có thể gọi đến StandardContext (tham khảo bài trước). Và từ StandardContext có thể tự do setup memshell theo mong muốn.

Full POC dùng với chain CC3

Gen payload với CC3

Kết quả:

Trước khi inject:

Tiến hành inject:

Sau khi inject:

Inject Filter

Tiếp theo ta tiến hành inject malicious Filter. Ta sẽ dùng chain ServletContext để gọi đến StandardContext như sau (chain đã nói ở bài trước)

Khi có ServletContext thì ta cũng inject malicious Filter như cách đã làm với file JSP. Tuy nhiên có một lưu ý nhỏ là ta sẽ gộp chung malicous Filter và class setup Filter luôn. Tức là class exploit này tự setup chính nó vòa Runtime như một malicous Filter.

Nguyên nhân của việc trên là nếu class setup một malicous Filter khác thì ta phải upload file class của malicous Filter lên server nếu không thì khi deser sẽ quăng ra lỗi ClassNotFound (các bạn debug sẽ dễ hiểu đoạn này hơn). Do đó ta sẽ gộp chung class inject và malicious filter vào chung 1 class.

Full POC kết hợp exploit với CC3

Kết quả:

Trước khi inject:

Danh sách Filters chỉ có 1 Filter mặc định

Tiến hành inject:

Sau khi inject:

Danh sách filter đã có thêm ShellFilter do ta inject

Lúc này chỉ cần truy cập bất ký path nào với param cmd để RCE

C. Tóm tắt cách khai thác

  • Gửi payload deser để inject ThreadLocal

  • Gửi payload deser để inject malicous Filter

  • RCE

Refer

Last updated