天天看點

PHP開發中常見的安全問題詳解和解決方法

這篇文章主要介紹了PHP開發中常見的安全問題詳解和解決方法,詳細介紹了例如:Sql注入、CSRF、Xss、CC等攻擊手段的背景知識以及解決方法,需要的朋友可以參考下。

淺談Php安全和防Sql注入,防止Xss攻擊,防盜鍊,防CSRF

前言:

首先,筆者不是web安全的專家,是以這不是web安全方面專家級文章,而是學習筆記、細心總結文章,裡面有些是我們phper不易發現或者說不重視的東西。是以筆者寫下來友善以後查閱。在大公司肯定有專門的web安全測試員,安全方面不是phper考慮的範圍。但是作為一個phper對于安全知識是:“知道有這麼一回事,程式設計時自然有所注意”。

目錄:

1、php一些安全配置

(1)關閉php提示錯誤功能

(2)關閉一些“壞功能”

(3)嚴格配置檔案權限。

2、嚴格的資料驗證,你的使用者不全是“好”人

2.1為了確定程式的安全性,健壯性,資料驗證應該包括内容。

2.2程式員容易漏掉point或者說需要注意的事項

3、防注入

   3.1簡單判斷是否有注入漏洞以及原理

   3.2常見的mysql注入語句

       (1)不用使用者名和密碼

       (2)在不輸入密碼的情況下,利用某使用者

       (3)猜解某使用者密碼

       (4)插入資料時提權

       (5)更新提權和插入提權同理

       (6)惡意更新和删除

       (7)union、join等

       (8)通配符号%、_

       (9)還有很多猜測表資訊的注入sql 

   33防注入的一些方法

       2.3.1 php可用于防注入的一些函數和注意事項。

       2.3.2防注入字元優先級。

2.3.3防注入代碼

    (1)參數是數字直接用intval()函數

    (2)對于非文本參數的過濾

(3)文本資料防注入代碼。

(4)當然還有其他與addslashes、mysql_escape_string結合的代碼。

4、防止xss攻擊

4.1Xss攻擊過程

4.2常見xss攻擊地方

4.3防XSS方法

5、CSRF

5.1簡單說明CSRF原理

5.2防範方法

6、防盜鍊

7、防拒CC攻擊

1、php一些安全配置

(1)關閉php提示錯誤功能

在php.ini 中把display_errors改成

display_errors = OFF 
           

或在php檔案前加入

error_reporting(0)
           

1) 使用error_reporting(0);失敗的例子:

A檔案代碼:

<?  

error_reporting(0);  

echo 555  

echo 444;  

?>
           

錯誤:

Parse error: parse error, expecting `’,” or `';” in E:\webphp\2.php on line 4
           

2)使用error_reporting(0);成功的例子:

a檔案代碼:

<?php  

error_reporting(0);  

include(“b.php”);  

?>
           

b檔案代碼:

<?php  

echo 555  

echo 444;  

?>
           

這是很多phper說用error_reporting(0)不起作用。第一個例子A.php裡面有緻命錯誤,導緻不能執行,不能執行伺服器則不知有這個功能,是以一樣報錯。

第二個例子中a.php成功執行,那麼伺服器知道有抑制錯誤功能,是以就算b.php有錯誤也抑制了。

ps:抑制不了mysql錯誤。

(2)關閉一些“壞功能”

1)關閉magic quotes功能

在php.ini 把magic_quotes_gpc = OFF

避免和addslashes等重複轉義

2)關閉register_globals = Off

在php.ini 把register_globals = OFF

在register_globals = ON的情況下

位址欄目:http://www.csdn.com?page=104919018

<?php  

//$page = $_GET[‘page’]   //因為register_globals = ON 是以這步不用了直接可以用$page

  echo $page;  

?>

           

這種情況下會導緻一些未初始化的變量很容易被修改,這也許是緻命的。是以把register_globals = OFF關掉

(3)嚴格配置檔案權限。

為相應檔案夾配置設定權限,比如包含上傳圖檔的檔案不能有執行權限,隻能讀取

2、嚴格的資料驗證,你的使用者不全是“好”人。

