PHP Conference Japan 2024

escapeshellcmd

(PHP 4, PHP 5, PHP 7, PHP 8)

escapeshellcmd跳脫 Shell 的中繼字元

描述

escapeshellcmd(string $command): string

escapeshellcmd() 會跳脫字串中任何可能會被用來欺騙 Shell 命令執行任意命令的字元。這個函式應該用來確保任何來自使用者輸入的資料,在傳遞給 exec()system() 函式,或 反引號運算子之前都已經過跳脫。

下列字元會加上反斜線前綴:&#;`|*?~<>^()[]{}$\\x0A\xFF'" 只有在不成對時才會被跳脫。在 Windows 上,所有這些字元加上 %! 都會加上插入符號 (^) 前綴。

參數

command

將要被跳脫的命令。

回傳值

已跳脫的字串。

範例

範例 1:escapeshellcmd() 範例

<?php
// 我們在此故意允許任意數量的參數。
$command = './configure '.$_POST['configure_options'];

$escaped_command = escapeshellcmd($command);

system($escaped_command);
?>

注意事項

警告

escapeshellcmd() 應該用在整個命令字串上,而且它仍然允許攻擊者傳遞任意數量的參數。為了跳脫單一參數,應該改用 escapeshellarg()

警告

空格不會被 escapeshellcmd() 跳脫,這在 Windows 上對於像是 C:\Program Files\ProgramName\program.exe 的路徑可能會造成問題。可以使用以下程式碼片段來緩解這個問題

<?php
$cmd
= preg_replace('`(?<!^) `', '^ ', escapeshellcmd($cmd));

參見

新增筆記

使用者貢獻的筆記 7 筆筆記

9
nicholas at nicholaswilson dot me dot uk
13 年前
關於 echo 的使用有一個需要注意的怪癖。如果您想要執行一個從 STDIN 接收輸入的命令,您通常會這樣做

<?php $output = shell_exec("echo $input | /the/command"); ?>

不幸的是,這是一個 *壞主意*,並且會使您的腳本無法移植,在某些系統上會產生很難追蹤的錯誤。根據伺服器的設定方式,/bin/sh 要嘛會呼叫 /bin/bash,要嘛會呼叫 /bin/dash,而這些的 echo 版本非常不同。永遠不要使用 echo;請改用 printf,它是穩定的。您要如何針對 printf 跳脫?這樣做

<?php
$input
= '要*精確地*傳遞給命令的字串';
//只跳脫 PHP 解析器所需的內容;我們想要
//PHP 將資料儲存在緩衝區中的字串
//精確地傳遞到命令的 stdin 緩衝區。
$cmd = str_replace(array('\\', '%'), array('\\\\', '%%'), $input);
$cmd = escapeshellarg($cmd);

$output = shell_exec("printf $cmd | /path/to/command");
?>

對於偏執狂,這個嚴苛的測試驗證了 shell 跳脫和 printf 自己的跳脫都已正確處理。請安心使用!

<?php

$test
= 'stuff bash interprets, space: # & ; ` | * ? ~ < > ^ ( ) [ ] { } $ \ \x0A \xFF. \' " %'.PHP_EOL.
'stuff bash interprets, no space: #&;`|*?~<>^()[]{}$\\x0A\xFF.\'\"%'.PHP_EOL.
'stuff bash interprets, with leading backslash: \# \& \; \` \| \* \? \~ \< \> \^ \( \) \[ \] \{ \} \$ \\\ \\\x0A \\\xFF. \\\' \" \%'.PHP_EOL.
'printf codes: % \ (used to form %.0#-*+d, or \\ \a \b \f \n \r \t \v \" \? \062 \0062 \x032 \u0032 and \U00000032)';

echo
"These are the strings we are testing with:".PHP_EOL.$test.PHP_EOL;
$cmd = $test;
$cmd = str_replace(array('\\', '%'), array('\\\\', '%%'), $test);
$cmd = escapeshellarg($cmd);

echo
PHP_EOL."This is the output using the escaping mechanism given:".PHP_EOL;
echo `
printf $cmd | cat`.PHP_EOL;

echo
PHP_EOL."They should match exactly".PHP_EOL;
?>
0
carlos at wfmh dot org dot pl dot REMOVE dot COM
14 年前
請注意,它不會跳脫 ! (驚嘆號)。因此,如果您想為稍後在 shell 中使用的 printf() 命令(例如,透過貼上到主控台)進行處理,您需要跳脫所有的驚嘆號,否則 shell 會嘗試將 ! 作為歷史紀錄參考。以下方法應足夠:

<?php $scaped = str_replace('!', '\!', escapeshellarg( $str ) ); ?>
0
docey
19 年前
引用命令的主要原因是,它不會讓多個命令被連接起來。我不確定這是否是正確的語法,但請記住,這可能會造成一些嚴重的安全性漏洞。這裡有一種方法可以確切了解您要入侵的目標。

通常,Linux 上的任何使用者都可以檢視幾乎任何目錄,所以
ls / -als 將會列印 Linux 檔案系統中任何檔案的完整清單,包括其大小、安全性以及隱藏檔案。

現在,輸出只會讓 PHP 知道,使用者永遠無法檢視此資料,除非 PHP 腳本實際開始將其列印出來。就像 passtru 做的那樣!但是一個好的 PHP 程式設計師知道除非沒有其他可能,否則永遠不要使用 passtru。

