改造mybatis抵御sql注入的研究和SDL的思考

这段时间看了百度的openrasp的源代码和相关资料  (地址:https://rasp.baidu.com)java

在实施的过程当中仍是遇到了不少的阻力,因此开始本身调研相关的技术方案程序员

我以为能在研发阶段就能使程序员适应安全的开发习惯是比较重要的事情,若是是测试环境上线openrasp会同时增长运维和研发的压力算法

而这些压力同时会反向针对到安全自己的身上,因此这里提供一个改造mybatis抵御sql注入的研究过程spring

望博君一笑sql

-----------------------------------------数据库

首先介绍一下本次的研究环境apache

spring boot 2.0.2环境  编辑器idea数组

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

springboot中的mybatis的替换(参照我以前的文章  编译mybatis 3.4.6 替换到springboot 2.0.2)安全

本次中的代码验证的调试环境能够参考个人这篇文章(我不会eclipse 哈哈哈   springboot 2.0调试)springboot

这样子咱们就能够本地编译一个mybatis的jar包  而后mvn install到本地仓库

这样springboot再次启动后使用的就是咱们本地仓库的mybatis的jar包也就是咱们二次修改过的 

咱们就能够在mybatis里面增长防护规则的算法甚至一些限制

看到这里咱们要整理一下思路  这样作到的目的是什么:  

1: 对sql进行规则检测,防止出现sql注入:

看到过很多公司对mybatis的sql编写中有着严格的和成熟规则,(例如 京东sec团队 Mybatis框架下SQL注入漏洞面面观)可是这种审计规则依赖人力审计和员工自觉, 没有强制性的规范话很难向外推广

我认为一个好的安全规范应该便于推广方便实施

2:辅助推动信息安全的SDL中的编码规范:

 通常来讲企业都会在内网建一个私有的maven仓库尤为是很多成熟规范的企业在研发部门都是内网隔离的场景,在这样的环境架构师上传的用于研发和测试专用的的mybatis的jar包就能够设置很是严格的规则,这时候对于mybatis的$符号作更为严格的使用限制,以保证研发环境和测试环境以及经过测试验收的编码都是极为安全和规范的编码

最终发布到生产环境时再使用原版的mybatis的jar包进行打包发布,既能够保证速度不受到影响也能保证严格编码带来的安全性和规范性

----接下来看看试验的各类结果,本文因为改造了mybatis的源码,因此阅读者须要对mybatis源码有必定了解------

因为中间调试时间很长很繁琐,这里展示一个结果,有兴趣的朋友能够手工跟踪一次http请求从接口收到请求到mybatis整个数据库获取数据的过程再到spring输出结果的流程,相信会对理解mybatis和spring有着更深的理解,比简单的读源代码可是云里雾里好得多

首先打开这个类   

org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java

通过跟踪来到这个类中的方法  getBoundSql 并增长一个函数checkTokenLength

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    //---------这是我本身加的
    String x = context.getSql();
    TextSqlNode y = (TextSqlNode) rootSqlNode;
    if ((checkTokenLength(x) - checkTokenLength(y.getText())) > 2) {
      System.out.println(" 用户提交最终造成的sql  :" + x);
      System.out.println(" 程序员在xml里面配置的原始sql  :" + y.getText());
    }
    //----------本身加的代码结束
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
/**从openrasp抄袭的代码  做用是把String的sql语句格式化 并计算SQL的格式长度
*          select * from user where name='dato' and 1=1 -- '
*格式化为  [select, *, from, user, where, name, =, 'dato', and, 1, =, 1]
*
*          select * from user where name='${name}'
*格式化为  [select, *, from, user, where, name, =, '${name}']
*函数返回值 为这个数组的length值
 * */
  private int checkTokenLength(String inputsql) {
    ArrayList<String> resultx = new ArrayList<String>();
    ANTLRInputStream input = new ANTLRInputStream(inputsql);
    SQLLexer lexer = new SQLLexer(input);
    for (Token token = lexer.nextToken(); token.getType() != -1; token = lexer.nextToken()) {
      resultx.add(token.getText());
    }
    System.out.println(" _query_  :" + Arrays.toString(resultx.toArray()));
    return resultx.toArray().length;
  }

这里提一下SQLLexer是百度openrasp中格式化sql的包 我直接拿来用了 因此项目中要引入如下依赖

<dependency>
   <groupId>com.baidu.openrasp</groupId>
   <artifactId>sqlparser</artifactId>
   <version>1.3</version>
</dependency>

改造完成后咱们来看看一个基本的例子 我构造了一个使用$符号致使的sql注入的状况(很明显能够sql 拼接构造sql注入攻击)

咱们来看看结果  我用postman提交name=dato' and 1=1 --%20 和 name=dato' and 1=2 --%20

下图中前四行是改造版mybatis的输出 红框是最终数据库执行的sql语句

sql错误没有返回

这里能够从新膜拜一下openrasp团队的天才的算法

当原始配置的sql语句和最终执行的sql语句的格式化长度大于2的时候就能够肯定这个sql访问是sql注入必须拦截

也就是对比这两个数组的length就好了

if ((checkTokenLength(x) - checkTokenLength(y.getText())) > 2) {
    System.out.println(" 用户提交最终造成的sql  :" + x);
    System.out.println(" 程序员在xml里面配置的原始sql  :" + y.getText());
}

通过尝试使用#方法着这里进行判断是没有意义的---由于#原本就不会致使sql注入  哈哈哈   来看这个例子

从这里咱们也看出 咱们修改的这个DynamicSqlSource的getBoundSql方法也只有在xml使用了$符号的时候才会调用

xml配置#符号并不经过getBoundSql这个方法 执行

那咱们在这个方法中能够进行两种操做

1:改造mybatis的/org/apache/ibatis/scripting/xmltags/TextSqlNode.class中的checkInjection方法 就能够了

图中能够看出dato and 1=1 -- (后面有一个空格)是咱们提交的name的值

咱们能够直接在这里限制输入的value值 过滤各类符号

//checkInjection(srtValue);
//-----一些过滤规则----- 危险英文符号所有替换为中文符号
//去除单引号并去掉两边空格 让程序员保持好习惯
srtValue = srtValue.replace('\'','‘').trim();
//替换为全角圆括号 阻止sql函数执行 主要抵御不用单引号和双引号的order by注入
srtValue = srtValue.replace('(','(');
srtValue = srtValue.replace(')',')');
//替换为全角花括号 阻止sql函数执行 主要抵御不常见的sql注入构造
srtValue = srtValue.replace('{','{');
srtValue = srtValue.replace('}','}');
//阻止union select 1,2,3...类型的注入
srtValue = srtValue.replace(',',',');
//阻止like "dato" 或者 regexp "dato" 注入
srtValue = srtValue.replace('"','“');
//srtValue 也能够限制性作一些强制的长度限制保证常见的payload攻击,这里推荐限制到不超过100
//100是通过经验性的sqlmap --dbs命令的长度 不能抵挡sleep耗尽攻击

不少人不知道符号 { 和 }也能sql注入

技术搞到这里讲一下安全工做的心得:

咱们关心的除了$符号构造的sql语句,还要考虑到程序员能力不足致使程序员只会用$符号构造sql知足业务需求的状况如何解决

这就是安全工程师要执行的真正的工做,也就是给程序员作好赋能!!要教他们怎么处理当前的业务场景并且是合规的!!

毕竟不少状况下研发工程师拼命的在堆业务,没有心思来学很细的安全问题,他们连业务都来不及作,怎么会有心思单步调试mybatis全流程??

而这种状况致使整个业务跑起来看起来很光鲜,可是一旦出了问题就是牵一发而动全身的大修大补,可怜的安全工程师每每会专职为运维

这其实就是SDL的威力,在项目开发的阶段对项目进行干预,在安全问题出现前就能完全避免问题的出现

我以为信息安全工程师要作好风险识别以外,还要作好风险的前置预防性工做,在技术和管理上要拿的出真正的解决方法

这才是一个信息安全工程师的价值

谢谢 观赏!

相关文章
相关标签/搜索