1. 程式人生 > >MyBatis原始碼閱讀--防注入實現

MyBatis原始碼閱讀--防注入實現

MyBatis原始碼閱讀–防注入實現

什麼是Sql注入

百度百科:Sql注入
簡單來講,一條查詢語句如下,userName為使用者傳入的String引數 :

public String getSql(String userName){
   return "select * from user where user_name =  " + userName;
}
//獲取到的sql就是
"select * from user where user_name =  (userName)";

如果輸入引數被使用者惡意偽造,使得 userName 傳入的字串為 " ‘xxxx’ or 1 =1",

//獲取到的sql就是
"select * from user where user_name = 'xxxx' or 1 =1";

那麼此時查詢條件永遠為真,資料就會被查詢出來。

jdbc 中的Statement介面

Statement是jdk sql包下的一個介面,其有三個實現類,Statement ,PreparedStatement,CallableStatement其中CallableStatement是PreparedStatement的子類;

  • Statement只能執行靜態sql 語句
  • PreparedStatement可以動態操作sq語句
  • CallableStatement在PreparedStatement的基礎上,增加了對儲存過程的支援。

例子

Statement訪問資料庫

 public void testSQLInjection() {
        String username = "a' OR PASSWORD = ";
        String password = " OR '1'='1";

        String sql = "SELECT * FROM users WHERE username = '" + username
                + "' AND " + "password = '" + password + "'";
        System.out.println
(sql); Connection connection = null; Statement statement = null; ResultSet resultSet = null; try { connection = JDBCTools.getConnection(); statement = connection.createStatement(); resultSet = statement.executeQuery(sql); } catch (Exception e) { e.printStackTrace(); } finally { JDBCTools.releaseDB(resultSet, statement, connection); } }

PreparedStatement訪問資料庫

public void testSQLInjection2() {
        String username = "a' OR PASSWORD = ";
        String password = " OR '1'='1";
        String sql = "SELECT * FROM users WHERE username = ? "
                + "AND password = ?";
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCTools.getConnection();
            
            preparedStatement = connection.prepareStatement(sql);
            //單獨設定sql語句佔位符中的兩個引數
            preparedStatement.setString(1, username);
            preparedStatement.setString(2, password);
            resultSet = preparedStatement.executeQuery();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(resultSet, preparedStatement, connection);
        }
    }

可以看到PreparedStatement的sql語句傳入引數都是“?”代替,引數可以動態改變,PreparedStatement會對sql語句進行預編譯處理。Statement的sql語句屬於字串拼接,Statement不會對sql語句進行預編譯處理,很容易出現上述的注入攻擊。

StatementType

org.apache.ibatis.mapping.StatementType定義了三個列舉量,分別對應上述的三種Statement。

public enum StatementType {
    STATEMENT,
    PREPARED,
    CALLABLE;

    private StatementType() {
    }
}

mapper.xml 防注入實現

這是mapper 介面中的方法

   SysUser selectByPrimaryKeyAndName(@Param("id") Long id,@Param("userName") String name);

執行查詢語句

 sysUser1 = sysUserMapper.selectByPrimaryKeyAndName(1030L,"liqia");

對應的mapper.xml查詢語句

通過設定statementType屬性來設定不同的實現類。

PREPARED防注入實現

使用PREPARED,最終使用PreparedStatement類來執行,防注入實現。

 <select id="selectByPrimaryKeyAndName"  resultMap="BaseResultMap"   statementType="PREPARED">
    select id, user_name, user_password, user_email, create_time, user_info, head_img
    from sys_user
    where id = #{id,jdbcType=BIGINT} and  user_name = #{userName,jdbcType=VARCHAR}
  </select>

日誌輸出

DEBUG [main] - ==>  Preparing: select id, user_name, user_password, user_email, 
create_time, user_info, head_img from sys_user where id = ? and user_name = ? 
DEBUG [main] - ==> Parameters: 1030(Long), liqia(String)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, 
create_time, user_info, head_img
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0, 
<<BLOB>>, <<BLOB>>
DEBUG [main] - <==      Total: 1

使用?類作為佔位符,最後再將兩個引數傳入。

STATEMENT存在注入風險

使用STATEMENT,最終使用Statement類來執行,存在注入攻擊的風險實現。

<select id="selectByPrimaryKeyAndName"  resultMap="BaseResultMap"  statementType="STATEMENT">
    select id, user_name, user_password, user_email, create_time, user_info, head_img
    from sys_user
    where id = ${id} and  user_name = '${userName}'
  </select>

日誌輸出

