滲透攻防Web篇-深入淺出SQL隱碼攻擊

京東雲 發表於 2022-08-26
SQL

1 背景

京東SRC(Security Response Center)收錄大量外部白帽子提交的sql注入漏洞,漏洞發生的原因多為sql語句拼接和Mybatis使用不當導致。

滲透攻防Web篇-深入淺出SQL隱碼攻擊

2 手工檢測

2.1 前置知識

mysql5.0以上版本中存在一個重要的系統資料庫information_schema,透過此資料庫可訪問mysql中存在的資料庫名、表名、欄位名等後設資料。information_schema中有三個表成為了sql注入構造的關鍵。

1)infromation_schema.columns:
  • table_schema 資料庫名
  • table_name 表名
  • column_name 列名
2)information_schema.tables
  • table_schema 資料庫名
  • table_name 表名
3)information_schema.schemata
  • schema_name 資料庫名

SQL隱碼攻擊常用SQL函式

  • length(str) :返回字串str的長度
  • substr(str, pos, len) :將str從pos位置開始擷取len長度的字元進行返回。注意這裡的pos位置是從1開始的,不是陣列的0開始
  • mid(str,pos,len) :跟上面的一樣,擷取字串
  • ascii(str) :返回字串str的最左面字元的ASCII程式碼值
  • ord(str) :將字元或布林型別轉成ascll碼
  • if(a,b,c) :a為條件,a為true,返回b,否則返回c,如if(1>2,1,0),返回0

2.2 注入型別

2.2.1 引數型別分類
  • 整型注入
    例如?id=1,其中id為注入點,型別為int型別。

  • 字元型注入
    例如?id=”1”,其中id為注入點,型別為字元型,要考慮閉合後端sql語句中的引號。

2.2.2 注入方式分類
  • 盲注
    • 布林盲注:只能從應用返回中推斷語句執行後的布林值。
    • 時間盲注:應用沒有明確的回顯,只能使用特定的時間函式來判斷,例如sleep,benchmark等。
  • 報錯注入:應用會顯示全部或者部分的報錯資訊
  • 堆疊注入:有的應用可以加入 ; 後一次執行多條語句
  • 其他

2.3 手動檢測步驟(字元型注入為例)


 // sqli vuln code            Statement statement = con.createStatement();            String sql = "select * from users where username = '" + username + "'";            logger.info(sql);            ResultSet rs = statement.executeQuery(sql);// fix code 如果要使用原始jdbc,請採用預編譯執行            String sql = "select * from users where username = ?";            PreparedStatement st = con.prepareStatement(sql);

使用未預編譯原始jdbc作為demo,注意此demo中sql語句引數採用單引號閉合。

2.3.1 確定注入點

對於字元型別注入,通常先嚐試單引號,判斷單引號是否被拼接到SQL語句中。推薦使用瀏覽器擴充套件harkbar作為手工測試工具。https://chrome.google.com/webstore/detail/hackbar/ginpbkfigcoaokgflihfhhmglmbchinc

正常頁面應該顯示如下:

滲透攻防Web篇-深入淺出SQL隱碼攻擊

admin後加單引號導致無資訊回顯,原因是後端sql執行報錯,說明引號被拼接至SQL語句中

滲透攻防Web篇-深入淺出SQL隱碼攻擊

滲透攻防Web篇-深入淺出SQL隱碼攻擊

select * from users where username = 'admin'  #正常sqlselect * from users where username = 'admin'' #admin'被帶入sql執行導致報錯無法顯示資訊
2.3.2 判斷欄位數

mysql中使用order by 進行排序,不僅可以是欄位名也可以是欄位序號。所以可以用來判斷表中欄位數,order by 超過欄位個數的數字就會報錯。

滲透攻防Web篇-深入淺出SQL隱碼攻擊

判斷欄位數

當order by 超過4時會報錯,所以此表共四個欄位。

滲透攻防Web篇-深入淺出SQL隱碼攻擊

後端所執行的sql語句

select * from users where username = 'admin' order by 1-- '

