PHP編碼安全:變量安全
變量覆蓋常常被惡意攻擊者用來跳過正常的業(yè)務(wù)邏輯,越過權(quán)限限制,惡意攻擊系統(tǒng),嚴(yán)重時(shí)將造成系統(tǒng)癱瘓。
1、全局變量覆蓋
當(dāng)register_globals全局變量設(shè)置開啟時(shí),傳遞過來的值會被直接注冊為全局變量而直接使用,這會造成全局變量覆蓋。
如果通過$GLOBALS從瀏覽器動態(tài)獲取變量,也會發(fā)生變量覆蓋的情況。為了方便理解,引用全局變量配置的例子進(jìn)行介紹。
- <form name="login" action="LoginUrl" method="POST">
- <input type=“text” name="username">
- <input type=“password” name="password">
- <input type=“submit” value="login">
- </form>
通過$GLOBALS獲取瀏覽器提交的變量。
- <?php
- foreach($_REQUEST as $param=>$value) {
- $GLOBALS[$param]=>$value; // 使用$GLOBALS造成變量覆蓋
- }
- if(authenticated_user()) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
攻擊者在請求中構(gòu)造authorized=true,無須認(rèn)證用戶名和密碼就可以直接設(shè)置authorized的值為true,從而跳過認(rèn)證進(jìn)入登錄狀態(tài)。
為了避免全局變量覆蓋的發(fā)生,研發(fā)人員不應(yīng)該使用上面的方式從客戶端接收動態(tài)變量將其放入全局的$GLOBALS中。以下是修復(fù)后的代碼。
- <?php
- $username=$_POST['username'];
- $password=$_POST['password'];
- if(authenticated_user($username,$password)) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
2、動態(tài)變量覆蓋
PHP動態(tài)變量是指一個(gè)變量的變量名可以動態(tài)地設(shè)置和使用,一個(gè)變量獲取另一個(gè)變量的值作為這個(gè)變量的變量名。以下是動態(tài)變量示例。
- <?php
- $Bar="a";
- $Foo="Bar";
- $World="Foo";
- $Hello="World";
- $a="Hello";
- echo $a; // 輸出Hello
- echo $$a; // 輸出World
- echo $$$a; // 輸出Foo
- echo $$$$a; // 輸出Bar
- echo $$$$$a; // 輸出a
- echo $$$$$$a; // 輸出Hello
- echo $$$$$$$a; // 輸出World
研發(fā)人員在平時(shí)研發(fā)過程中多多少少會使用一些動態(tài)變量,然而使用不當(dāng)將會造成變量覆蓋,所以應(yīng)該盡量避免使用PHP的動態(tài)變量。
以下代碼示例中的動態(tài)變量就屬于使用不當(dāng)?shù)那闆r。
- <?php
- foreach($_POST as $key=>$value) {
- $$key=$value; // 造成動態(tài)變量覆蓋
- }
- if(authenticated_user()) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
- ?>
當(dāng)用戶提交的參數(shù)中包含authorized=true時(shí),在執(zhí)行authenticated_user()步驟之前,authorized的值已經(jīng)被設(shè)置為true,因此用戶在無須通過校驗(yàn)的情況下即可直接向下執(zhí)行,繞過了校驗(yàn)邏輯,造成任意越權(quán)訪問的后果。
為了避免全局變量覆蓋的發(fā)生,應(yīng)盡量不使用動態(tài)變量接收客戶端參數(shù)。以下是修復(fù)后的代碼。
- <?php
- $username=$_POST['username'];
- $password=$_POST['password'];
- if(authenticated_user($username,$password)) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
3、函數(shù)extract()變量覆蓋
extract()函數(shù)的作用是從數(shù)組中導(dǎo)入變量到當(dāng)前符號表中,檢查每個(gè)鍵是否是有效的變量名。它還檢查與符號表中現(xiàn)有變量是否沖突。為了防止發(fā)生變量覆蓋,在使用的時(shí)候需要將flags設(shè)置為EXTR_SKIP,以免將已有變量覆蓋。
- <?php
- extract($_REQUEST ); // 使用extract造成變量覆蓋
- if(authenticated_user()) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
- ?>
當(dāng)用戶提交的參數(shù)中包含authorized=true時(shí),在執(zhí)行authenticated_user()步驟之前,extract()函數(shù)從$_REQUEST中解析到authorized并設(shè)置全局變量,它的值被設(shè)置為true。此時(shí),用戶在無須通過校驗(yàn)的情況下可直接向下執(zhí)行,繞過了校驗(yàn)邏輯,造成任意越權(quán)訪問。
為了避免全局變量覆蓋的發(fā)生,應(yīng)盡量不使用extract()函數(shù)接收客戶端參數(shù)。下面是修復(fù)后的代碼。
- <?php
- $username=$_POST['username'];
- $password=$_POST['password'];
- if(authenticated_user($username,$password)) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
4、函數(shù)import_request_variables()變量覆蓋
import_request_variables()函數(shù)的作用是導(dǎo)入GET/POST/Cookie變量進(jìn)入全局范圍。如果在PHP配置中禁用了register_globals,但是又希望導(dǎo)入一些全局變量,可能會用到import_request_variables()函數(shù)。
- <?php
- import_request_variables("gp"); // 導(dǎo)入GET和POST中的變量造成變量覆蓋
- if(authenticated_user()) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
- ?>
當(dāng)用戶提交的參數(shù)中包含authorized=true時(shí),在執(zhí)行authenticated_user()步驟之前,import_request_variables解析GET或POST中包含的authorized參數(shù),并且設(shè)置為true。此時(shí),用戶在無須通過校驗(yàn)的情況下可直接向下執(zhí)行,繞過了校驗(yàn)邏輯,造成任意越權(quán)訪問。
為了避免全局變量覆蓋的發(fā)生,應(yīng)盡量不使用上述方式接收客戶端參數(shù)。以下是修復(fù)后的代碼。
- <?php
- $username=$_POST['username'];
- $password=$_POST['password'];
- if(authenticated_user($username,$password)) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
5、函數(shù)parse_str()變量覆蓋
parse_str()函數(shù)用于解析客戶端以x-www-form-urlencoded編碼格式的字符串到PHP變量中。該函數(shù)有指定輸出變量和不指定輸出變量兩種使用方式。
以下示例是parse_str()的兩種使用方式。
- <?php
- $str="first=value&arr[]=foo+bar&arr[]=baz";
- // 第一種:當(dāng)指定輸出變量時(shí)
- parse_str($str,$output);
- echo $output['first']; // value
- echo $output['arr'][0]; // foo bar
- echo $output['arr'][1]; // baz
- // 第一種:當(dāng)不指定輸出變量時(shí)
- parse_str($str);
- echo $first; // value
- echo $arr[0]; // foo bar
- echo $arr[1]; // baz
- ?>
在不指定輸出變量的情況下,極易出現(xiàn)變量覆蓋,影響正常業(yè)務(wù)邏輯,例如以下形式。
- <?php
- parse_str($GLOBALS['HTTP_RAW_POST_DATA']); //獲取POST中的變量造成變量覆蓋
- if(authenticated_user()) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }
- ?>
當(dāng)用戶在提交的參數(shù)中直接提交authorized=true時(shí),parse_str()函數(shù)通過解析POST中的authorized并且將值設(shè)置為true。此時(shí),無須執(zhí)行if條件內(nèi)部語句即可將authorized的值設(shè)置為true,就跳過了用戶驗(yàn)證邏輯,造成任意登錄。
為了避免全局變量覆蓋的發(fā)生,應(yīng)盡量使用指定輸出變量的方式。以下是修復(fù)后的代碼。
- <?php
- parse_str($_POST,$output);
- if(authenticated_user($output['username'],$output['password'])) { // 認(rèn)證用戶是否登錄
- $authorized=true;
- }