記得筆者和一個朋友在讨論資料驗證的時候,他說了一句話:你不要把你使用者個個都想得那麼壞!但筆者想說的這個問題不該出現在我們開發情景中,我們要做的是嚴格驗證控制資料流,哪怕10000萬使用者中有一個是壞使用者也足以緻命,再說好的使用者也有時在資料input框無意輸入中文的時,他已經不經意變“壞”了。

2.1為了確定程式的安全性,健壯性,資料驗證應該包括

(1)     關鍵資料是否存在。如删除資料id是否存在

(2)     資料類型是否正确。如删除資料id是否是整數

(3)     資料長度。如字段是char(10)類型則要strlen判斷資料長度

(4)     資料是否有危險字元

資料驗證有些人主張是把功能完成後再慢慢去寫安全驗證,也有些是邊開發邊寫驗證。筆者偏向後者,這兩種筆者都試過,然後發現後者寫的驗證相對健壯些,主要原因是剛開發時想到的安全問題比較齊全,等開發完功能再寫時有兩個問題,一個phper急于完成名額草草完事,二是确實漏掉某些point。

2.2程式員容易漏掉point或者說需要注意的事項:

(1)     進庫資料一定要安全驗證,筆者在廣州某家公司參與一個公司内部系統開發的時候,見過直接把$_POST資料傳給類函數classFunctionName($_POST),理由竟然是公司内部使用的,不用那麼嚴格。暫且不說邏輯操作與資料操控耦合高低問題,連判斷都沒判斷的操作是緻命的。安全驗證必須,沒任何理由推脫。

(2)     資料長度問題,如資料庫建表字段char(25),大多phper考慮到是否為空、資料類型是否正确,卻忽略字元長度,忽略還好更多是懶于再去判斷長度。(這個更多出現在新手當中,筆者曾經也有這樣的思想)

(3)     以為前端用js判斷驗證過了,背景不需要判斷驗證。這也是緻命,要知道僞造一個表單就幾分鐘的事,js判斷隻是為了減少使用者送出次數進而提高使用者體驗、減少http請求減少伺服器壓力,在安全情況下不能防“小人”,當然如果合法使用者在js驗證控制下是完美的,但作為phper我們不能隻有js驗證而抛棄再一次安全驗證。

(4)     缺少對表單某些屬性比如select、checkbox、radio、button等的驗證,這些屬性在web頁面上開發者已經設定定其值和值域(白名單值),這些屬性值在js驗證方面一般不會驗證,因為合法使用者隻有選擇權沒修改權,然後phper就在後端接受資料處理驗證資料的時候不會驗證這些資料,這是一個慣性思維,安全問題也就有了,小人一個僞表單。

(5)     表單相應元素name和資料表的字段名一緻,如使用者表使用者名的字段是user_name,然後表單中的使用者名輸入框也是user_name,這和暴庫沒什麼差別。

(6)     過濾危險字元方面如防注入下面會獨立講解。

3、防注入

3.1簡單判斷是否有注入漏洞以及原理。

網址:http://www.csdn.net/benwin.php?id=1 運作正常,sql語句如:select  *  from phpben where id = 1

(1) 網址:http://www.csdn.net/benwin.php?id=1′   sql語句如:select  *  from phpben where id = 1′  然後運作異常 這能說明benwin.php檔案沒有對id的值進行“’” 過濾和intval()整形轉換,當然想知道有沒有對其他字元如“%”,“/*”等都可以用類似的方法窮舉測試(很多測試軟體使用)

(2)網址:http://www.csdn.net/benwin.php?id=1 and 1=1  則sql語句可能是 select  *  from phpben where id = 1 and 1=1,運作正常且結果和http://www.csdn.net/benwin.php?id=1結果一樣,則說明benwin.php可能沒有對空格“ ”、和“and”過濾(這裡是可能,是以要看下一點)

(3)網址:http://www.csdn.net/benwin.php?id=1 and 1=2則sql語句可能是 select  *  from phpben where id = 1 and 1=2 如果運作結果異常說明sql語句中“and 1=2”起作用,是以能3個條件都滿足都則很确定的benwin.php存在注入漏洞。