但是,如果您可以將 ls 的輸出也從同一個命令列導向到網頁根目錄中的一個檔案會發生什麼事呢?大多數網頁伺服器仍然將其預設根目錄設定為 /var/www/,因此將其儲存在該處的文字檔案中以便稍後下載,而且您可以一邊喝咖啡,一邊檢查哪些檔案可以透過 PHP 安全模式讀取,然後簡單地使用 cp 命令將這些檔案複製到網頁根目錄,然後下載到您自己的硬碟。如果沒有檔案清單,您只能猜測要從哪裡複製!這比猜測 root 密碼更難。

因此,如果第一個命令被引號括起來,則由於語法錯誤,不可能附加另一個命令。想想看,一旦您獲得檔案系統上每個檔案的完整清單,您可以做哪些事情。包括透過 NFS 和其他方式掛載的檔案。安全性始於將門隱藏起來。

另一個讓網頁伺服器掛起的好命令是 "php <?php while(true){ exec('ls / -als'); }; ?>"。這會不斷在整個檔案系統上建立檔案清單,不僅讓硬碟忙碌,還讓記憶體和 CPU 也必須儲存傳回的清單。因此,請記住,並非所有從使用者接收的命令都可以盲目使用。

實際上,永遠不要接受來自外部來源的任何命令,只應執行經過驗證的內建預定義命令。
0
trisk at earthling dot net
19 年前
此函數無法如 php.net 範例中所示運作。

如果您像他們建議的那樣將編碼的檔案名稱放入雙引號中,那麼它會在檔案名稱中的某些字元(例如 & 符號)上中斷。

例如,如果您的檔案名稱為「foo & bar.jpg」,並且您對其使用此函數,那麼您的結果檔案名稱在加上雙引號後會產生這個,但找不到

"foo \& bar.jpg"

如果您需要一個包含空格的單一引數,則不要在加上雙引號的情況下使用此函數,請使用 escapeshellarg(),它會將整個字串以單引號括起來。

我不明白此特定函數的用途是什麼。我看不出它有任何用途,除非您將它傳遞到另一個函數並將空格「 」轉換為「\ 」,這樣您就可以直接在命令列上使用字串。
-2
ceejay at trashfactory dot de
18 年前
各位,我發現當 safe_mode 開啟時,將命令傳遞給 exec、system 或 popen 時,會強制執行 escapeshellarg 和 escapeshellcmd,這非常困難。

目前,我找不到任何可用的解決方案來傳遞像這樣的命令
cmd -arg1 -arg2 "<BLA varname=\"varvalue\" varname1=\"varvalue1\" />"

實際的情況是,arg2 的參數是一個看起來像帶有各種屬性設定的 HTML 標籤的字串,arg2 中字串的所有屬性都會被其中的空格分割。當 safe_mode 關閉時,這不會發生,因此一定是其中一個跳脫函數中斷了功能。

為了規避這個問題,我已經制定了一個臨時解決方案,它會動態建立一個腳本檔案(透過 fopen),其中只包含帶有引數的整個命令,然後執行該腳本檔案。我不喜歡這個解決方案,但在另一方面,無法輕易地在該伺服器上關閉 safe_mode。
-3
abennett at clarku dot edu
18 年前
我有一個 PHP 腳本,需要透過 exec 將使用者名稱和密碼傳遞給 Perl 腳本。問題是有效的密碼字元被跳脫了...

這是我寫的一個小 Perl 函數來修正它。

sub unescape_string {
my $string = shift;
# 所有這些內插的正規表示式都很慢,所以如果字串中沒有
# 反斜線,就不要管它
# index() 比正規表示式快
if ( ! index($string,'\\\\') ) {
return $string;
}
my @characters = ('#', '&', ';', '`', '|', '*', '?', '~', '<', '>', '^', '(', ')',
'[', ']', '{', '}', '$', '\\', ',', ' ', '\x0A', '\xFF' );
my $character;
foreach $character (@characters) {
$character = quotemeta($character);
my $pattern = "\\\\(" . $character . ")";
$string =~ s/$pattern/$1/g;
}
return $string;
}

希望這對您有幫助。
-3
Leon
17 年前
這個函數很棒,除非您需要合法地使用跳脫字元作為您命令的一部分。下面的程式碼會將以單引號括起來的命令部分保留下來,但會跳脫其餘部分,例如

"echo Never use the '<blink>' tag ; cat /etc/passwd"
變成
"echo Never use the '<blink>' tag \; cat /etc/passwd"
而不是
"echo Never use the '\<blink\>' tag \; cat /etc/passwd"

也就是說,我們確實希望跳脫「;」,而不是 HTML 標籤。我確實需要下面的程式碼才能正確且安全地執行外部 ImageMagick 的「convert」命令...

<?php

// 跳脫整個字串
$cmdQ = escapeshellcmd($cmd);

// 建立引號部分的陣列,並跳脫相同的
preg_match_all('/\'[^\']+\'/', $cmd, $matches);
$matches = current($matches);
$quoted = array();
foreach(
$matches as $match )
$quoted[escapeshellcmd($match)] = $match;

// 將以單引號括起來的部分取代為原始內容
foreach( $quoted as $search => $replace )
$cmdQ = str_replace( $search, $replace, $cmdQ );

return
$cmdQ;

?>
To Top