简介
为了保障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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!
发表回复