DEBUG [main] - ==>  Executing: select id, user_name, user_password, user_email, 
create_time, user_info, head_img from sys_user where id = 1030 and user_name = 'liqia' 
TRACE [main] - <==    Columns: id, user_name, user_password,
user_email, create_time, user_info, head_img
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 2018-03-14 14:53:11.0,
 <<BLOB>>, <<BLOB>>
DEBUG [main] - <==      Total: 1

沒有站位符,直接將Sql語句和引數拼接。

注意兩段日誌中的不同之處:

//PREPARED
 where id = ? and user_name = ?
 //STATEMENT
 where id = 1030 and user_name = 'liqia' 

注意:
statementType未指定的情況下,預設是“PREPARED”。
需要注意的是

  1. PREPARED時,佔位符使用"#"。STATEMENT時,佔位符使用"$",否則會出現異常。
  2. 由於傳入的userName是字串型別,STATEMENT時,${userName}外邊需要家引號。
  3. STATEMENT時,相當字串拼接,因此不能再使用jdbcType=BIGINT,jdbcType=VARCHAR等。

什麼情況下會出現注入風險

上面的兩個例子並不會出現注入攻擊風險。
因為上述例子中,’${userName}’ ,userName已經使用單引號包圍,不管你的輸入引數是什麼,都會被解析成一個查詢字串。
比如輸入是" xxxx or 1 =1"
sql語句是

select id, user_name, user_password, user_email, create_time,
 user_info, head_img from sys_user where id = 1030 and user_name = 'xxxx or 1 =1' 

當沒有單引號時,才會出現注入攻擊風險。

<select id="selectByPrimaryKeyAndName"  resultMap="BaseResultMap"  statementType="STATEMENT">
    select id, user_name, user_password, user_email, create_time, user_info, head_img
    from sys_user
    where id = ${id} and  user_name = ${userName}
  </select>

執行查詢語句,注意 xxx使用了單引號包圍。

 sysUser1 = sysUserMapper.selectByPrimaryKeyAndName(1030L," 'xxx' or 1=1");

輸出,注意此時的查詢語句:select id, user_name, user_password, user_email, create_time, user_info, head_img from sys_user where id = 1030 and user_name = ‘xxxx’ OR 1=1

DEBUG [main] - ==>  Executing: select id, user_name, user_password, user_email, 
create_time, user_info, head_img from sys_user where id = 1030 
and user_name = 'xxxx' OR 1=1 
TRACE [main] - <==    Columns: id, user_name, user_password, user_email,
 create_time, user_info, head_img
TRACE [main] - <==        Row: 1, admin, 123456, admin@mybatis.tk, 
2018-03-03 11:00:00.0, <<BLOB>>, <<BLOB>>
TRACE [main] - <==        Row: 1030, liqia, 789, Mali@qq.com, 
2018-03-14 14:53:11.0, <<BLOB>>, <<BLOB>>
TRACE [main] - <==        Row: 1054, null, null, null, 2018-09-30 17:16:26.0, 
<<BLOB>>, <<BLOB>>
Exception in thread "main" org.apache.ibatis.exceptions.TooManyResultsException: 
Expected one result (or null) to be returned by selectOne(), but found: 3
DEBUG [main] - <==      Total: 3

我們的資料庫中並沒有一條資料的user_name是輸入引數(" ‘xxx’ or 1=1"),但所有的資料都被查詢出來,這就是注入攻擊。

相關推薦

MyBatis原始碼閱讀--注入實現

