使用阿里 Druid 实现应用级waf
Druid内置了语义级的waf,为何我们要把waf能力集成到应用中呢?
优点:
1)流量waf还是存在各种解析差异导致的bypass,类似于该 例子
2)k8s的逐渐流行,在应用中实现waf能力能减少架构层设计负担
缺点:
1)需要应用安全工程师不间断配合
2)对代码质量较好的开发团队会显得多此一举
本次使用springboot2、mysql5.7.3、Druid1.2.23
先看看输入接口
@RequestMapping("list2")
public User getList2(String name) {
if (name == null) {
name = "dato";
}
String x = CheckSqlInjection(name);
if (x != null) {
User u = new User();
u.setName(x);
return u;
}
//@Select("select * from user where name='${name}'")
return userMapper.getUserByName3(name);
}
其中 CheckSqlInjection 内容就是调用Druid进行语义分析的逻辑
通过sqlmap不断的测试已经无法判断出注入了
下面是不断实验形成的具体代码
//设置路径穿越正则 和 危险sql正则 进行无视大小写的正则匹配
static Pattern pattern = Pattern.compile("\\.\\./|\\.\\.\\\\|select.*?from", Pattern.CASE_INSENSITIVE);
public static String CheckSqlInjection(String sql) {
if (sql == null || sql.length() < 3) {
return null;
}
String re;
String regexdirty = null;
//首先开展基础的sql正则表达式检查
//正则表达式检查
Matcher m = pattern.matcher(sql);
while (m.find()) {
//匹配到了危险特征就标记
regexdirty = m.group();
}
if (regexdirty != null) {
//匹配到了危险sql特征直接告警,不再进行语义分析
//System.out.println("regex check:" + regexdirty);
return "regex check:" + regexdirty;
}
//正则表达式检查结束
//正则匹配结束,启动druid检查
MySqlWallProvider newSqlWallProvider = getMySqlWallProvider();
//入参类型包含无符号、单引号、双引号,其实还要考虑只有左侧符号无右侧符号或只有右侧符合无左侧符号
List<String> quotes = Arrays.asList("", "'", "\"");
for (String quote : quotes) {
StringBuilder checksql = new StringBuilder();
WallCheckResult check_quote;
//sql的in语句也可以形成sql注入,需要小心
checksql.append("Select id from x where id in (").append(quote).append(sql).append(quote).append(")");
check_quote = newSqlWallProvider.check(checksql.toString());
re = Violation_Message(check_quote);
if (re != null) {
//System.out.println("AST check:" + re);
return re;
}
checksql = new StringBuilder();
//检查常见的id=1这类拼接注入和like注入
checksql.append("select id from x where id RLIKE ").append(quote).append(sql).append(quote);
check_quote = newSqlWallProvider.check(checksql.toString());
re = Violation_Message(check_quote);
if (re != null) {
//System.out.println("AST check:" + re);
return re;
}
}
return null;
}
private static MySqlWallProvider getMySqlWallProvider() {
WallConfig config = new WallConfig("/");
//防止类似OR 'jwOw'='gQxQ 的永假式
config.setConditionAndAlwayFalseAllow(false);
//防止类似AND 7467=7467 的永真式
config.setConditionAndAlwayTrueAllow(false);
//初始化配置
return new MySqlWallProvider(config);
}
private static String Violation_Message(WallCheckResult check) {
List<Violation> violations = check.getViolations();
if (!violations.isEmpty()) {
int firstViolation = violations.get(0).getErrorCode();
//1001是sql语法不正确、未知错误为9999,它俩应忽略掉,因为正常的用户输入都不可能是sql注入
//错误码在druid/wall/violation/ErrorCode.java之中
if (firstViolation != 1001 && firstViolation != 9999) {
//异常sql的druid的提示
//System.out.println(test_lexer(check.getSql()));
return violations.get(0).getMessage();
}
}
return null;
}
同时也应把注入语句涉及到root用户注入时的敏感库和函数都放到黑名单中
总结:
1)Druid这个库很成熟了,除了RLIKE有点bug,已经可以完全在输入时就完成语义sql注入校验
2)整体代码没有什么难度,可以很快就复现传达给同事
3)代码还有性能优化空间,请留言赐教
4)XSS攻击防护准备使用成熟的antisamy https://github.com/nahsra/antisamy