1. 程式人生 > 程式設計 >sql注入原理及防範

sql注入原理及防範

前言

        sql注入是一種危險係數較高的攻擊方式,現在由於我們持久層框架越來越多,大部分框架會處理這個問題,因此導致我們對它的關注度越來越少了。最近部門在整理安全漏洞時,提到了一些關於sql注入的修改點,因此共同記錄學習一下。

正文

原理

        sql注入的原理是將sql程式碼偽裝到輸入引數中,傳遞到伺服器解析並執行的一種攻擊手法。也就是說,在一些對server端發起的請求引數中植入一些sql程式碼,server端在執行sql操作時,會拼接對應引數,同時也將一些sql注入攻擊的“sql”拼接起來,導致會執行一些預期之外的操作。

示例:

        比如我們使用的登入介面:在登入介面包括使用者名稱和密碼輸入框,以及提交按鈕,輸入使用者名稱和密碼,提交。

        登入時呼叫介面/user/login/ 加上引數username、password,首先連線資料庫,然後後臺對請求引數中攜帶的使用者名稱、密碼進行引數校驗,即sql的查詢過程。假設正確的使用者名稱和密碼為ls和123456,輸入正確的使用者名稱和密碼、提交,相當於呼叫了以下的SQL語句。

SELECT * FROM user WHERE username = 'ls' AND password = '123456'
複製程式碼

        sql中會將#及--以後的字串當做註釋處理,如果我們使用“' or 1=1 #” 作為使用者名稱引數,那麼服務端構建的sql語句就如下:

select * from users where
username='' or 1=1#' and password='123456' 複製程式碼

        而#會忽略後面的語句,因此上面的sql也等價於:

select * from users where username='' or 1=1
複製程式碼

        而1=1屬於常等型條件,因此這個sql便成為瞭如下,查詢出所有的登陸使用者。

select * from users
複製程式碼

        其實上面的sql注入只是在引數層面做了些手腳,如果是引入了一些功能性的sql那就更危險了,比如上面的登陸介面,如果使用者名稱使用這個“' or 1=1;delete * from users; #”,那麼在";"之後相當於是另外一條新的sql,這個sql是刪除全表,是非常危險的操作,因此sql注入這種還是需要特別注意的。

解決sql注入原理

sql預編譯

        在知道了sql注入的原理之後,我們同樣也瞭解到mysql有預編譯的功能,指的是在伺服器啟動時,mysql client把sql語句的模板(變數採用佔位符進行佔位)傳送給mysql伺服器,mysql伺服器對sql語句的模板進行編譯,編譯之後根據語句的優化分析對相應的索引進行優化,在最終繫結引數時把相應的引數傳送給mysql伺服器,直接進行執行,節省了sql查詢時間,以及mysql伺服器的資源,達到一次編譯、多次執行的目的,除此之外,還可以防止SQL注入。

        具體是怎樣防止SQL注入的呢?實際上當將繫結的引數傳到mysql伺服器,mysql伺服器對引數進行編譯,即填充到相應的佔位符的過程中,做了轉義操作。         我們常用的jdbc就有預編譯功能,不僅提升效能,而且防止sql注入。

        String sql = "select id,no from user where id=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1,id);
        ps.executeQuery();
複製程式碼

嚴格的引數校驗

        引數校驗就沒得說了,在一些不該有特殊字元的引數中提前進行特殊字元校驗即可。

框架的支援——mybatis

        java生態中很常用的持久層框架mybatis就能很好的完成對sql注入的預防,如下兩個mapper檔案,前者就可以預防,而後者不行。

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id,username,password,role
from user
where username = #{username,jdbcType=VARCHAR}
and password = #{password,jdbcType=VARCHAR}
</select>

<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id,role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>
複製程式碼
  1. #將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號。 如: where username=#{username},如果傳入的值是111,那麼解析成sql時的值為where username="111",如果傳入的值是id,則解析成的sql為where username="id". 
  2. 將傳入的資料直接顯示生成在sql中。
如: where username={username},如果傳入的值是111,那麼解析成sql時的值為where username=111;如果傳入的值是;drop table user;,則解析成的sql為:select id,role from user where username=;drop table user;

        因此如果不是真的要執行功能型的sql如刪除表、建立表等,還是需要用#來避免sql注入。mybatis底層還是使用jdbc的預編譯功能。

結語

        以上是對sql注入方式、原理及危害、問題解決做出的一些小小的總結,如果有問題希望大家能夠指出,多多交流。