MyBatis原始碼閱讀–防注入實現 什麼是Sql注入 百度百科:Sql注入 簡單來講,一條查詢語句如下,userName為使用者傳入的String引數 : public String getSql(String userName){ return "

【Spring原始碼閱讀】populateBean實現 依賴注入原始碼解析

在完成Bean例項化後,Spring容器會給這個Bean注入相關的依賴Bean,在原始碼中,這一步通過類AbstractAutowireCapableBeanFactory中的populateBean方法完成。 測試程式碼 下面開始進入原始碼分析之前,先基於以下例項進行: /

MyBatis原始碼閱讀--MyBatis配置檔案

MyBatis配置內容 MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定(settings)和屬性(properties)資訊。文件的頂層結構如下: configurati

MyBatis原始碼閱讀--生命週期

MyBatis原始碼閱讀–生命週期 ##前言 MyBatis執行SQL主要有以下三個步驟: 1.通過SqlSessionFactoryBuilder獲取SqlSessionFactory 2.通過SqlSessionFactory獲取SqlSession 3

JDK原始碼閱讀—ArrayList的實現

1 繼承結構圖 ArrayList繼承AbstractList,實現了List介面 2 建構函式 transient Object[] elementData; // 陣列儲存元素 private int size; // 記錄長度 size記錄

MyBatis原始碼閱讀——MyBatis對事務的處理過程分析

事務管理器 在 MyBatis 中有兩種型別的事務管理器(也就是 type=”[JDBC|MANAGED]”): <environments default="development"> <environment id="

mybatis原始碼閱讀記錄

文章目錄書名 書名 深入淺出MyBatis 技術原理與實戰 楊開振著 這裡講了基本api和部分mybaits的原始碼導讀非常推薦 #大體結構 SqlSessionFactory (defaultSqlSessionFactory)構建SqlSession con

String原始碼閱讀之contains實現原理

本文將對String部分原始碼進行閱讀分析的記錄。 contains 對String中的contains方法進行分析,瞭解其採用的是什麼演算法進行匹配。 //用於判斷源字串是否包含目標字元序列 CharSequence s public bo

MyBatis原始碼閱讀——Spring載入MyBatis過程解析

我們平時在專案中都是用Spring來管理的,那麼,Spring是如何管理MyBatis的呢?我們來一探究竟。 程式設計式載入MyBatis 要了解Spring是如何載入MyBatis的,我想還是先來回顧一下我們是如何用程式設計的方式去載入MyBatis框

SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現

首先熟悉三個概念: SqlSessionFactoryBean –為整合應用提供SqlSession物件資源 MapperFactoryBean –根據指定的Mapper介面生成Bean例項 MapperScannerConfigurer –根據指定

MyBatis原始碼閱讀——通過debug解析MyBatis執行流程

前言 最近在閱讀MyBatis框架的原始碼。發現它其實是一個非常值得閱讀的框架。它靈活得運用了常見的設計模式去設計。值得我們去學習。我還是比較喜歡以debug閱讀MyBatis的原始碼。下面,就一起來看看吧。 首先,我們先寫一個demo,以供除錯使用 pu

Mybatis原始碼閱讀之一

    Spring中使用Mybatis-spring。     Spring版本4.3

Mybatis原始碼閱讀之二

    由之前的系列一,我們知道SqlSession是new SqlSessionTemplate(sqlSe

Mybatis原始碼閱讀 之 玩轉Executor

承接上篇部落格, 本文探究MyBatis中的Executor, 如下圖: 是Executor體系圖 本片部落格的目的就是探究如上圖中從頂級介面Executor中拓展出來的各個子執行器的功能,以及進一步瞭解Mybatis的一級快取和二級快取 預覽: BaseExecutor :實現了Executor的全部

Mybatis是如何實現SQL注入

Mybatis這個框架在日常開發中用的很多,比如面試中經常有一個問題:$和#的區別,它們的區別是使用#可以防止SQL注入,今天就來看一下它是如何實現SQL注入的。 什麼是SQL注入 在討論怎麼實現之前,首先了解一下什麼是SQL注入,我們有一個簡單的查詢操作:根據id查詢一個使用者資訊。它的sql語句應該是這樣

登入注入最簡單的實現

原來是這樣寫的,當我登入時輸入:' or 1=1 -- 會導致登入成功!這樣讓我必須要做防注入。 /** * 獲取登入使用者 * @param userName * @param md5password * @return */

Redis之資料庫實現原始碼閱讀

lookupKey:查詢指定的鍵,如果存在返回對應的值 robj *lookupKey(redisDb *db, robj *key) { // 查詢鍵空間 dictEntry *de = dictFind(db->dict,key->ptr);

原始碼系列Spring,Mybatis,Springboot,Netty原始碼深度解析-Spring的整體架構與容器的基本實現-mybatis原始碼深度解析與最佳實踐

6套原始碼系列Spring,Mybatis,Springboot,Netty原始碼深度解析視訊課程   6套原始碼套餐課程介紹: 1、6套精品是掌櫃最近整理出的最新課程,都是當下最火的技術,最火的課程,也是全網課程的精品;   2、6套資源包含:全套完整

[文件和原始碼分享] 基於JAVA實現的塔遊戲

塔防遊戲主要代表一類通過在遊戲地圖上裝置炮塔,阻止敵人進攻的策略型遊戲。本遊戲是在地圖上的特定地點裝置多種能力不同的炮臺以抵禦多種怪獸的入侵。同時玩家每場戰鬥將擁有多種道具讓玩家防守更加輕鬆。遊戲原型是【保衛蘿蔔】和【皇城守衛】,總體設計風格和遊戲背景音樂音效向【皇城守衛】靠攏,而遊戲機制是參照了【保衛蘿蔔】

一個日誌檢視功能實現-seelog原始碼閱讀

最近被後臺日誌弄的很煩,看到有個專案簡簡單單,又能滿足需要,順便試下看看效果,做下記錄。只是記錄下一部分內容,就不全部讀了,關於原始碼可以去https://github.com/xmge/seelog。 結構設計 // websocket客戶端 type client struct