使用阿里 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

posted @ 2024-06-01 21:41  国产大熊猫~  阅读(75)  评论(0编辑  收藏  举报