老大反饋代碼里面存在sql注入错览,這個漏洞會導致系統遭受攻擊纫雁,定位到對應的代碼,如下圖所示
like 進行了一個字符串拼接倾哺,正常的情況下轧邪,前端傳一個 cxk 過來,那么執(zhí)行的sql就是
select * from test where name like '%cxk%';
好像沒有什么問題羞海,但是忌愚,如果被攻擊了傳了個 cxk%'; DELETE FROM test WHERE name like '%cxk
那么 這條sql 將會拼接成
select * from test where name like '%cxk%'; DELETE FROM test WHERE name like '%cxk%';
執(zhí)行不會報錯,結果是 name like cxk 的數據全部刪除扣猫,這個還是比較溫柔的sql注入菜循,如果是 drop table ,那不是要原地爆炸?
既然Sql 注入危害這么大申尤,那么怎么防范呢癌幕?
采用sql語句預編譯和綁定變量,是最簡單昧穿,也是最有效的方案.
那什么是預編譯呢勺远?
like this
select * from test where name like ? args: WrappedArray(JdbcValue(%cxk%))
先是 select * from test where name like ? 這樣預編譯好,然后傳進來的數據以參數化的形式執(zhí)行sql,就可以防止sql 注入时鸵。
為什么這樣可以防止sql 注入呢胶逢?
分別給兩種場景測試
1.likeString
代碼如下
def likeString(name:String) ={
dataSource.rows[Test](sql" select * from test where name like "+s"'%${name}%'")
}
輸入 cxk%'; DELETE FROM test WHERE name like '%cxk
打斷點可以看到
將statement 中的值復制出來 到navicat 中,可以看到
那么jdbc 執(zhí)行就會 直接執(zhí)行饰潜,然后把cxk 刪了初坠。
2.like
代碼如下
def like(name:String) ={
// cxu
// %cku%
dataSource.rows[Test](sql" select * from test where name like ${name.likeSql}")
}
implicit class StringBuildSqlLikeImplicit(s:String){
def likeSql: String ={
s"%${s}%"
}
def likeLeftSql: String = {
s"%${s}"
}
def likeRightSql: String ={
s"${s}%"
}
}
輸入 cxk%'; DELETE FROM test WHERE name like '%cxk
打斷點可以看到
可以看到 statement = select * from test where name like ** NOT SPECIFIED **
PreparedStatement.setString(1,'cxk%'; DELETE FROM test WHERE name like '%cxk')
之后,
statement =
select * from test where name like '%cxk\'; DELETE FROM test WHERE name like \'%cxk%'
將statement 中的值復制出來 到navicat 中彭雾,可以看到
string 內部的; % 被格式化碟刺, 這樣執(zhí)行的話,內部的sql 就以字符串的形式存在薯酝,這樣避免了Sql 注入半沽。
那 PreparedStatement 是這么做到的呢, 主要的原因是 PreparedStatement.setString(int parameterIndex, String x)
這里面會對x 進行一個格式化
判斷是否需要格式化
貼上源碼
private boolean isEscapeNeededForString(String x, int stringLength) {
boolean needsHexEscape = false;
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0: /* Must be escaped for 'mysql' */
needsHexEscape = true;
break;
case '\n': /* Must be escaped for logs */
needsHexEscape = true;
break;
case '\r':
needsHexEscape = true;
break;
case '\\':
needsHexEscape = true;
break;
case '\'':
needsHexEscape = true;
break;
case '"': /* Better safe than sorry */
needsHexEscape = true;
break;
case '\032': /* This gives problems on Win32 */
needsHexEscape = true;
break;
}
if (needsHexEscape) {
break; // no need to scan more
}
}
return needsHexEscape;
}
可以看到 它會對傳進來的參數判斷吴菠,如果含有一些非法字符會判斷傳過來的值需要格式化者填, 那它是怎么格式化的呢? 我們看下源碼
// setString 里的部分源碼
if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
needsQuoted = false; // saves an allocation later
StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));
buf.append('\'');
//
// Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...
//
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0: /* Must be escaped for 'mysql' */
buf.append('\\');
buf.append('0');
break;
case '\n': /* Must be escaped for logs */
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '"': /* Better safe than sorry */
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\032': /* This gives problems on Win32 */
buf.append('\\');
buf.append('Z');
break;
case '\u00a5':
case '\u20a9':
// escape characters interpreted as backslash by mysql
if (this.charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
this.charsetEncoder.encode(cbuf, bbuf, true);
if (bbuf.get(0) == '\\') {
buf.append('\\');
}
}
// fall through
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
從這里可以看出做葵,會講' 加一個'' 那么原傳入的Sring 就會被格式化成上文所說占哟。
打斷點我們可以看到
這一塊是jdbc PreparedStatement 對SQL注入的防范。
從網上我還看到了一些這樣的
那么,什么是所謂的“precompiled SQL statement”呢榨乎?
回答這個問題之前需要先了解下一個SQL文在DB中執(zhí)行的具體步驟:
1.Convert given SQL query into DB format -- 將SQL語句轉化為DB形式(語法樹結構)
2.Check for syntax -- 檢查語法
3.Check for semantics -- 檢查語義
4.Prepare execution plan -- 準備執(zhí)行計劃(也是優(yōu)化的過程嗓化,這個步驟比較重要,關系到你SQL文的效率谬哀,準備在后續(xù)文章介紹)
5.Set the run-time values into the query -- 設置運行時的參數
6.Run the query and fetch the output -- 執(zhí)行查詢并取得結果
出自 PreparedStatement是如何防止SQL注入的
打斷點調試的時候看到,PreparedStatement 最終還是會轉化成statement 然后執(zhí)行严肪,
jdbc 這么做應該是做應該是為了 mysql 的緩存機制史煎,我們知道,mysql 進行select 查詢的時候驳糯,會有一個緩存機制篇梭,如果執(zhí)行語句一致的話,就會拿mysql 的緩存直接獲取數據酝枢,如果以參數形式傳到mysql 的話恬偷,這樣就沒有辦法命中緩存了(個人看法,錯誤請佐證)帘睦。
綜上所述袍患,SQL注入,用PreparedStatement 防治是可以防治的竣付,代碼中也盡量用 PreparedStatement 這種形式诡延。
題外話,那么這個是jdbc 的做法古胆,那其他的框架是怎么解決SQL 注入的呢?
PHP 防治Sql注入
1.通過函數去對一些特殊字符進行處理 例如 addslashes(str)
2.預編譯的做法
Node 防治SQL 注入
1.使用escape()對傳入參數進行編碼,
2.使用connection.query()的查詢參數占位符:(預編譯)
3.使用escapeId()編碼SQL查詢標識符:
4.使用mysql.format()轉義參數:
參考文章 node-mysql中防止SQL注入