ps:這裡用get方法驗證,post也可以,隻要把值按上面的輸入,可以一一驗證。

3.2常見的mysql注入語句。

(1)不用使用者名和密碼

//正常語句  
$sql =”select * from phpben where user_name=’admin’ and pwd =’123′”;  
//在使用者名框輸入’or’=’or’或 ‘or 1=’1 然後sql如下  
$sql =”select * from phpben where user_name=’ ‘or’=’or” and pwd =” “;  
$sql =”select * from phpben where user_name=’ ‘or 1=’1′ and pwd =” “;
           

這樣不用輸入密碼。話說筆者見到登入框都有嘗試的沖動。

(2)在不輸入密碼的情況下,利用某使用者。

//正常語句  
$sql =”select * from phpben where user_name=’$username’ and pwd =’$pwd'”;  
//利用的使用者名是benwin 則使用者名框輸入benwin’#  密碼有無都可,則$sql變成  
$sql =”select * from phpben where user_name=’ benwin’#’ and pwd =’$pwd'”;
           

這是因為mysql中其中的一個注悉是“#”,上面語句中#已經把後面的内容給注悉掉,是以密碼可以不輸入或任意輸入。網上有些人介紹說用“”時,mysql會報錯,也不是說“”不能注悉,而是這裡很難添加上“*/”來結束注悉,還有“– ”也是可以注悉mysql 但要注意“–”後至少有一個空格也就是“– ”,當然防注入代碼要把三種都考慮進來,值得一提的是很多防注入代碼中沒把“– ”考慮進防注入範圍。

(3)猜解某使用者密碼

//正常語句  
$sql =”select * from phpben.com where user_name=’$username’ and pwd =’$pwd'”;  
//在密碼輸入框中輸入“benwin’ and left(pwd,1)=’p’#”,則$sql是  
$sql =”select * from phpben.com where user_name=’ benwin’ and left(pwd,1)=’p’#’ and pwd =’$pwd'”;

           

如果運作正常則密碼的密碼第一個字元是p,同理猜解剩下字元。

(4)插入資料時提權

//正常語句,等級為1  
$sql = “insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin’,’iampwd’,1) “;  
//通過修改密碼字元串把語句變成  
$sql = “insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin’,’iampwd’,5)#’,1) “;  
$sql = “insert into phpben.com (`user_name`,`pwd`,`level`) values(‘benwin’,’iampwd’,5)–  ‘,1) “;這樣就把一個權限為1的使用者提權到等級5
           

(5)更新提權和插入提權同理

//正常語句  
$sql = “update phpben set  `user_name` =’benwin’, level=1″;  
//通過輸入使用者名值最終得到的$sql  
$sql = “update phpben set  `user_name` =’benwin’,level=5#’, level=1″;  
$sql = “update phpben set  `user_name` =’benwin’,level=5–  ‘, level=1″;
           

(6)惡意更新和删除

//正常語句  
$sql = “update phpben set `user_name` = ‘benwin’ where id =1″;  
//注入後,惡意代碼是“1 or id>0”  
$sql = “update phpben set `user_name` = ‘benwin’ where id =1 or id>0″;  
//正常語句  
$sql = “update phpben set  `user_name` =’benwin’ where id=1″;  
//注入後  
$sql = “update phpben set  `user_name` =’benwin’ where id>0#’ where id=1″;  
$sql = “update phpben set  `user_name` =’benwin’ where id>0– ‘ where id=1″;
           

(7)union、join等

//正常語句  
$sql =”select * from phpben1 where `user_name`=’benwin’ “;  
//注入後  
$sql =”select * from phpben1 where`user_name`=’benwin’ uninon select * from phpben2#’ “;  
$sql =”select * from phpben1 where`user_name`=’benwin’ left join……#’ “;
           

(8)通配符号%、_

//正常語句  
$sql =”select * from phpben where `user_name`=’benwin’ “;  
//注入通配符号%比對多個字元,而一個_比對一個字元,如__則比對兩個字元  
$sql =”select * from phpben where `user_name` like ‘%b’ “;  
$sql =”select * from phpben where `user_name` like ‘_b_’ “;
           

