PHP安全、Sql防注入安全彙總

OldBoy~發表於2017-05-17

利用Mysqli和PDO

產生原因

主要就是一些資料沒有經過嚴格的驗證,然後直接拼接 SQL 去查詢。導致漏洞產生,比如:

$id  = $_GET['id'];
$sql = "SELECT name FROM users WHERE id = $id";

因為沒有對 $_GET['id'] 做資料型別驗證,注入者可提交任何型別的資料,比如 " and 1= 1 or " 等不安全的資料。如果按照下面方式寫,就安全一些。

$id  = intval($_GET['id']);
$sql = "SELECT name FROM users WHERE id = $id";

把 id 轉換成 int 型別,就可以去掉不安全的東西。

驗證資料

防止注入的第一步就是驗證資料,可以根據相應型別進行嚴格的驗證。比如 int 型別直接同過 intval 進行轉換就行:

$id =intval( $_GET['id']);

字元處理起來比較複雜些,首先通過 sprintf 函式格式話輸出,確保它是一個字串。然後通過一些安全函式去掉一些不合法的字元,比如:

$str = addslashes(sprintf("%s",$str));
//也可以用 mysqli_real_escape_string 函式替代addslashes

這樣處理以後會比較安全。當然還可以進一步去判斷字串長度,去防止「緩衝區溢位攻擊」比如:

$str = addslashes(sprintf("%s",$str)); 
$str = substr($str,0,40); //最大長度為40

引數化繫結

引數化繫結,防止 SQL 注入的又一道屏障。php MySQLi 和 PDO 均提供這樣的功能。比如 MySQLi 可以這樣去查詢:

$mysqli = new mysqli('localhost', 'my_user', 'my_password', 'world');
$stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)");
$code = 'DEU';
$language = 'Bavarian';
$official = "F";
$percent = 11.2;
$stmt->bind_param('sssd', $code, $language, $official, $percent);

PDO 的更是方便,比如:

/* Execute a prepared statement by passing an array of values */
$sql = 'SELECT name, colour, calories  
FROM fruit
WHERE calories < :calories AND colour = :colour'; $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':calories' => 150, ':colour' => 'red'));
$red = $sth->fetchAll();
$sth->execute(array(':calories' => 175, ':colour' => 'yellow'));
$yellow = $sth->fetchAll();

我們多數使用 php 的框架進行程式設計,所以最好不要自己拼寫 SQL,按照框架給定引數繫結進行查詢。遇到較為複雜的 SQL 語句,一定要自己拼寫的時候,一定要注意嚴格的判斷。沒有用 PDO 或者 MySQLi 也可以自己寫個 prepared,比如 wordprss db 查詢語句,可以看出也是經過嚴格的型別驗證。

