java nuprocess库的空字节分割特性

前言:java System.arraycopy空字节分割特性

参考文章:https://github.com/golang/go/issues/56284
参考文章:https://github.com/brettwooldridge/NuProcess/pull/143

nuprocess是什么

NuProcess是Brett Wooldridge个人开发者的一种低开销、非阻塞I/O、Java的外部进程实现。

NuProcess 1.2.0及其之后,2.0.5之前版本存在命令注入漏洞,该漏洞源于用户输入构造执行命令过程中,网络系统或产品未能正确过滤其中的特殊字符、命令等。攻击者可利用漏洞在其字符串中使用NUL字符来执行命令行注入。

nuprocess的空字节分割特性

在nuprocess小于2.0.5版本之前都存在这种特性,这边来简单的记录下

使用这个库一般出现的情况都是在创建进程之前进行解析命令行的时候所使用

maven包参考如下

<!-- https://mvnrepository.com/artifact/com.zaxxer/nuprocess -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>nuprocess</artifactId>
    <version>2.0.4</version>
</dependency

com.zaxxer.nuprocess.linux.LinuxProcess#prepareProcess 需要用到三个参数,分别是要执行的命令数组以及环境变量和要执行的路径目录

其中参数args的获取是如下操作

    public static void prepareProcess(List<String> command, String[] environment, Path cwd) throws IOException {
        String[] cmdarray = command.toArray(new String[0]);

        // See https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/java/lang/ProcessImpl.java#L71-L83
        byte[][] args = new byte[cmdarray.length - 1][];
        int size = args.length; // For added NUL bytes
        for (int i = 0; i < args.length; i++) {
            args[i] = cmdarray[i + 1].getBytes();
            size += args[i].length;
        }
        byte[] argBlock = new byte[size];
        int i = 0;
        for (byte[] arg : args) {
            System.arraycopy(arg, 0, argBlock, i, arg.length);
            i += arg.length + 1;
            // No need to write NUL bytes explicitly
        }

        byte[] envBlock= TestMain.toEnvironmentBlock(environment);

        System.out.println(1);
    }

其中环境变量的获取是如下操作 key = value 的形式 作为字符串数组保存

    public static byte[] toEnvironmentBlock(String[] environment) {
        int count = environment.length; // This implicitly adds an extra null byte for each entry
        for (String entry : environment) {
            count += entry.getBytes().length;
        }

        byte[] block = new byte[count];

        int i = 0;
        for (String entry : environment) {
            byte[] bytes = entry.getBytes();
            System.arraycopy(bytes, 0, block, i, bytes.length);
            i += bytes.length + 1;
            // No need to write NUL byte explicitly
            //block[i++] = (byte) '\u0000';
        }

        return block;
    }

而在System.arraycopy方法在进行数组拷贝的时候,分隔符是以NULL字符作为分隔符拷贝,那么这里就可能出现一种情况,如果nuprocess和System.arraycopy拷贝数组中的其中参数数据能够进行控制,那么从而能够实现逃逸效果

测试代码如下所示,可以看到注入的payload为new String[]{"a=1", "payload=aaa\000payload2=bbb",最终解析的结果原本两个部分的,但是通过配合System.arraycopy的空字节截断特性是被分成了三个部分,如果其中的参数可控的话那么就存在风险,这里同样也是CVE-2022-43781 Atlassian Bitbucket 命令注入的原因

public class TestMain {

    public static byte[] toEnvironmentBlock(String[] environment) {
        int count = environment.length; // This implicitly adds an extra null byte for each entry
        for (String entry : environment) {
            count += entry.getBytes().length;
        }

        byte[] block = new byte[count];

        int i = 0;
        for (String entry : environment) {
            byte[] bytes = entry.getBytes();
            System.arraycopy(bytes, 0, block, i, bytes.length);
            i += bytes.length + 1;
            // No need to write NUL byte explicitly
            //block[i++] = (byte) '\u0000';
        }

        return block;
    }

    public static void prepareProcess(List<String> command, String[] environment, Path cwd) throws IOException {
        String[] cmdarray = command.toArray(new String[0]);

        // See https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/java/lang/ProcessImpl.java#L71-L83
        byte[][] args = new byte[cmdarray.length - 1][];
        int size = args.length; // For added NUL bytes
        for (int i = 0; i < args.length; i++) {
            args[i] = cmdarray[i + 1].getBytes();
            size += args[i].length;
        }
        byte[] argBlock = new byte[size];
        int i = 0;
        for (byte[] arg : args) {
            System.arraycopy(arg, 0, argBlock, i, arg.length);
            i += arg.length + 1;
            // No need to write NUL bytes explicitly
        }

        byte[] envBlock= TestMain.toEnvironmentBlock(environment);
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        TestMain.prepareProcess(Arrays.asList("python", "bot.py"), new String[]{"a=1", "payload=aaa\000payload2=bbb"},
                new Path() {...});
    }
}

上面测试的环境变量,如果command的数组中参数可以控制的话,同样是可能存在风险

Arrays.asList("find",".","-type", "injection")

这种情况可能不太恰当,这边就是单纯的举个例子。。。。

Arrays.asList("find",".","-type", "f\000-exec\000ls\000{}\000\\;")

关于修复代码

在2.0.5及之后统一在执行前对命令数组进行了过滤进行了过滤操作,不过这里只针对command数据进行过滤,并没有对environment进行过滤

posted @ 2023-01-13 02:01  zpchcbd  阅读(131)  评论(0)    收藏  举报