這樣隻要有一個使用者名字是b開頭的都能正常運作,“ _b_”是比對三個字元,且這三個字元中間一個字元時b。這也是為什麼有關addslashes()函數介紹時提示注意沒有轉義%和_(其實這個是很多phper不知問什麼要過濾%和_下劃線,隻是一味的跟着網上代碼走)

(9)還有很多猜測表資訊的注入sql

//正常語句  
$sql =”select * from phpben1 where`user_name`=’benwin'”;  
//猜表名,運作正常則說明存在phpben2表  
$sql =”select * from phpben1 where`user_name`=’benwin’ and (select count(*) from phpben2 )>0#’ “;  
//猜表字段,運作正常則說明phpben2表中有字段colum1  
$sql =”select * from phpben1 where`user_name`=’benwin’ and (select count(colum1) from phpben2 )>0#'”;  
//猜字段值  
$sql =”select * from phpben1 where`user_name`=’benwin’ and left(pwd,1)=’p’#””;
           

當然還有很多,筆者也沒研究到專業人士那種水準,這裡提出這些都是比較常見的,也是phper應該知道并掌握的,而不是一味的在網上複制粘貼一些防注入代碼,知然而不解其然。

下面一些防注入方法回看可能更容易了解。

3.3防注入的一些方法

3.3.1 php可用于防注入的一些函數和注意事項。

(1)addslashes 和stripslashes。

Addslashes給這些 “’”、“””、“\”,“NULL” 添加斜杆“\’”、“\””、“\\”,“\NULL”, stripslashes則相反,這裡要注意的是php.ini是否開啟了magic_quotes_gpc=ON,開啟若使用addslashes會出現重複。是以使用的時候要先get_magic_quotes_gpc()檢查

一般代碼類似:

if(!get_magic_quotes_gpc())  
{  
         $abc = addslashes($abc);  
}
           

其實這個稍微學習php一下的人都知道了,隻不過筆者想系統點介紹(前面都說不是專家級文章),是以也順便寫上了。addslashes

(2)mysql_escape_string()和mysql_ real _escape_string()

mysql_real_escape_string 必須在(PHP 4 >= 4.3.0, PHP 5)的情況下才能使用。否則隻能用 mysql_escape_string

if (PHP_VERSION >= ‘4.3’)  
{  
$string  =  mysql_real_escape_string($string);  
}else  
{  
$string  =  mysql_escape_string($string );  
}
           

mysql_escape_string()和mysql_ real _escape_string()卻别在于後者會判斷目前資料庫連接配接字元集,換句話說在沒有連接配接資料庫的前提下會出現類似錯誤:

Warning: mysql_real_escape_string() [function.mysql-real-escape-string]: Access denied for user ‘ODBC’@’localhost’ (using password: NO) in E:\webphp\test.php on line 11
           

(3)字元代替函數和比對函數

str_replace() 、perg_replace()這些函數之是以也在這裡提是因為這些函數可以用于過濾或替代一些敏感、緻命的字元。

3.3.2防注入字元優先級。

防注入則要先知道有哪些注入字元或關鍵字,常見的mysql注入字元有字元界定符号如“’”、“””;邏輯關鍵字如“and”、“or”;mysql注悉字元如“#”,“– ”,“”;mysql通配符“%”,“_”;mysql關鍵字“select|insert|update|delete|*|union|join|into|load_file|outfile”

(1)對于一些有規定格式的參數來說,防注入優先級最高的是空格” ”。

如一些銀行卡号,身份證号,郵箱,電話号碼,,生日,郵政編碼等這些有自己規定的格式且格式規定不能有空格符号的參數,在過濾的時候一般最先過濾掉空格(包括一些空格“變種”),因為其他字元界定符号,邏輯關鍵字,mysql注悉,注意下圖可以看出重要的是“’”,“ ”

ps:空格字元的變種有:“%20”,“\n”,“\r”,“\r\n”,“\n\r”,“chr(“32″)” 這也是為什麼mysql_escape_string()和mysql_real_escape_string() 兩個函數轉義“\n”,“\r”。其實很多phper隻知道轉義\n,\r而不知原因,在mysql解析\n,\r時把它們當成空格處理,筆者測試驗證過,這裡就不貼代碼了。