此處我們將原本username的值admin替換為admin’ order by 1 —+,其中admin後的單引號用於閉合原本sql語句中的前引號,—+用於註釋sql語句中的後引號。—後的+號主要作用是提供一個空格,sql語句單行註釋後需有空格,+會被解碼為空格。

2.3.3 確定回顯位置

主要用於定位後端sql欄位在前端顯示的位置,採用聯合查詢的方式確定。注意聯合查詢前後欄位需一致,這也就是我們為什麼做第二步的原因。

透過下圖可知,後端查詢並回顯的欄位位置為2,3位。

滲透攻防Web篇-深入淺出SQL隱碼攻擊

聯合查詢後的欄位可以隨意,本次採用的是數字1到4直觀方便。

滲透攻防Web篇-深入淺出SQL隱碼攻擊

2.3.4 利用information_schema庫實現注入

group_concat()函式用於將查詢結果拼接為字串。

  • 檢視存在資料庫

滲透攻防Web篇-深入淺出SQL隱碼攻擊

  • 檢視當前資料庫中的表

滲透攻防Web篇-深入淺出SQL隱碼攻擊

  • 檢視指定表中欄位

滲透攻防Web篇-深入淺出SQL隱碼攻擊

  • 利用以上獲取資訊讀取users表中username和password

滲透攻防Web篇-深入淺出SQL隱碼攻擊

3 自動化檢測

3.1 sqlmap 使用

sqlmap相容python2和python3,可以自動化檢測各類注入和幾乎所有資料庫型別。

3.1.1 常用命令


-u  可能存在注入的url連結-r讀取http資料包--data 指定post資料--cookie 指定cookie--headers 指定http頭 如採用token認證的情況下--threads 指定執行緒數--dbms 指定後端的資料庫--os 指定後端的作業系統型別--current-user 當前使用者--users 所有使用者--is-dba 是否是dba--sql-shell 互動式的sqlshell-p指定可能存在注入點的引數--dbs 窮舉系統存在的資料庫-D指定資料庫--tables 窮舉存在的表-T指定表--column 窮舉欄位-C指定欄位--dump dump資料

直接檢測
其中—cookie用於指定cookie,—batch 自動化執行,—dbms指定資料庫型別

滲透攻防Web篇-深入淺出SQL隱碼攻擊

檢測結果

滲透攻防Web篇-深入淺出SQL隱碼攻擊

讀取系統中存在資料庫
—dbs讀取當前使用者下的資料庫

滲透攻防Web篇-深入淺出SQL隱碼攻擊

讀取指定庫下的表
-D java_sec_code —tables

滲透攻防Web篇-深入淺出SQL隱碼攻擊

dump users表資料
-D java_sec_code -T users —dump

滲透攻防Web篇-深入淺出SQL隱碼攻擊

4 進階

4.1 Mybatis注入
1)$錯誤使用導致注入
//採用#不會導致sql注入,mybatis會使用預編譯執行    @Select("select * from users where username = #{username}")    User findByUserName(@Param("username") String username);//採用$作為入參可導致sql注入    @Select("select * from users where username = '${username}'")    List<User> findByUserNameVuln01(@Param("username") String username);
2)模糊查詢拼接
//錯誤寫法  <select id="findByUserNameVuln02" parameterType="String" resultMap="User">        select * from users where username like '%${_parameter}%'    </select> //正確寫法 <select id="findByUserNameVuln02" parameterType="String" resultMap="User">        select * from users where username like concat(‘%’,#{_parameter}, ‘%’)      </select>
3)order by 注入

order by 後若使用#{}會導致報錯,因為#{}預設新增引號會導致找不到欄位從而報錯。

   //錯誤寫法 <select id="findByUserNameVuln03" parameterType="String" resultMap="User">        select * from users        <if test="order != null">            order by ${order} asc        </if>    </select>//正確寫法 id指欄位id 此表欄位共四個 所以id為1-4    <select id="OrderByUsername" resultMap="User">        select * from users order by id asc limit 1    </select>

5 文章及資料推薦

slqmap手冊:https://octobug.gitbooks.io/sqlmap-wiki-zhcn/content/Users-manual/Introduction.html
sql注入詳解:http://sqlwiki.radare.cn/#/


作者:羅宇(物流安全小分隊)