Hướng dẫn cài đặt và dựng web liferay 6.2
# Quay về quá khứ với lỗ hổng 1-day 😐 Dưới đây là bài blog phân tích lại lỗ hổng [Insecure Deserialization của Liferay](https://www.acunetix.com/vulnerabilities/web/liferay-tunnelservlet-deserialization-remote-code-execution/). ![](https://i.imgur.com/Fun6tl3.png) **Tổng quan:** - **Endpoint `/api/liferay`:** Chức năng `Tunnel Servlet` của Liferay Portal nhận `ObjectInputStream` từ POST data và thực hiện deserialize bằng `readObject()` ngay sau đó, điều này dẫn đến lỗi insecure deserialization tại đây. Mặc dù theo cấu hình mặc định, chỉ localhost mới truy cập được endpoint `/api/liferay`, tuy nhiên unauthenticated attacker dễ dàng bypass để RCE hoặc DoS bằng serialized payload. - **Endpoint `/api/spring`:** Chức năng `Remoting Servlet` của Liferay Portal (sử dụng Spring framework) dính lỗi insecure deserialization trong quá trình `Dispatcher Servlet` handle POST request. Mặc dù theo cấu hình mặc định, chỉ localhost mới truy cập được endpoint `/api/spring`, tuy nhiên unauthenticated attacker dễ dàng bypass để RCE hoặc DoS bằng serialized payload. **Các phiên bản ảnh hưởng:** - Liferay Portal CE 7.0 GA3, 7.0.1 GA2, 7.0.2 GA3 - Liferay Portal EE 6.0, 6.0 SP1, 6.0 SP2, 6.1 GA1, 6.1 GA2, 6.1 GA3, 6.2 **References:** - https://sourceforge.net/projects/lportal/files/Liferay%20Portal/6.1.1%20GA2/ - https://testbnull.medium.com/ - https://sec.vnpt.vn/2019/01/ahihihihihihihihihihihihi/ - https://sec.vnpt.vn/2019/05/liferay-story-part-3/ ### 1. Dựng môi trường test **Môi trường:** - JDK 1.8 - Liferay Portal CE 6.1.1 GA2 **Cách setup remote debug trong Intellj trên Windows:** Tạo remote debug configuration với port `6868`. ![](https://i.imgur.com/dwICb1Q.png) Thêm dòng sau vào file `liferay-portal-6.1.1-ce-ga2/tomcat-7.0.27/bin/Catalina.bat`, trong đó `address=6868` chính là listener port của remote debugger. ```shell set CATALINA_OPTS=%CATALINA_OPTS% -Xdebug -Xrunjdwp:transport=dt_socket,address=6868,server=y,suspend=n` ``` Sau đó chạy `liferay-portal-6.1.1-ce-ga2/tomcat-7.0.27/bin/startup.bat` để khởi động ứng dụng Liferay. Ấn `Alt+Shift+F9` hoặc click vào biểu tượng như hình dưới để bắt dầu debug: ![](https://i.imgur.com/NKIy57w.png) ### 2. Reproduce lỗi và debug #### 2.1 Endpoint `/api/liferay` **Reproduce:** - Liferay chặn remote POST request đến `/api/liferay`. ![](https://i.imgur.com/4K2DXfy.png) - Tuy nhiên, dễ dàng bypass bằng `/api/////liferay`. ![](https://i.imgur.com/hHQ2Mcs.png) - RCE bằng CommonsCollections3 gadgetchain payload. Sử dụng syntax sau để tạo payload dạng base64 nhằm RCE trigger `calc.exe`: ```bash D:/>java.exe -jar ysoserial.jar CommonsCollections3 "calc.exe" 2> nul | base64 | sed ":a;N;$!ba;s/\n//g" ``` Gửi request đến `/api/////liferay` kèm theo payload được base64_decode bằng Hackvector. ![](https://i.imgur.com/F3jRsFJ.png) Kết quả tiến trình `calc.exe` được trigger thành công. **Debug** Tại `tomcat-7.0.27/webapps/ROOT/WEB-INF/web.xml`, ta biết được các request đến `/api/liferay/*` sẽ được `Tunnel Servlet` xử lí. ![](https://i.imgur.com/kcMHkL8.png) Tại hàm `doPost()` của `Tunnel Servlet` xuất hiện 1 bug deserialization khá to khi nhận user input và thực hiện deserialize ngay sau đó mà không có cơ chế validate. ![](https://i.imgur.com/oiwHsoW.png) Tuy nhiên, các đường dẫn `/api/liferay/*` mặc định chỉ được truy cập bởi localhost. Cụ thể, khi truy cập `/api/liferay/*` thì `Tunnel Servlet Filter` được sử dụng. ![](https://i.imgur.com/V4xDHaD.png) Nó được định nghĩa tại class `com.liferay.portal.servlet.filters.secure.SecureFilter`. ![](https://i.imgur.com/BGI0nmc.png) Hàm `processFilter` là hàm được gọi để thực hiện các quy trình filter. Trong đó có chức năng kiểm tra remote address có nằm trong whitelist các allowed host được access hay không bằng hàm `isAccessAllowed()`. Nếu không thì sẽ bị trả `403 Access denied` như ở trên. Đặt breakpoint như hình và debug. ![](https://i.imgur.com/GK5zODU.png) Ở đây mình truy cập từ remote IP `192.168.114.58` và nó không nằm trong `_hostsAllowed` (chỉ chứa `SERVER_IP` và `127.0.0.1`) → Access denied. ![](https://i.imgur.com/YzxQGz6.png) Mặc dù vậy, có 1 cách bypass để không phải trải qua quá trình filter của `SecureFilter`. Cụ thể đó là truy cập bằng path `/api/////liferay` (hoặc với rất nhiều slashes `/` như `/api//////////liferay`). Khi truy cập `/api/liferay`, xem stack trace, ta thấy bước đầu request sẽ được filter thông qua hàm `doFilter()` của `InvokerFilter` class. Sau đó các `InvokerFilterChain` về `url-pattern: /api/liferay/*` được lấy để thực hiện filter request qua tất cả filter trên. Có thể thấy với `/api/liferay` thì có đi qua `SecureFilter`. ![](https://i.imgur.com/ryqj7cR.png) ![](https://i.imgur.com/sqZOAPv.png) > Tuy nhiên, đối với `/api/////liferay` thì không có. ![](https://i.imgur.com/LbY2hfx.png) Nguyên nhân chỉ nằm ở chỗ lấy `InvokerFilterChain` thôi. Nó được gọi qua hàm `getInvokerFilterChain()`. ![](https://i.imgur.com/mjb3tpB.png) Tại đó, ứng dụng thực hiện tạo 1 cache key cho uri `/api/////liferay`. Sau đó method sẽ lấy filter chain có trước thông qua `_filterChains` với key là cache key vừa tạo cho uri `/api/////liferay`. Tuy nhiên, vì cache key này chưa tồn tại trong `_filterChains` → `invokerFilterChain == null` → Chương trình nhảy xuống statement tiếp theo, thực hiện gọi hàm `createInvokerFilterChain()` để tạo filter chain mới cho uri trên. ![](https://i.imgur.com/2hgCtnC.png) Vào trong `createInvokerFilterChain()` xem nó làm gì. Có thể thấy nó sẽ chạy hết list các filter mapping đã được defined và xem uri được truyền vào match với filter nào thông qua hàm `isMatch()`. ![](https://i.imgur.com/KVYvKiD.png) Cụ thể hơn, hàm `isMatch()` này chạy hết `urlPattern` có trong filter mapping hiện tại và so sánh xem nó khớp với uri truyền vào hay không. Và tất nhiên khi chạy qua filter mapping với filter name `Tunnel Servlet Filter`, uri `/api/////liferay` sẽ không khớp với `/api/liferay/*`. → Uri `/api/////liferay` không đi qua `SecureFilter`. ![](https://i.imgur.com/ZnCPlGJ.png) Như vậy ta đã có thể bypass và truy cập `/api/liferay` từ remote IP. Lúc này chỉ việc POST 1 gadgetchain payload (xem phần **Reproduce**) đến `/api/////liferay` vì như đã nói, có 1 lỗ hổng insecure deserialization tại hàm `doPost()` của `Tunnel Servlet`. ![](https://i.imgur.com/oiwHsoW.png) #### 2.2 Endpoint `/api/spring` **Background** > 🐣 "RemotingServlet" là một servlet được sử dụng trong các ứng dụng web để cung cấp giao tiếp từ xa giữa các máy chủ. Nó hỗ trợ nhiều giao thức và công nghệ như HTTP, RMI, Hessian, Burlap, ... > 🐣 File "remoting-servlet.xml" chứa cấu hình cho Spring Remoting Servlet. Nó được sử dụng để cấu hình các bean để xử lý các yêu cầu từ xa thông qua các giao thức như HTTP, RMI, Hessian, Burlap hoặc các giao thức tùy chỉnh. **Reproduce** - Liferay chặn remote request đến `/api/spring`. ![](https://i.imgur.com/qIeS7s3.png) - Thử bypass bằng `/api/////spring` thì response trả về 404 → bypass thành công nhưng chưa tìm được đúng endpoint xử lí POST request. ![](https://i.imgur.com/M9VKufB.png) - Trong quá trình debug, ta biết được các request sẽ được xử lí nếu truy cập vào `/api/////spring/ |