(2)“and”,“or”,“\”,“#”,“– ”

邏輯關鍵可以組合很多注入代碼;mysql注悉則把固有sql代碼後面的字元全部給注悉掉進而讓注入後的sql語句能正常運作;“\”也是能組合很多注入字元\x00,\x1a。

ps:sql解析“#”,“– ”是大多數mysql防注入代碼沒有考慮到的,也是很多phper忽略。還有因為一些phper給參數指派的時候會有用“-”來隔開,是以筆者建議不要這樣寫參數,當然也可以再過濾參數的時候“– ”(注意有空格的,沒空格不解析為注悉)當一個整體過濾而不是過濾“-” ,這樣就避免過多過濾參數。

(3)“null”,“%”,“_”

這幾個不能獨立,都不要在特定情況下,比如通配字元“%,_”都要在mysql like子句的前提下。是以“%”,“_”的過濾一般在搜尋相關才過濾,不能把它們納入通常過濾隊列,因為有些如郵箱就可以有”_”字元

(4)關鍵字“select|insert|update|delete|*|union|join|into|load_file|outfile”

也許你會問怎麼這些重要關鍵字卻優先級這麼低。筆者想說的是因為這些關鍵字在沒有“’”,“””,“ ”,“and”,“or”等情況下購不成傷害。換句話說這些關鍵字不夠“獨立”,“依賴性”特别大。當然優先級低,不代表不要過濾。

3.3.3防注入代碼。

(1)參數是數字直接用intval()函數

注意:現在很多網上流行的防注入代碼都隻是隻是用addslashes()、mysql_escape_string()、mysql_real_escape_string()或三者任意組合過濾,但phper以為過濾了,一不小心一樣有漏洞,那就是在參數為數字的時候:

$id = addslashes($_POST[‘id’]); //正确是$id = intval($_POST[‘id’]);  
$sql =” select * from phpben.com where id =$id”;  
$sql =” select * from phpben.com where id =1 or 1=1″;
           

對比容易發現,post過來的資料通過addslashes過濾後的确很多注入已經不起作用,但是$id并沒有intval,導緻漏洞的存在,這是個小細節,不小心則導緻漏洞。

(2)對于非文本參數的過濾

文本參數是名額題、留言、内容等可能有“’”,“’”等内容,過濾時不可能全部轉義或代替。

但非文本資料可以。

