payload:
这次绕过的大体思路是通过java.lang.Class,将JdbcRowSetImpl类加载到map缓存,从而绕过autotype的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkautotype就会抛出异常并中止。
入口在parse方法,单步进去
一步步跟到DefaultJSONParser.java中有一段调用checkautotype,也就是检测的核心逻辑。跟进该方法
clazz = this.config.checkAutoType(typeName, (Class)null, lexer.getFeatures())
在开启的情况下,checkautotype方法类似黑名单,会进入下图逻辑,通过将类名hash后和denyHashCodes进行对比。目前有人fuzz出了部分黑名单中的类:https://github.com/LeadroyaL/fastjson-blacklist。
开启的情况下,当黑名单检测命中时,根据代码逻辑,会先通过loadClass方法加载该类并返回,因此就绕过了检测。
在autotype关闭的情况下,checkautotype方法类似白名单,主要检测类是否在白名单中,也就是是否被加载。通过getClassFromMapping尝试在缓存加载该类。如果不存在,下边还会通过deserializers来找,如果都没有,下边就会抛异常。
当发送第一次请求时,Class是通过deserializers.findClass加载的,然后Class将JdbcRowSetImpl类加载进map中,然后第二次请求时,就这里就成功找到了JdbcRowSetImpl类,从而绕过检测。
加载JdbcRowSetImpl后,就和之前的payload一样了,通过JdbcRowSetImpl中的调用链,通过jndi的lookup加载远程类。
调用栈如下图
JavaBeanDeserializer.deserialze -> FieldDeserializer.setValue -> 通过反射调用setAutoCommit方法给属性赋值 -> JNDI connect,connect里调用InitialContext的lookup方法,根据前面payload里设置的DataSourceName找到,然后请求我们的jndi server下载远程类并执行构造函数,从而造成rce。当然在8u191之上,需要结合tomcat el或者ldap来绕过。8u191之下可以通过ldap reference来绕过对rmi从远程的Codebase加载Reference工厂类的限制。
JNDI注入高版本绕过参考:https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
48中的修复措施是,在loadClass时,将缓存开关默认置为false,所以默认是不能通过Class加载进缓存的。同时将Class类加入到了黑名单中。
最后附上jndi server