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的預編譯功能。