第一篇:SQL Injection Prevention Cheat Sheet
本文的重點是提供清晰,簡單,防止SQL注入漏洞在您的應用程序可操作的指導。有如此多成功的SQL注入發生是有些遺憾的,因爲在你編寫的代碼中SQL注入是非常容易避免的。
SQL注入漏洞是被軟件開發人員在創建動態的數據庫查詢時引入的,這些數據庫查詢包含用戶的輸入。爲了避免SQL注入缺陷是很容易的。開發人員不要寫動態的數據庫查詢,阻止用戶的輸入惡意的SQL去影響查詢的邏輯。
本文提供一些用於阻止SQL注入漏洞的方法去避免這兩個問題。這些技術被用於任何的編程語言和數據庫。這裏存在其他類型的數據庫,比如xml 數據庫也存在也是的問題,也能使用相同的方法去避免SQL注入漏洞。
1. 主要防禦方法
l 使用prepared statement(參數化查詢);
l 使用存儲過程;
l 控制用戶的輸入;
2. 其他防禦方法
l 賦予最小的權限;
l 對輸入使用白名單控制;
3. 不安全的例子
典型的SQL注入例子如下:下面(Java)例子是不安全的,並且能在數據庫查詢中運行攻擊者注入的的代碼。未驗證過的用戶輸入值被附加到SQL查詢中,這些查詢可以是攻擊者想要的任意的注入代碼。不幸的是,鏈接操作數據庫的方式是相當常見的。
- String query ="select account_balance from user_data where username = "request.getParamter("customName")";
- try{
- Statement statement = connection.createStatement(...);
- ResultSet results = statement.executeQuery(query);
- }
4. 主要防禦方法
4.1. 防禦方法1:preparedstatement()
prepared statement的用處是所有的開發人員應該知道如何去寫數據庫查詢。他們比動態查詢更加容易理解,更加容易編寫。預處理過的查詢要求開發人員首先定義所有的SQL代碼,然後成傳遞參數到這個查詢中。無論用戶輸入的是什麼內容,這種編碼方式數據庫能夠區分出來數據與代碼。prepared statement確保攻擊者不能夠改變SQL查詢的目的,即使SQL中插入的攻擊者的SQL命令。下面是一個安全的例子,如果攻擊者輸入一個user_id爲 tom' or '1' = '1,參數化的查詢不是可攻擊的並且查詢user_id通過完整的匹配爲 tom' or '1' ='1。
相關語言的處理
l Java EE:使用preparedStatement()來綁定參數;
l .net:使用參數化的查詢,例如:用sqlCommand() 或者 oleDbCommand()綁定參數;
l PHP:用強類型參數化查詢的PDO,使用bindParam();
l Hibernate:使用createQuery()來綁定參數;
l SQlite:使用sqlite3_prepare()去創建一個;
在少數情況下,prepared statement 不利於性能。當面對這種情況時,最好的方法爲強制驗證所有的輸入數據。
Java prepared statement
例子:下面的代碼例子使用預知語句,Java參數化查詢的實現結果與數據庫查詢結果相同。
1.1.1.1 String custname = request.getParamter("customName");
1.1.1.2 String query ="selectaccount_balance from user_data where username = ?";
1.1.1.3
1.1.1.4 PreparedStatement pstmt = connection.preparedStatement(query);
1.1.1.5 pstmt.setString(1,custname);
1.1.1.6 ResultSet results = pstmt.executeQuery();
4.2防禦2:存儲過程
存儲過程與預置語句有相同的影響在實現的時候,這是大多數存儲過程實現的規範。需要開發人員去創建帶有動態參數的SQL語句。存儲過程與預置語句的差別是存儲過程的SQL代碼定義並且存儲在數據庫中的,然後被其他應用調用。這兩種技術在預防SQL注入有相同的效果,因此選擇一種最合適的方法取決於開發人員。
注意:安全地實現意味着存儲過程不應該包含任何不安全的動態的SQL。開發人員一般不會在存儲過程中生成動態的SQL。如果動態的SQL不能避免,這個存儲過程必須使用輸入驗證或者合適逃逸正如在這篇文章中所描述確保給存儲過程的用戶輸入不能被應用到SQL注入到動態的查詢中。代碼審覈人應該尋找sp_execute,execute和exec在存儲過程的用處。
有幾種風險能夠增加存儲過程的風險。例如:在MS SQL Server,有三大默認規則:db_datareader,db_datawriter和db_owner。在存儲過程投入使用之前,DBA會根據需求給webservervice用戶db_datareader,db_datawriter的權限。然而,存儲過程需要執行權限。
用戶管理的一些設置已經集中了,而且這些設置被限制在那些3個角色中,因爲所有的web apps 是在db_owner的權限下運行的。
安全的Java存儲過程例子
下面的代碼例子使用CallableStatement,存儲過程的接口的java實現,執行相同的數據查詢。
String custname = request.getParameter("customerName"); // This should REALLY be validated
try {
CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
// … result set handling
} catch (SQLException se) {
// … logging and error handling
}
4.3轉譯用戶提供的輸入
第2種技術是在用戶提交查詢之前轉譯用戶的輸入。不過,這種技術與參數化查詢相比是脆弱的並且我們不能阻止所有的SQL注入。這項技術只能謹慎的使用去改造之前代碼,這樣是最有效的且成本最低的方式。
這項技術是這樣工作的。每種數據針對某些特定的查詢支持一個或者多個字符轉譯模式。如果你通過數據庫的合適的轉譯模式轉譯用戶的所有輸入,數據庫對開發人員寫的SQL語句不會產生困惑,從而避免任何可能的SQL注入漏洞。
爲了找到針對數據庫的編碼器的javadoc文檔,點擊左邊的‘codec’類。這裏有很多編碼器的實現。兩類數據庫確定的編碼器是oralcecodec和mysqlcodec。
轉譯動態查詢
使用ESAPI數據庫轉譯是很簡單的。一個oracle例子:
ESAPI.encoder().encodeForSQL( new OracleCodec(), queryparam );
因此,如果在代碼中你有一個動態的SQL查詢,如果是Oracle如下:
String query = "SELECT user_id FROM user_data WHERE user_name = '" + req.getParameter("userID")
+ "' and user_password = '" + req.getParameter("pwd") +"'";
try {
Statement statement = connection.createStatement( … );
ResultSet results = statement.executeQuery( query );
}
你可以重新寫第一行如下:
Codec ORACLE_CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = '" +
ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("userID")) + "' and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("pwd")) +"'";
這樣這個SQL能避免注入,無論提交的輸入是什麼。
爲了獲得代碼的可讀性,你可以構建自己的OracleEncode。
Encoder oe = new OracleEncoder();
String query = "SELECT user_id FROM user_data WHERE user_name = '"
+ oe.encode( req.getParameter("userID")) + "' and user_password = '"
+ oe.encode( req.getParameter("pwd")) +"'";
通過這種方法,所有的開發人員必須包裝用戶的輸入參數通過ESAPI.encoder().encodeForOracle( )。
Like關鍵字允許文字搜索。在oracle中,下劃線“_”只能匹配一個字符,百分號“%”用於匹配0個或者多個字符。這些字符必須被轉譯在like語句中,如下:
SELECT name FROM emp
WHERE id LIKE '%/_%' ESCAPE '/';
SELECT name FROM emp
WHERE id LIKE '%\%%' ESCAPE '\';
附加防禦
除了採取三個主要防禦方法之一,我們也建議採用下面的附加方法去提供更加深度的防禦。他們包括:最小權限,白名單驗證。
1最小權限
爲了降低潛在的SQL注入威脅,應該降低給每個數據庫賬號的權限。不要賦予DBA或者amdin權限給應用程序。