使用阿里 Druid 实现应用级waf

k4n5ha0 / 2024-06-02 / 原文

Druid内置了语义级的waf,为何我们要把waf能力集成到应用中呢?

1)流量waf还是存在各种解析差异导致的bypass,类似于该 例子

2)k8s的逐渐流行,在应用中实现waf能力能减少架构层设计负担

缺点:

1)需要应用安全工程师不间断配合

2)对代码质量较好的开发团队会显得多此一举

 

本次使用springboot2、mysql5.7、Druid12.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进行语义分析的逻辑

public static String CheckSqlInjection(String sql) {
       if (sql == null || sql.length() < 6) {
           return null;
       }
       //首先开展基础的sql正则表达式检查
       //正则表达式检查
       //.......正则检查开始..........
       //正则表达式检查结束

       //启动druid检查
       WallConfig config = new WallConfig("/");
       MySqlWallProvider newSqlWallProvider = new MySqlWallProvider(config);
       //入参类型包含无符号、单引号、双引号
       //也隐藏包含了 SELECT `id`,`uid` from x WHERE `id` LIKE '%2%' 这种like注入
       List < String > quotes = Arrays.asList("", "'", "\"");
       String checksql;
       String re;
       for (String quote: quotes) {
           checksql = "select id from x where id = " + quote + sql + quote;
           WallCheckResult check_quote = newSqlWallProvider.check(checksql);
           re = Violation_Message(checksql, check_quote);
           if (re != null) {
               return re;
           }
       }

       //order by注入的情况下进行校验,order by情况下无单引号或双引号
       //似乎和上面的无符号情况一致,可以省略?
       /*
       checksql = "select id,uid from x order by " + sql;
       WallCheckResult check_order_by = newSqlWallProvider.check(checksql);
       re = Violation_Message(checksql, check_order_by);
       if (re != null) {
           return re;
       }
       */

       //sql的in语句也可以形成sql注入,也需要小心
       checksql = "Select id,uid,pid from x where id in (" + sql + ")";
       WallCheckResult check_in = newSqlWallProvider.check(checksql);
       re = Violation_Message(checksql, check_in);
       if (re != null) {
           return re;
       }
       return null;
   }

   private static String Violation_Message(String Sql, 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) {
               //System.out.println("sqlAtt&ck: " + violations.get(0).getMessage() + " : " + Sql + "\n---------");
               return violations.get(0).getMessage();
           }
       }
       return null;
   }

通过sqlmap不断的测试发现Druid存在bug,无法解析RLIKE注入已经提交给官方,issues地址

所以也将sqlmap中涉及到root用户注入的敏感库和函数都放到了黑名单之中

 

总结:

1)这个库很成熟了,除了RLIKE这个bug,已经可以完全在输入时就完成语义sql注入校验

2)整体没有什么难度,可以很快就复现传达给同事