天天看點

一文給你講明白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預編譯

MySQL的預編譯

在伺服器啟動時,mysql client把sql語句模闆(變量采用占位符)發給mysql伺服器,mysql伺服器對sql語句模闆進行編譯,編譯之後根據語句的優化分析對相應的索引進行優化,在最終綁定參數時把相應的參數傳送給mysql伺服器,直接執行,節省了sql查詢時間,以及mysql伺服器的資源,達到一次編譯、多次執行的目的,除此之外,還可以防止SQL注入。

何時真正防止SQL注入

當将綁定的參數傳到mysql伺服器,mysql伺服器對參數進行編譯,即填充到相應的占位符的過程中,做了轉義操作。

Java 的 jdbc就有預編譯功能,不僅提升性能,而且防止sql注入。

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

嚴格的參數校驗

在一些不該有特殊字元的參數中提前進行特殊字元校驗即可。

架構支援-mybatis

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, username, password, role
from user
where username = ${username,jdbcType=VARCHAR}
and password = ${password,jdbcType=VARCHAR}
</select>      

#将傳入的資料都當成一個字元串,會對自動傳入的資料加一個雙引号。

如: 

where username=#{username}

,如果傳入的值是111,那麼解析成sql時的值為

where username="111"

, 如果傳入的值是id,則解析成的sql為

where username="id"      

如果傳入的值是111,那麼解析成sql時的值為where username=111;如果傳入的值是;drop table user;,則解析成的sql為:select id, username, password, role from user where username=;drop table user;

總結

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