关于java:java安全编码指南之输入校验

51次阅读

共计 3060 个字符,预计需要花费 8 分钟才能阅读完成。

简介

为了保障 java 程序的平安,任何内部用户的输出咱们都认为是可能有歹意攻打用意,咱们须要对所有的用户输出都进行肯定水平的校验。

本文将率领大家探讨一下用户输出校验的一些场景。一起来看看吧。

在字符串标准化之后进行校验

通常咱们在进行字符串校验的时候须要对一些特殊字符进行过滤,过滤之后再进行字符串的校验。

咱们晓得在 java 中字符是基于 Unicode 进行编码的。然而在 Unicode 中,同一个字符可能有不同的示意模式。所以咱们须要对字符进行标准化。

java 中有一个专门的类 Normalizer 来负责解决,字符标准化的问题。

咱们看上面一个例子:

    public void testNormalizer(){System.out.println(Normalizer.normalize("\u00C1", Normalizer.Form.NFKC));
        System.out.println(Normalizer.normalize("\u0041\u0301", Normalizer.Form.NFKC));
    }

输入后果:

Á
Á

咱们能够看到,尽管两者的 Unicode 不一样,然而最终示意的字符是一样的。所以咱们在进行字符验证的时候,肯定要先进行 normalize 解决。

思考上面的例子:

    public void falseNormalize(){
        String s = "\uFE64" + "script" + "\uFE65";
        Pattern pattern = Pattern.compile("[<>]"); // 查看是否有尖括号
        Matcher matcher = pattern.matcher(s);
        if (matcher.find()) {throw new IllegalStateException();
        }
        s = Normalizer.normalize(s, Normalizer.Form.NFKC);
    }

其中 uFE64 示意的是 <, 而 uFE65 示意的是 >, 程序的本意是判断输出的字符串是否蕴含了尖括号,然而因为间接传入的是 unicode 字符,所以间接 compile 是检测不到的。

咱们须要对代码进行上面的改变:

    public void trueNormalize(){
        String s = "\uFE64" + "script" + "\uFE65";
        s = Normalizer.normalize(s, Normalizer.Form.NFKC);
        Pattern pattern = Pattern.compile("[<>]"); // 查看是否有尖括号
        Matcher matcher = pattern.matcher(s);
        if (matcher.find()) {throw new IllegalStateException();
        }
    }

先进行 normalize 操作,而后再进行字符验证。

留神不可信字符串的格式化

咱们常常会应用到格式化来对字符串进行格式化,在格式化的时候如果格式化字符串外面带有用户输出信息,那么咱们就要留神了。

看上面的例子:

    public void wrongFormat(){Calendar c = new GregorianCalendar(2020, GregorianCalendar.JULY, 27);
        String input="%1$tm";
        System.out.format(input + "工夫不匹配,应该是某个月的第 %1$terd 天", c);
    }

粗看一下没什么问题,然而咱们的 input 中蕴含了格式化信息,最初输入后果:

 07 工夫不匹配,应该是某个月的第 27rd 天 

变相的,咱们获取到了零碎外部的信息,在某些状况上面,可能会裸露零碎的外部逻辑。

下面的例子咱们应该将 input 也作为一个参数,如下所示:

    public void rightFormat(){Calendar c = new GregorianCalendar(2020, GregorianCalendar.JULY, 27);
        String input="%1$tm";
        System.out.format("%s 工夫不匹配,应该是某个月的第 %terd 天",input, c);
    }

输入后果:

 %1$tm 工夫不匹配,应该是某个月的第 27rd 天 

小心应用 Runtime.exec()

咱们晓得 Runtime.exec() 应用来调用系统命令的,如果有歹意的用户调用了“rm -rf /”, 所有的所有都完蛋了。

所以,咱们在调用 Runtime.exec() 的时候,肯定要小心留神检测用户的输出。

看上面的一个例子:

    public void wrongExec() throws IOException {String dir = System.getProperty("dir");
        Runtime rt = Runtime.getRuntime();
        Process proc = rt.exec(new String[] {"sh", "-c", "ls" + dir});
    }

下面的例子中,咱们从零碎属性中读取 dir,而后执行了零碎的 ls 命令来查看 dir 中的内容。

如果有歹意用户给 dir 赋值成:

/usr & rm -rf /

那么零碎实际上执行的命令就是:

sh -c 'ls /usr & rm -rf /'

从而导致歹意的删除。

解决下面的问题也有几个办法,第一个办法就是对输出做个校验,比方咱们只运行 dir 蕴含特定的字符:

    public void correctExec1() throws IOException {String dir = System.getProperty("dir");
        if (!Pattern.matches("[0-9A-Za-z@.]+", dir)) {// Handle error}
        Runtime rt = Runtime.getRuntime();
        Process proc = rt.exec(new String[] {"sh", "-c", "ls" + dir});
    }

第二种办法就是应用 switch 语句,限定特定的输出:

    public void correctExec2(){String dir = System.getProperty("dir");
        switch (dir){
            case "/usr":
                System.out.println("/usr");
                break;
            case "/local":
                System.out.println("/local");
                break;
            default:
                break;
        }
    }

还有一种就是不应用 Runtime.exec() 办法,而是应用 java 自带的办法。

正则表达式的匹配

在正则表达式的构建过程中,如果应用用户自定义输出,同样的也须要进行输出校验。

思考上面的正则表达式:

(.*? +public\[\d+\] +.*<SEARCHTEXT>.*)

下面的表达式本意是想在 public[1234] 这样的日志信息中,搜寻用户的输出。

然而用户实际上能够输出上面的信息:

.*)|(.*

最终导致正则表达式变成上面的样子:

(.*? +public\[\d+\] +.*.*)|(.*.*)

从而导致匹配所有的日志信息。

解决办法也有两个,一个是应用白名单,判断用户的输出。一个是应用 Pattern.quote() 来对歹意字符进行本义。

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-input/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0