function prepare( $query, $args ) {
    if ( is_null( $query ) )
         return;
    // This is not meant to be foolproof -- 
           but it will catch obviously incorrect usage.
    if ( strpos( $query, '%' ) === false ) {
         _doing_it_wrong( 'wpdb::prepare' , 
         sprintf ( __( 'The query argument of %s
                 must have a placeholder.' ), 'wpdb::prepare()' ), '3.9' );
   }
    $args = func_get_args();
    array_shift( $args );
    // If args were passed as an array (as in vsprintf), move them up
    if ( isset( $args[ 0] ) && is_array( $args[0]) )
         $args = $args [0];
    $query = str_replace( "'%s'", '%s' , $query ); 
        // in case someone mistakenly already singlequoted it
    $query = str_replace( '"%s"', '%s' , $query ); 
        // doublequote unquoting
    $query = preg_replace( '|(?<!%)%f|' , '%F' , $query ); 
        // Force floats to be locale unaware
    $query = preg_replace( '|(?<!%)%s|', "'%s'" , $query ); 
        // quote the strings, avoiding escaped strings like %%s
    array_walk( $args, array( $this, 'escape_by_ref' ) );
    return @ vsprintf( $query, $args );
}

總結

安全性很重要,也可以看出一個人基本功,專案漏洞百出,擴充套件性和可維護性再好也沒有用。平時多留意,樹立安全意識,養成一種習慣,一些基本的安全當然也不會佔用用 coding 的時間。養成這個習慣,即便在專案急,時間短的情況一下,依然可以做的質量很高。不要等到自己以後負責的東西,資料庫都被拿走了,造成損失才重視。共勉!

 

雖然國內很多PHP程式設計師仍在依靠addslashes防止SQL隱碼攻擊,還是建議大家加強中文防止SQL隱碼攻擊的檢查。addslashes的問題在於黑客可以用0xbf27來代替單引號,而addslashes只是將0xbf27修改為0xbf5c27,成為一個有效的多位元組字元,其中的0xbf5c仍會被看作是單引號,所以addslashes無法成功攔截。

當然addslashes也不是毫無用處,它是用於單位元組字串的處理,多位元組字元還是用mysql_real_escape_string吧。

另外對於php手冊中get_magic_quotes_gpc的舉例:

if (!get_magic_quotes_gpc()) {  

    $lastname = addslashes($_POST[&lsquo;lastname&rsquo;]); 

} else {  

    $lastname = $_POST[&lsquo;lastname&rsquo;]; 

}

最好對magic_quotes_gpc已經開放的情況下,還是對$_POST[&rsquo;lastname&rsquo;]進行檢查一下。

再說下mysql_real_escape_string和mysql_escape_string這2個函式的區別:

mysql_real_escape_string 必須在(PHP 4 >= 4.3.0, PHP 5)的情況下才能使用。否則只能用 mysql_escape_string ,兩者的區別是:mysql_real_escape_string 考慮到連線的

當前字符集,而mysql_escape_string 不考慮。

總結一下:

* addslashes() 是強行加\;

* mysql_real_escape_string() 會判斷字符集,但是對PHP版本有要求;

* mysql_escape_string不考慮連線的當前字符集。

服務端配置方面

(1) 開啟php的安全模式

php的安全模式是個非常重要的內嵌的安全機制,能夠控制一些php中的函式,比如system(),

同時把很多檔案操作函式進行了許可權控制,也不允許對某些關鍵檔案的檔案,比如/etc/passwd,

但是預設的php.ini是沒有開啟安全模式的,我們把它開啟:

safe_mode = on

(2) 使用者組安全

當safe_mode開啟時,safe_mode_gid被關閉,那麼php指令碼能夠對檔案進行訪問,而且相同

組的使用者也能夠對檔案進行訪問。

建議設定為:

safe_mode_gid = off

如果不進行設定,可能我們無法對我們伺服器網站目錄下的檔案進行操作了,比如我們需要

對檔案進行操作的時候。

(3) 安全模式下執行程式主目錄

如果安全模式開啟了,但是卻是要執行某些程式的時候,可以指定要執行程式的主目錄:

safe_mode_exec_dir = D:/usr/bin

一般情況下是不需要執行什麼程式的,所以推薦不要執行系統程式目錄,可以指向一個目錄,

然後把需要執行的程式拷貝過去,比如:

safe_mode_exec_dir = D:/tmp/cmd

但是,我更推薦不要執行任何程式,那麼就可以指向我們網頁目錄:

safe_mode_exec_dir = D:/usr/www

(4) 安全模式下包含檔案

如果要在安全模式下包含某些公共檔案,那麼就修改一下選項:

safe_mode_include_dir = D:/usr/www/include/

其實一般php指令碼中包含檔案都是在程式自己已經寫好了,這個可以根據具體需要設定。

(5) 控制php指令碼能訪問的目錄

使用open_basedir選項能夠控制PHP指令碼只能訪問指定的目錄,這樣能夠避免PHP指令碼訪問

不應該訪問的檔案,一定程度上限制了phpshell的危害,我們一般可以設定為只能訪問網站目錄:

open_basedir = D:/usr/www

(6) 關閉危險函式

如果開啟了安全模式,那麼函式禁止是可以不需要的,但是我們為了安全還是考慮進去。比如,

我們覺得不希望執行包括system()等在那的能夠執行命令的php函式,或者能夠檢視php資訊的

phpinfo()等函式,那麼我們就可以禁止它們:

disable_functions = system,passthru,exec,shell_exec,popen,phpinfo

如果你要禁止任何檔案和目錄的操作,那麼可以關閉很多檔案操作

disable_functions = chdir,chroot,dir,getcwd,opendir,readdir,scandir,fopen,unlink,delete,copy,mkdir, rmdir,rename,file,file_get_contents,fputs,fwrite,chgrp,chmod,chown

以上只是列了部分不叫常用的檔案處理函式,你也可以把上面執行命令函式和這個函式結合,

就能夠抵制大部分的phpshell了。

(7) 關閉PHP版本資訊在http頭中的洩漏

我們為了防止黑客獲取伺服器中php版本的資訊,可以關閉該資訊斜路在http頭中:

expose_php = Off

比如黑客在 telnet www.12345.com 80 的時候,那麼將無法看到PHP的資訊。

(8) 關閉註冊全域性變數

在PHP中提交的變數,包括使用POST或者GET提交的變數,都將自動註冊為全域性變數,能夠直接訪問,

這是對伺服器非常不安全的,所以我們不能讓它註冊為全域性變數,就把註冊全域性變數選項關閉:

register_globals = Off

當然,如果這樣設定了,那麼獲取對應變數的時候就要採用合理方式,比如獲取GET提交的變數var,

那麼就要用$_GET[&#39;var&#39;]來進行獲取,這個php程式設計師要注意。

(9)關閉段標籤功能,short_open_tag

short_open_tag = Off

防止一句話木馬攻擊

(10) 開啟magic_quotes_gpc來防止SQL隱碼攻擊

SQL隱碼攻擊是非常危險的問題,小則網站後臺被入侵,重則整個伺服器淪陷,

所以一定要小心。php.ini中有一個設定:

magic_quotes_gpc = Off

這個預設是關閉的,如果它開啟後將自動把使用者提交對sql的查詢進行轉換,

比如把 &#39; 轉為 \&#39;等,這對防止sql注射有重大作用。所以我們推薦設定為:

magic_quotes_gpc = On

(11) 錯誤資訊控制

一般php在沒有連線到資料庫或者其他情況下會有提示錯誤,一般錯誤資訊中會包含php指令碼當

前的路徑資訊或者查詢的SQL語句等資訊,這類資訊提供給黑客後,是不安全的,所以一般伺服器建議禁止錯誤提示:

display_errors = Off

如果你卻是是要顯示錯誤資訊,一定要設定顯示錯誤的級別,比如只顯示警告以上的資訊:

error_reporting = E_WARNING & E_ERROR

當然,我還是建議關閉錯誤提示。

(12) 錯誤日誌

建議在關閉display_errors後能夠把錯誤資訊記錄下來,便於查詢伺服器執行的原因:

log_errors = On

同時也要設定錯誤日誌存放的目錄,建議根apache的日誌存在一起:

error_log = D:/usr/local/apache2/logs/php_error.log

注意:給檔案必須允許apache使用者的和組具有寫的許可權。

MYSQL的降權執行

新建立一個使用者比如mysqlstart

net user mysqlstart fuckmicrosoft /add

net localgroup users mysqlstart /del

不屬於任何組

如果MYSQL裝在d:\mysql ,那麼,給 mysqlstart 完全控制 的許可權

然後在系統服務中設定,MYSQL的服務屬性,在登入屬性當中,選擇此使用者 mysqlstart 然後輸入密碼,確定。

重新啟動 MYSQL服務,然後MYSQL就執行在低許可權下了。

如果是在windos平臺下搭建的apache我們還需要注意一點,apache預設執行是system許可權,

這很恐怖,這讓人感覺很不爽.那我們就給apache降降許可權吧。

net user apache fuckmicrosoft /add

net localgroup users apache /del

ok.我們建立了一個不屬於任何組的使用者apche。

我們開啟計算機管理器,選服務,點apache服務的屬性,我們選擇log on,選擇this account,我們填入上面所建立的賬戶和密碼,

重啟apache服務,ok,apache執行在低許可權下了。

實際上我們還可以通過設定各個資料夾的許可權,來讓apache使用者只能執行我們想讓它能幹的事情,給每一個目錄建立一個單獨能讀寫的使用者。

這也是當前很多虛擬主機提供商的流行配置方法哦,不過這種方法用於防止這裡就顯的有點大材小用了。

相關文章