【转】从源码分析PreparedStatement是如何防止SQL注入的?
為什么在Java中PreparedStatement能夠有效防止SQL注入?這可能是每個(gè)Java程序員思考過(guò)的問(wèn)題。
?
首先我們來(lái)看下直觀的現(xiàn)象(注:需要提前打開(kāi)mysql的SQL文日志)
1. 不使用PreparedStatement的set方法設(shè)置參數(shù)(效果跟Statement相似,相當(dāng)于執(zhí)行靜態(tài)SQL)
String param = "'test' or 1=1"; String sql = "select file from file where name = " + param; // 拼接SQL參數(shù) PreparedStatement preparedStatement = connection.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery(); System.out.println(resultSet.next());輸出結(jié)果為true,DB中執(zhí)行的SQL為
-- 永真條件1=1成為了查詢(xún)條件的一部分,可以返回所有數(shù)據(jù),造成了SQL注入問(wèn)題 select file from file where name = 'test' or 1=1?
2.?使用PreparedStatement的set方法設(shè)置參數(shù)
String param = "'test' or 1=1"; String sql = "select file from file where name = ?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, param); ResultSet resultSet = preparedStatement.executeQuery(); System.out.println(resultSet.next());輸出結(jié)果為false,DB中執(zhí)行的SQL為
select file from file where name = '\'test\' or 1=1'我們可以看到輸出的SQL文是把整個(gè)參數(shù)用引號(hào)包起來(lái),并把參數(shù)中的引號(hào)作為轉(zhuǎn)義字符,從而避免了參數(shù)也作為條件的一部分
?
?
接下來(lái)我們分析下源碼(以mysql驅(qū)動(dòng)實(shí)現(xiàn)為例)
打開(kāi)java.sql.PreparedStatement通用接口,看到如下注釋,了解到PreparedStatement就是為了提高statement(包括SQL,存儲(chǔ)過(guò)程等)執(zhí)行的效率。
An object that represents a precompiled SQL statement. A SQL statement is precompiled and stored in a PreparedStatement object. This object can then be used to efficiently execute this statement multiple times.那么,什么是所謂的“precompiled SQL statement”呢?
回答這個(gè)問(wèn)題之前需要先了解下一個(gè)SQL文在DB中執(zhí)行的具體步驟:
而所謂的“precompiled SQL statement”,就是同樣的SQL文(包括不同參數(shù)的),1-4步驟只在第一次執(zhí)行,所以大大提高了執(zhí)行效率(特別是對(duì)于需要重復(fù)執(zhí)行同一SQL的)
?
言歸正傳,回到source中,我們重點(diǎn)關(guān)注一下setString方法(因?yàn)槠渌O(shè)置參數(shù)的方法諸如setInt,setDouble之類(lèi),編譯器會(huì)檢查參數(shù)類(lèi)型,已經(jīng)避免了SQL注入。)
查看mysql中實(shí)現(xiàn)PreparedStatement接口的類(lèi)com.mysql.jdbc.PreparedStatement中的setString方法(部分代碼)
public void setString(int parameterIndex, String x) throws SQLException {synchronized (checkClosed().getConnectionMutex()) {// if the passed string is null, then set this column to nullif (x == null) {setNull(parameterIndex, Types.CHAR);} else {checkClosed();int stringLength = x.length();if (this.connection.isNoBackslashEscapesSet()) {// Scan for any nasty chars// 判斷是否需要轉(zhuǎn)義處理(比如包含引號(hào),換行等字符)boolean needsHexEscape = isEscapeNeededForString(x, stringLength); // 如果不需要轉(zhuǎn)義,則在兩邊加上單引號(hào)if (!needsHexEscape) {byte[] parameterAsBytes = null;StringBuilder quotedString = new StringBuilder(x.length() + 2);quotedString.append('\'');quotedString.append(x);quotedString.append('\'');...} else {...}String parameterAsString = x;boolean needsQuoted = true;// 如果需要轉(zhuǎn)義,則做轉(zhuǎn)義處理if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {...從上面加紅色注釋的可以明白為什么參數(shù)會(huì)被單引號(hào)包裹,并且類(lèi)似單引號(hào)之類(lèi)的特殊字符會(huì)被轉(zhuǎn)義處理,就是因?yàn)檫@些代碼的控制避免了SQL注入。?
這里只對(duì)SQL注入相關(guān)的代碼進(jìn)行解讀,如果在setString前后輸出預(yù)處理語(yǔ)句(preparedStatement.toString()),會(huì)發(fā)現(xiàn)如下輸出
Before bind: com.mysql.jdbc.JDBC42PreparedStatement@b1a58a3: select file from file where name = ** NOT SPECIFIED ** After bind: com.mysql.jdbc.JDBC42PreparedStatement@b1a58a3: select file from file where name = '\'test\' or 1=1'編程中建議大家使用PrepareStatement + Bind-variable的方式避免SQL注入
大家有什么其它的看法,歡迎留下評(píng)論!
參考:https://stackoverflow.com/questions/30587736/what-is-pre-compiled-sql-statement
總結(jié)
以上是生活随笔為你收集整理的【转】从源码分析PreparedStatement是如何防止SQL注入的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 联想笔记本蓝屏怎么修复软件 联想笔记本蓝
- 下一篇: mysql的各种语句_MySql常用操作