PHP 執行外部程式
說明 PHP 如何執行外部程式,以及可能造成無法成功執行的原因,包含 SELinux、php.ini 設定、Sudoer、環境變數路徑和參數問題。在 PHP 中可以使用 shell_exec()
、exec()
、system()
等函式執行外部程式,但是要注意有些設定可能會限制而無法執行,這裡以 Linux 系統為主要討論,要注意的地方如下:
SELinux
這是加強系統安全的模組,但沒有特別去設定的話時常會無法讓程式正常運作,而且在 PHP 端沒有任何訊息,例如執行 shell_exec("ls")
,沒有任何訊息。不過如果被 SELinux 封鎖的話,在 /var/log/messages
裡面會有類似以下的訊息:
1 | Aug 10 11:24:48 pc111168 setroubleshoot: SELinux is preventing the sudo from using potentially mislabeled files /bin/ls (ls_exec_t). For complete SELinux messages. run sealert -l 03c866e3-216b-4f2b-83a9-cb83123af7fe |
你可以輸入以下指令來查詢目前針對httpd的設定
1 | /usr/sbin/getsebool -a | grep httpd |
例如會輸出
1 | allow_httpd_anon_write -- off |
你可以依據需求更改,根據網路上找到的資料,設定 httpd_ssi_exec
為 on 可以解決掉這問題,輸入
1 | setsebool -P httpd_ssi_exec 1 |
停用SELinux
除此之外,你也可以直接關閉 SELinux,修改 SELinux 設定檔,不同系統可能會不同位置,以我用到的 CentOS 為例是在 /etc/selinux/config
,有的系統在 /etc/sysconfig/selinux
1 | vi /etc/selinux/config |
將 SELINUX
的值改成 disabled
1 | SELINUX=disabled |
然後還要重開機
。
暫時停用
或者也可以暫時的停用啟用 SELinux,不同系統有不同方式,Fedora 和 Red Hat 使用 setenforce
開關。
停用
1 | setenforce 0 |
啟用
1 | setenforce 1 |
有些則用以下方式
停用
1 | echo 0 >/selinux/enforce |
啟用
1 | echo 1 >/selinux/enforce |
php.ini
safe_mode
另外 php.ini 的 safe_mode
如果被啟用的話,shell_exec()
就無法使用,而 exec()
則只能執行在 safe_mode_exec_dir
下的檔案。
disable_functions
有些版本的 php.ini 會預設將 shell_exec()
等函式放到這下面,顧名思義在這之下的函式就無法使用,確認 shell_exec()
等沒有被設定在這項目之下。
Sudoer
網頁的執行身分通常是 apache,你可以在 PHP 中使用以下方式查看
1 |
|
所以 PHP 當然只能執行 apache 有權限執行的指令,如同 Linux 一般系統帳號的權限設定一樣,要使用 sudoer 設定 apache 允許執行的指令,你可以使用以下指令來編輯
1 | visudo |
或
1 | vi /etc/sudoers |
apache 沒有密碼同時避免輸入密碼的過程,使用 NOPASSWD
的屬性,例如我們給 root 權限來測試如下
1 | apache ALL=(ALL) NOPASSWD: /sbin/ifconfig |
由於 apache 沒有 tty,註解 requiretty 設定
1 | #requiretty |
最後再 PHP 中可使用 sudo
取得權限,例如
1 |
|
環境變數
由於 PHP 執行外部程式的環境變數不太一樣,所以有時候必須要使用絕對路徑才能正確執行程式。
escapeshellarg
如果呼叫的程式會帶入動態的參數,應該要使用 escapeshellarg()
函式來檢驗,尤其是參數會由使用者輸入的內容來決定的時候,例如
1 |
|
其他
有些程式雖然在 ssh 執行的時候是正常有輸出訊息,但是在 PHP 執行外部程式就是沒有,例如直接輸入 /usr/bin/java
會有使用說明,但 PHP 端看不到,不過直接執行 jar 程式還是可以正常運作,這是 shell_exec()
只回傳 stdout 的資料,stderr 看不到,要讓 shell_exec()
都看的到可以在指令後面加上 2>&1 1> /dev/null
,例如
1 |
|