function _str_replace($str )  
{  
     $str = str_replace(” “,””,$str);  
     $str = str_replace(“\n”,””,$str);  
     $str = str_replace(“\r”,””,$str);  
     $str = str_replace(“‘”,””,$str);  
     $str = str_replace(‘”‘,””,$str);  
     $str = str_replace(“or”,””,$str);  
     $str = str_replace(“and”,””,$str);  
     $str = str_replace(“#”,””,$str);  
     $str = str_replace(“\\”,””,$str);  
     $str = str_replace(“– “,””,$str);  
     $str = str_replace(“null”,””,$str);  
     $str = str_replace(“%”,””,$str);  
     //$str = str_replace(“_”,””,$str);  
     $str = str_replace(“>”,””,$str);  
     $str = str_replace(“<“,””,$str);  
     $str = str_replace(“=”,””,$str);  
     $str = str_replace(“char”,””,$str);  
     $str = str_replace(“declare”,””,$str);  
     $str = str_replace(“select”,””,$str);  
     $str = str_replace(“create”,””,$str);  
     $str = str_replace(“delete”,””,$str);  
     $str = str_replace(“insert”,””,$str);  
     $str = str_replace(“execute”,””,$str);  
     $str = str_replace(“update”,””,$str);  
     $str = str_replace(“count”,””,$str);  
     return $str;  
}
           

ps:還有一些從清單頁操作過來的一般href是”phpben.php?action=delete&id=1”,這時候就注意啦,_str_replace($_GET[‘action’])會把參數過濾掉,筆者一般不用敏感關鍵作為參數,比如delete會寫成del,update寫成edite,隻要不影響可讀性即可;

還有上面代碼過濾下劃線的筆者注悉掉了,因為有些參數可以使用下劃線,自己權衡怎麼過濾;

有些代碼把關鍵字當重點過濾對象,其實關鍵字的str_replace很容易“蒙過關”,str_replace(“ininsertsert”)過濾後的字元還是insert,是以關鍵的是其他字元而不是mysql關鍵字。

(3)文本資料防注入代碼。

文本參數是名額題、留言、内容等這些資料不可能也用str_replace()過濾掉,這樣就導緻資料的完整性,這是很不可取的。

代碼:

function no_inject($str)  

{  
         if(is_array($str))  
         {  
                   foreach($str as $key =>$val)  
                   {  
                           $str[$key]=no_inject($val);  
                   }  
         }else  
         {  
                   $str = str_replace(” “,” “,$str);  
                   $str = str_replace(“\\”,”\”,$str);  
                   $str = str_replace(“‘”,”‘”,$str);  
                   $str = str_replace(‘”‘,”””,$str);  
                   $str = str_replace(“or”,”or”,$str);  
                   $str = str_replace(“and”,”and”,$str);  
                   $str = str_replace(“#”,”#”,$str);  
                   $str = str_replace(“– “,”– “,$str);  
                   $str = str_replace(“null”,”null”,$str);  
                   $str = str_replace(“%”,”%”,$str);  
                   //$str = str_replace(“_”,””,$str);  
                   $str = str_replace(“>”,”>”,$str);  
                   $str = str_replace(“<“,”<“,$str);  
                   $str = str_replace(“=”,”=”,$str);  
                   $str = str_replace(“char”,”char”,$str);    
                   $str = str_replace(“declare”,”declare”,$str);  
                   $str = str_replace(“select”,”select”,$str);  
                  $str = str_replace(“create”,”create”,$str);  
                  $str = str_replace(“delete”,”delete”,$str);  
                  $str = str_replace(“insert”,”insert”,$str);  
                 $str = str_replace(“execute”,”execute”,$str);  
                 $str = str_replace(“update”,”update”,$str);  
                 $str = str_replace(“count”,”count”,$str);  
         }  
    return $str;  
}
           

(4)當然還有其他與addslashes、mysql_escape_string結合的代碼。

防注入的代碼其實來來去去都是那些組合,然後根據自己程式代碼變通,筆者這些代碼也是沒考慮全的,不如cookes、session、request都沒全過濾。重要是知道其中原理,為什麼過濾這些字元,字元有什麼危害。

4、防止xss攻擊

XSS:cross site script 跨站腳本,為什麼不叫css,為了不和div+css混淆。

4.1Xss攻擊過程:

(1)發現A站有xss漏洞。

(2)注入xss漏洞代碼。可以js代碼,木馬,腳本檔案等等,這裡假如A站的benwin.php這個檔案有漏洞。

(3)通過一些方法欺騙A站相關人員運作benwin.php,其中利用相關人員一些會員資訊如cookies,權限等。

相關人員:

管理者(如貼吧版主),管理者一般有一定權限。目的是借用管理者的權限或進行提權,添或加管理者,或添加後門,或上傳木馬,或進一步滲透等相關操作。

A站會員:會員運作A站的benwin.php。目的一般是偷取會員在A站的資訊資料。

方法:

1)       在A站發誘騙相關人到benwin.php的資訊,比如網址,這種是本地誘騙

2)       在其他網站發誘騙資訊或者發郵件等等資訊。

一般通過僞裝網址騙取A站相關人員點選進benwin.php

(4)第三步一般已經是一次xss攻擊,如果要更進一步攻擊,那不斷重複執行(2)、(3)步以達到目的。

簡單例說xss攻擊

代碼:benwin.php檔案

<html>  
<head>  
<title>簡單xss攻擊例子</title></head>  
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″>  
<dody>  
<form action=”phpben.com?user_name=<?php echo $user_name; ?>”>  
<input type=”submit” value=”送出” >  
</form>  
</body>  
</html>
           

更多請參考:PHP安全,PHP

繼續閱讀