sql注入問題從WEB誕生到現在也一直沒停過,各種大小公司都出現過sql注入問題,導致被拖庫,然后存在社工庫撞庫等一系列影響。
防止sql注入個人理解最主要的就一點,那就是變量全部參數化,能根本的解決sql注入問題。比如代碼里寫sql或數據庫層面寫存儲過程,只要不拼接sql語句,不執行動態sql語句,那就能防止sql注入。
但實際情況sql語句可能非常復雜。很多年前有討論很熱的話題,非常復雜的業務邏輯到底用sql語句在存儲過程里來實現,還是在代碼service層來實現復雜的業務邏輯。比如電信移動很多老項目都是有非常復雜的存儲過程或函數來實現業務,這里不說到底放哪里好,因為和實際業務情況有關系。不管把這么復雜的業務邏輯在代碼層面service或dao里實現還是存儲過程實現,都需要防止sql注入問題。
和數據庫交互的業務系統,去掉業務邏輯層面,不管應用服務器還是數據庫服務器,就剩下sql的操作。這里說下我認為的幾種和實際情況最接近的寫sql語句的情況:
1、where里是單條件或條件個數明確的sql語句:
比如按照用戶ID查詢用戶信息;1個訂單多個產品,按照訂單ID,產品ID查詢該訂單里某個產品的信息,where條件是明確的。
2、where條件不明確的,不明確的原因是需要根據用戶的選擇作為條件:
比如查詢訂單,訂單有多個元素:OrderID,UserID,ProductID,ProductName,CreateTime,Status,現在是多條件搜索頁面,用戶可能選擇按照CreateTime和Status作為條件查詢某個時間段訂單狀態為支付完成的,where條件為:where CreateTime=.. and Status=..,如果用戶按照其他元素搜索,where寫法又不一樣。
3、where條件是一個in范圍的或like查詢的:
比如where UserID in (1,2,3,4)。
這里最簡單的是第1個情況,條件明確,直接把每個確定的條件參數化查詢sql即可。
第2個情況稍微復雜點,一般開發人員很容易拼接sql語句,尤其條件非常多的情況下,按照用戶查詢的不同條件,if else..按照條件直接拼接sql語句,這樣就造成了sql注入。實際處理時,可以在第一個if前定義一個List變量,針對每個if條件,如果符合if條件,sql語句就參數化一個占位符,List變量add一個用戶傳入的條件。下面是示例代碼:
if (null != begindate && !"".equals(begindate)) {
// 開始日期
sqlBuilder.append(" and beginDate >= ? ");
params.add(DateUtil.conversionDate6(begindate.trim() + " 00:00"));
}
sqlBuilder用於參數化拼接sql,params是參數化集合List params,后面再參數化sql查詢:
Query query = sessionFactory.getCurrentSession().createQuery(sqlBuilder.toString());
private void setQueryParamter(Query query, List params) {
if (!params.isEmpty()) {
for (int i = 0; i < params.size(); i++) {
query.setParameter(i, params.get(i));
}
}
}
第3個情況in會有一個問題,就是比如in(?)?傳入的問題,需要把?參數傳為一個數組,經常出現的問題可能傳入一個字符串導致查詢不出來,比如:
String sqlStr = "select * from user i where i.id in (:ids)";
Query query = sessionFactory.getCurrentSession().createQuery(queryString);
query.setParameterList("ids", idsValue);
idsValue可能會被賦值為"'1','2','3'"的字符串,實際應該為String[] splits=...的一個數組。
like里應該如下:
sql.append("and name like ? ");
list.add("%"+param+"%");
而不應該寫為:
sql.append("and name like '%?%' ");
list.add(param);
list作為參數化傳入的參數。
上面的示例代碼是java的示例代碼,其他.NET和PHP也可以參考。不同數據庫參數化的占位符號不一樣,比如msql是@,oracle是:,mysql是?,mysql里用名字取名的占位符是":名字",比如前面的:ids。
上面說了如何防止sql注入,下面簡單介紹下神器sqlmap,它是一個典型的sql注入檢測和利用工具。
sqlmap支持5種sql注入模式:
1、基於布爾的盲注(Boolean-based blind SQL injection),即可以根據返回頁面判斷條件真假的注入。
比如對參數加一個' and '1'='1和' and '1'='2,如果第一個能查詢出來,第二個不行,則說明可以注入,后面可以根據字段and exist(...)不斷猜測。
2、基於時間的盲注(Time-based blind SQL injection),即不能根據頁面返回內容判斷任何信息,用條件語句查看時間延遲語句是否執行(即頁面返回時間是否增加)來判斷。
比如直接把某個參數的值刪除,改為if((ascii(mid(user(),1,1))=103 and sleep(3))如果ascii(mid(user(),1,1))=103條件為真,則會等待3秒才返回結果,如果ascii(mid(user(),1,1))=103為假,則直接返回頁面,這樣不斷猜測user()的第1,2,3個字符。
3、基於報錯注入(Error-based SQL injection),即頁面會返回錯誤信息,或者把注入的語句的結果直接返回在頁面中。
比如在條件后面增加+and(select 1 from(select count(*),concat((select (select (select concat(0x7e,user(),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a),通過報錯把user()顯示出來,這個方法是最快的。
這里報錯注入的sql很不好理解,首先它實現的前提是如果sql報錯了,后台配置的按照sql怎么出錯怎么拋出異常到頁面(即非友好頁面),然后比如這里報錯主要依靠floor(rand(0)*2),rand(0)是mysql的一個函數,他會返回0到1之間的隨機浮點數,rand(0)*2為1到2之間的隨機浮點數,floor是往下取整的函數,比如floor(0.2)=0,floor(1.2)=1。這里group by x就非常重要了,比如我們查詢select 1 from information_schema.tables group by rand(0) limit 0,30;將會查詢30個1出來,原因是group by 一個隨機數,每次都將查詢不同的數據出來,limit限制30就顯示30條。所以剛才試的sql語句,因為group by x,x的值是隨機的,比如x值為~root@127.0.0.1~0,也可能為~root@127.0.0.1~1,group by 這個數據如果遇到兩個相同的group key,則就報錯了,如果頁面吧錯誤拋出到頁面,則可以通過正則匹配~和~符號之間的為user()的值,比如報錯:Duplicate entry '~root@127.0.0.1~1' for key 'group_key'。
4、聯合查詢注入(UNION query SQL injection),可以使用union的情況下的注入。
這個很好理解,就是在select后union把數據查詢出來,比如先在查詢條件后輸入order by 3,order by 4..到order by 8的時候報錯了,說明select了7條記錄。然后條件后輸入 and 1=2 union select 1,user(),database()...查詢7個數據出來,由於第一個select后有了1=2條件不成i,因此只會查詢我們寫的select語句,這樣覆蓋之前的查詢記錄返回出我們需要的數據,這個速度也非常快。
5、堆查詢注入(Stacked queries SQL injection),可以同時執行多條語句的執行時的注入。
比如用;分隔后執行其他sql語句,一般用於更新數據或執行系統相關命令,比如在參數后面增加; update sysuser set password='' where username='admin',或比如sqlserver執行exec master..xp_cmdshell,執行cmd命令創建遠程登錄用戶等。
sqlmap非常強大,可以get,post,httpheader的值變化識別是否存在注入,它可以自動識別MySQL, Oracle, PostgreSQL, Microsoft SQL Server, Microsoft Access, IBM DB2, SQLite, Firebird, Sybase和SAP MaxDB數據庫,還有一些常用的繞過防止sql注入的替換符號,也可以自己擴展python腳本繞過WAF之類的防注入程序,常用參數和常用檢測命令后續單獨再寫一個blog文章。
如需轉載,請注明來自:http://lawson.cnblogs.com