改造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的威力,在項目開發的階段對項目進行干預,在安全問題出現前就能完全避免問題的出現

我以爲信息安全工程師要作好風險識別以外,還要作好風險的前置預防性工做,在技術和管理上要拿的出真正的解決方法

這纔是一個信息安全工程師的價值

謝謝 觀賞!

相關文章
相關標籤/搜索