+3

[CVE-2019-16891] Liferay RCE via JSON Deserialization

Description

1.Liferay

Liferay Portal là giải pháp Cổng điện tử được thiết kế phù hợp với các mô hình ứng dụng trong các cơ quan, tổ chức và doanh nghiệp có nhu cầu phát triển hệ thống thông tin trên môi trường web nhằm thực hiện các giao dịch trực tuyến và sử dụng Intranet/Internet như một công cụ thiết yếu trong các hoạt động, cung cấp thông tin, giao tiếp, quản lý và điều hành, trao đổi và cộng tác.

2.CVE-2019-16891

Liferay Portal từ phiên bản 7.1.0 trở về trước bị dính lỗi RCE thông qua lỗ hổng liên quan tới json deserialization. Đây là lỗ hổng có thể khai thác mà không cần có tài khoản.

Setup Debug

1.Server

set JPDA_ADDRESS=8000

set JPDA_TRANSPORT=dt_socket

set JRE_HOME=%ProgramFiles%\Java\jre1.8.0_202 (*thay đổi đường dẫn tương ứng)

catalina.bat jpda start

2.Client Ở đây mình dùng Intellij để remote debug.

Mở project -> Edit configuration -> Add new config -> Remote JVM Debug -> config port 8000

Analysis

Entrypoints

JSONFactoryImpl là class sẽ handle hầu hết các tác vụ liên quan tới json (convert,deserialize,...).

Có rất nhiều method ở đây nên mình sẽ rải breakpoint đồng thời fuzz các entrypoint cho tới khi request trigger được breakpoint.Sau một hồi thì thử đến phần login thì đã trigger được breakpoint.

Quan sát stacktrace thì tìm được Chain từ HttpServlet qua tới đoạn call deserialize JSON.

HttpServlet.service() -> PollerServlet.service() -> PollerServlet.getContent() -> PollerRequestHandlerUltil.getPollerHeader() -> PollerRequestHandlerImpl.getPollerHeader() -> PollerRequestHandlerImpl.parsePollerRequestParameters() -> JSONFactoryImpl.deserialize()

Tóm tắt

  • Endpoint login : /web/guest/home
  • Endpoint triggers bug : /poller/receive
  • Method thực hiện deserialize : JSONFactoryImpl.deserialize(String)
  • Chain này cần phải được Authenticate

JSON Deserialize

Method thực hiện deserialize ở đây là JSONFactoryImpl.deserialize(String) từ đây call tới org.jabsorb.JSONSerializer.fromJSON() rồi lại call tới JSONSerializer.unmarshall() để tiếp tục deserialize object

Có thể thấy method này thực hiện lấy một loại serializer(class) thích hợp cho đoạn json string và thực hiện deserialize(unmarshall) nó theo cái serializer đó.

Set breakpoint tại method getSerializer để tìm các class thực hiện việc unmarshall

Đây là list những class có thể sẽ unmarshall object JSON truyền vào, được xét từ trên xuống dưới:Locale,Liferay,Primitive,... Trong list này thì thấy org.jabsorb.serializer.impl.BeanSerializer là đáng nghi nhất. Method BeanSerializer.unmarshall() có invoke Setter Method của cái object được khởi tạo từ JSON String ra:

public Object unmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException {
...
instance = clazz.newInstance();
...
while(i.hasNext()) {
            String field = (String)i.next();
            Method setMethod = (Method)bd.writableProps.get(field);
            if (setMethod != null) {
                Object fieldVal;
                try {
                    Class[] param = setMethod.getParameterTypes();
                    fieldVal = this.ser.unmarshall(state, param[0], jso.get(field));
                } catch (UnmarshallException var13) {
                    throw new UnmarshallException("could not unmarshall field \"" + field + "\" of bean " + clazz.getName(), var13);
                } catch (JSONException var14) {
                    throw new UnmarshallException("could not unmarshall field \"" + field + "\" of bean " + clazz.getName(), var14);
                }

                if (log.isDebugEnabled()) {
                    log.debug("invoking " + setMethod.getName() + "(" + fieldVal + ")");
                }

                invokeArgs[0] = fieldVal;

                try {
                    setMethod.invoke(instance, invokeArgs);
                } catch (Throwable var19) {
                    Throwable e = var19;
                    if (var19 instanceof InvocationTargetException) {
                        e = ((InvocationTargetException)var19).getTargetException();
                    }

                    throw new UnmarshallException("bean " + clazz.getName() + "can't invoke " + setMethod.getName() + ": " + e.getMessage(), e);
                }
            }
...

Với field là param key, dòng này sẽ lấy setter method của field từ class bd (class thỏa mãn method canSerialize() của BeanSerializer). Nếu setMethod tồn tại thì sẽ lấy param types và value của field sau đó invoke invokeArgs[0] = fieldVal; => Để trigger được unmarshall() bằng BeanSerializer thì class để trigger setter method không được Serializable, nếu class này Serializable thì nó sẽ dừng ngay ở LiferaySerializer khi gọi method getSerialize(), method này không invoke setter method.

Tóm tắt

Source class có thể trigger được BeanSerializer này là 1 class có dạng:

  • Có public constructor không có arg
  • Không implement Serializable
  • Có setter method có thể lợi dụng được!

Exploit

Class thỏa mãn các điều kiện trên là com.mchange.v2.c3p0.mbean.C3P0PooledDataSource

Class này có emty constructor, không implement Serializeable,có setter method có thể lợi dụng để deserialize object qua rmi BeanSerializer.unmarshall -> C3P0PooledDataSource.setJndiName.invoke(instance, jndiName) -> C3P0PooledDataSource.rebind(jndiName, obj) => rmi

POC

Authenticated endpoint:

https://github.com/DuyVuong/CVE-Analysis/assets/125139802/3ff2521f-563c-4308-a7b5-7f3a274fb162

Unauthenticated endpoint (https://dappsec.substack.com/p/an-advisory-for-cve-2019-16891-from) :

POST /c/portal/portlet_url HTTP/1.1
Host: liferay.victim.example.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

parameterMap={"Json payload for deserialization in here!"}

https://github.com/DuyVuong/CVE-Analysis/assets/125139802/21490733-4e5c-455a-893f-1f7efb715b20

References


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí