D^3CTF2023 (新的getter+高版本JNDI不出网+Hessian异常toString)

附件👉Click Me

这道题模拟了真实世界当中动态配置中心的架构——注册端存储相关配置,而服务端定期同步相关配置。而在这道题中的配置是Java原生反序列化的黑名单

Registry Hessian Deser Vul

注册端存在Hessian反序列化漏洞,这里它用的不是原生的Hessian,而是蚂蚁金服魔改后的Sofa Hessian

SOFA-Hessian 基于原生 Hessian v4.0.51 进行改进,支持导入Hessian黑名单来防止常见的Hessian利用链。

import com.alipay.hessian.ClassNameResolver;
import com.alipay.hessian.NameBlackListFilter;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.example.registry.data.Blacklist;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class HessianSerializer {
    public HessianSerializer() {
    }

    public static byte[] serialize(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(bos);
        output.writeObject(obj);
        output.close();
        return bos.toByteArray();
    }

    public static Object deserialize(byte[] obj) throws Exception {
        ByteArrayInputStream is = new ByteArrayInputStream(obj);
        Hessian2Input input = new Hessian2Input(is);
        ClassNameResolver resolver = new ClassNameResolver();
        resolver.addFilter(new AntInternalNameBlackListFilter());
        input.getSerializerFactory().setClassNameResolver(resolver);
        return input.readObject();
    }

    static class AntInternalNameBlackListFilter extends NameBlackListFilter {
        public AntInternalNameBlackListFilter() {
            super(Blacklist.hessianBlackList);
        }
    }
}

ClassNameResolver可以添加过滤器,过滤器继承自NameBlackListFilter,黑名单为resources/security/hessian_blacklist.txt

(这两个类都是com.alipay.hessian下的)

题目存在fastjson2.0.24的依赖,刚好黑名单中没有过滤fastjson

考虑fastjson原生反序列化,现在的问题变成

  • 怎么触发fastjson的toString

  • 寻找Hessian黑名单之外的可利用的getter

Getter without accessing the network

ysomap中存在这么一个利用类javax.naming.spi.ContinuationContext#getTargetContext

NamingManager.getContext进去就是JNDI了

JNDI的8u191绕过提到了本地Class的利用

目前公开常用的利用方法是通过 Tomcatorg.apache.naming.factory.BeanFactory 工厂类去调用 javax.el.ELProcessor#eval 方法或 groovy.lang.GroovyShell#evaluate 方法

org.apache.naming.factory.BeanFactorygetObjectInstance() 中会通过反射的方式实例化Reference所指向的Bean Class,并且能调用一些指定的方法

如何理解?

Reference类是我们可控的,这个类指定了JNDI要加载的类名(resourceClass)和用于加载这个类的工厂类(factory),在这里欲加载的类就是ELProcessor,工厂类为BeanFactory。之所以用这个工程类,

跟进NamingManager#getContext,进到了NamingManager#getObjectInstance

首先判断refInfo是否为ReferenceReferenceable类型

接着根据ref获取工厂类,这里传进去的factoryNameorg.apache.naming.factory.BeanFactorygetObjectFactoryFromReference会去加载并实例化这个类

接着调用返回的factorygetObjectInstance

image-20230727195328229

首先判断当前ref是否为ResourceRef,接着获取beanClass,并用当前线程的类加载去加载,后面实例化这个类

image-20230727200409090

Introspector.getBeanInfo获取类的基本信息,包括类标识符、属性(貌似只有对应getter、setter的属性才会获取)、方法

从ref中获取addrTypeforceStringRefAddr,并创建了一个键为字符串,值为方法的HashMap

image-20230727201508750

接着获取了StringRefAddr的内容,以逗号为分隔符分开为每一项,若该项中含有等于号=,则左边作为值,右边作为getter方法名,获取到对应Method后作为值,放入forced这个HashMap

源代码的注释也写得很清楚了,每一项可能是name=method的形式,也可以只是一个属性名,这里会对属性名进行setter标准化。显然这里如果是前者,并没有检查method是否为形为getXxx的方法,比如这里为eval

image-20230727202211112

获取ref的所有RefAddr并遍历,跳过addrTypefactoryscopeauthforceStringsingletonRefAddr

对于其他RefAdrr,获取其内容,并放入一个Object数组,只有一个元素且为String

到这里应该可以猜到了这个值会作为上面获取的setter方法的参数,接着就是激动人心的方法调用了

image-20230727202752165

所以实际上这里可以调用任意对象的方法,方法需要满足参数只有一个且为String类型

不过由于无法修改对象的属性,挺难找到ElProcessor之外的利用类

Hessian Exception triggering toString

和Dubbo中的类似,参考见这

image-20230728005709554

Server Java Native Deser Vul

flag在服务端,我们只能通过注册中心打服务端

同样服务端设置了一大堆黑名单,会同步注册中心的黑名单

访问注册中心的/client/status,其会请求服务端的/status,调用update更新黑名单,若返回的是List则直接更新denyClasses,若为字符串,则先进行反序列化后再更新

image-20230728011146258
image-20230728011408073

DefaultSerializer#deserialize还会判断传入的denyClasses是否为空,若为空则读取原始的黑名单。

因此我们通过注入filter内存马来改写注册中心返回的黑名单为一些没用的数据,先清除服务端黑名单,然后再改写一次,让其返回恶意的序列化数据,让服务端对请求/blacklist/jdk/get返回的数据进行反序列化,打原生fastjson或jackson。

为了回显,同样需要改写服务端的返回包,注入filter来改写/status的返回数据为读取的flag

好吧,Filter内存马不好使,要打两次才行(或许是lastServicedResponse的原因?),得加载两次字节码,类名还得不同,麻烦。

网上找了其他师傅写的Spring内存马,太好用了!orz

生成无效黑名单👇

fastjson打Server👇

RegistryInterceptor👇

ServerInterceptor👇

访问三次/client/status即可得到flag

Summary

  • Registry存在Hessian反序列化漏洞,没有禁fastjson,通过构造畸形数据触发toString,最后调用ContinuationContext#getTargetContext触发JNDI加载本地Class,打EL表达式

  • Registry植入内存马,修改返回体的数据,让Server请求到无效的黑名单

  • 再次修改Registry返回包数据为恶意序列化数据,fastjson原生反序列化打Server

  • Server植入内存马,修改返回体为flag数据

优雅~优雅!

Reference

https://github.com/wh1t3p1g/ysomap

https://pupil857.github.io/2023/05/03/d3ctf-ezjava/

Last updated

Was this helpful?