创建博客 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

isblog主页

爱是一切的原动力!

 
 
 

日志

 
 

PHP在Linux系统中执行系统命令  

2010-05-02 11:34:20|  分类: PHP学习 |  标签: |举报 |字号 订阅

在PHP中以root身份运行外部命令[转自linuxaid]    
    孤狼   发表于   2001-10-11   10:15:41   PHP   ←返回版面                   [快速返回]    
   
  Hunte   2001年4月15日  
   
  在PHP中运行只有root用户才可以运行的外部程序,一直是个老问题,用常规的办法很难实现。这是因为一般情况下,PHP是作为APACHE的一个模块 的,也就是说,PHP是APACHE的一部分,而APACHE除了suEXEC机制外,是不能以不同的用户ID来执行命令的,但suEXEC机制只能 CGI有效。  
   
  网上曾经有一篇文章,说用调用"su   -   -c   COMMAND"可以实现,但经过多次试验,发现不行,因为su命令必须在STDIN上输入root的密码。  
   
  怎么办?用常规的方法难以奏效,只能再想其它的方法了。成功的关键在于能有一个可以切换用户ID但又可以在命令上输入密码(或不用输入密码)的工具。有这样的工具吗?有,它就是super。  
   
  下面就具体说说如何来做?  
   
  要注意的是,安装和配置super,都要以root身份来进行。  
   
  第一步,切换到root下  
   
  第二步,安装super  
  先到ftp://ftp.mdtsoft.com/pub/super下载super-3.14.0-1.i386.rpm。这是一个RPM文件,其它包括了两个工具:setuid和super,以及它们的文档和man手册。用下面的命令将它安装到系统中:  
  %   rpm   -Uvh   super-3.14.0-1.i386.rpm  
  你还可以用这个命令来查看这个RPM中的文件:  
  %   rpm   -qpl   super-3.14.0-1.i386.rpm  
  从结果可以看到,两个工具都将被安装到/bin目录下。  
   
  第三步,配置super  
  super的配置文件是/etc/super.tab。这是一个文本文件,格式也比较复杂。不过,我们这里只要很简单的加上几行就可以了。至于详细的说明,可以通过man   super.tab来查看。  
  假设运行Apache的用户是nobody,我们欲通过super来增加系统用户(调用useradd命令),那么我们只要在super.tab文件中加入以下这行:  
  auser   /sbin/useradd   nobody,hunte  
  第一段是super能够识别的命令的别名;第二段是该别名所对应的系统命令的全路径;第三段是可以运行该命令的用户列表,用逗号分隔。这里除了nobody外,还一个叫hunte的普通用户,是用于下面的测试。当然,你应该用你系统中有的任意一个普通用户。  
  至此,super的配置就算好了。  
   
  第四步,测试  
  以第三步中指定的非nobody用户登录,运行:  
  %   /bin/super   auser   testuser  
  如果前面的配置没什么错误的话,用户testuser应该是成功地创建了。可以用:  
  %   cat   /etc/passwd   |   grep   testuser  
  命令来验证一下。  
   
  第五步,在PHP中调用该命令  
  下面是PHP代码:  
  <?  
  if   ($username)  
  {  
  //应该检查新用户是否已经存在  
  echo   '正在创建用户<$username>...';  
  system(escapeshellcmd("/bin/super   auser   $username"));  
  }  
  ?>    
  使用super,使得在PHP中以root身份运行外部命令不再是难事。试试看吧。  
  测试环境:RedHat   Linux   7.0   (Kernel   2.4.3)   +   Apache   1.3.9   +   PHP   4.0.4pl1  
   
  chpasswd.cgi是一个修改linux用户密码的cgi程序,弥补更改密码需要两次输入密码的缺陷 


php执行linux系统命令的几个函数:

system函数
说明:执行外部程序并显示输出资料。
语法:string system(string command, int [return_var]);
返回值: 字符串

详细介绍:
本函数就像是 C 语中的函数 system(),用来执行指令,并输出结果。若是 return_var 参数存在,则执行 command 之后的状态会填入 return_var 中。同样值得注意的是若需要处理用户输入的资料,而又要防止用户耍花招破解系统,则可以使用 EscapeShellCmd()。若 PHP 以模块式的执行,本函数会在每一行输出后自动更新 Web 服务器的输出缓冲暂存区。若需要完整的返回字符串,且不想经过不必要的其它中间的输出界面,可以使用 PassThru()。

实例代码:

< ?php
$last_line = system('ls', $retval);
echo 'Last line of the output: ' . $last_line;
echo '<hr />Return value: ' . $retval;
?>

exec函数
说明:执行外部程序。
语法:string exec(string command, string [array], int [return_var]);
返回值: 字符串

详细介绍:
本函数执行输入 command 的外部程序或外部指令。它的返回字符串只是外部程序执行后返回的最后一行;若需要完整的返回字符串,可以使用 PassThru() 这个函数。

要是参数 array 存在,command 会将 array 加到参数中执行,若不欲 array 被处理,可以在执行 exec() 之前呼叫 unset()。若是 return_var 跟 array 二个参数都存在,则执行 command 之后的状态会填入 return_var 中。

值得注意的是若需要处理使用者输入的资料,而又要防止使用者耍花招破解系统,则可以使用 EscapeShellCmd()。

实例代码:

< ?php
echo exec('whoami');
?>

popen函数
说明:打开文件。
语法:int popen(string command, string mode);
返回值: 整数

详细介绍:
本函数执行指令开档,而该文件是用管道方式处理的文件。用本函数打开的文件只能是单向的 (只能读或只能写),而且一定要用 pclose() 关闭。在文件操作上可使用 fgets()、fgetss() 与 fputs()。若是开档发生错误,返回 false 值。

实例代码:

< ?php
$fp = popen( "/bin/ls", "r" );
?>

但是执行的命令要涉及权限,举个例子:使用php执行"service network restart"命令,重启网络。apache是以一个一般用户执行的,我计算机使用apache用户,根本没有执行这个命令的权限。
解决办法:
1、绕开这个限制:
首先写个c程序,例如/usr/local/a.c
#include <stdlib.h>
main()
{
uid_t uid,euid;

uid = getuid();
euid = geteuid();

if (setreuid(euid,uid))
perror("setreuid");

system("service network restart");
}
编译文件gcc -oa -Wall a.c,这时会在当前目录下生成程序a
程序的属主chmod u+s ./a
apache的uid 为48。调用setreuid后将有效用户id和实际用户id互换了。(必须在chmod u+s生效的情况下) 使apache当前的uid为0这样就能执行root命令了。

php程序调用a:
<?php
function RestartNetwork() {
system("/usr/local/a");
}
RestartNetwork();
?>
2、编辑/etc/sudoers文件,或者使用visudo命令
Host_Alias SERVER = localhost,你的主机名
User_Alias USERLIST = apache用户名
Cmnd_Alias COMMANDLIST = /sbin/service #添加想让apache用户可以使用的命令

#Defaults    requiretty
......
修改完成后,保存/etc/sudoers只能是0440权限
然后,php就可以使用service命令
<?php
system("sudo service network restart");
?>


/etc/sudoers配置范例以及解释:
 
示例

下面是sudoers条目的示例.显然,一些示例有点不自然.首先,我们定义别名:
# User alias specification
User_Alias FULLTIMERS = millert, mikef, dowdy
User_Alias PARTTIMERS = bostley, jwfox, crawl
User_Alias WEBMASTERS = will, wendy, wim

# Runas alias specification
Runas_Alias OP = root, operator
Runas_Alias DB = oracle, sybase

# Host alias specification
Host_Alias SPARC = bigtime, eclipse, moet, anchor :\
SGI = grolsch, dandelion, black :\
ALPHA = widget, thalamus, foobar :\
HPPA = boa, nag, python
Host_Alias CUNETS = 128.138.0.0/255.255.0.0
Host_Alias CSNETS = 128.138.243.0, 128.138.204.0/24, 128.138.242.0
Host_Alias SERVERS = master, mail, www, ns
Host_Alias CDROM = orion, perseus, hercules

# Cmnd alias specification
Cmnd_Alias DUMPS = /usr/bin/mt, /usr/sbin/dump, /usr/sbin/rdump,\
/usr/sbin/restore, /usr/sbin/rrestore
Cmnd_Alias KILL = /usr/bin/kill
Cmnd_Alias PRINTING = /usr/sbin/lpc, /usr/bin/lprm
Cmnd_Alias SHUTDOWN = /usr/sbin/shutdown
Cmnd_Alias HALT = /usr/sbin/halt, /usr/sbin/fasthalt
Cmnd_Alias REBOOT = /usr/sbin/reboot, /usr/sbin/fastboot
Cmnd_Alias SHELLS = /usr/bin/sh, /usr/bin/csh, /usr/bin/ksh, \
/usr/local/bin/tcsh, /usr/bin/rsh, \
/usr/local/bin/zsh
Cmnd_Alias SU = /usr/bin/su
接 下来我们改写了一些编译进的默认值.我们希望sudo在所有情况下使用auth设施在系统日志中作记录.我们不想总是输出初次使用sudo的提示,并且用 户millert使用sudo不需要口令.另外,我们为SERVERS Host_Alias中的主机维护一个附加的本地日志文件.并且由于这个日志需要保存许多年,我们要在日志的每一行中加入年份信息.
# Override built in defaults
Defaults syslog=auth
Defaults:FULLTIMERS !lecture
Defaults:millert !authenticate
Defaults@SERVERS log_year, logfile=/var/log/sudo.log
实际上决定谁能运行什么的是用户说明部分:
root ALL = (ALL) ALL
%wheel ALL = (ALL) ALL
我们让root和wheel组的成员可以在任何主机上以任何人的身份运行任何命令.
FULLTIMERS ALL = NOPASSWD: ALL
专职系统管理员(millert,mikef和dowdy)可以在任何主机上执行任何命令而不需要进行身份验证.
PARTTIMERS ALL = ALL
兼职系统管理员(bostley,jwfox和crawl)可以在任何主机上运行任何命令,但他们首先必须进行身份验证(因为这个条目没有NOPASSWD标签).
jack CSNETS = ALL
用 户jack可以在CSNETS别名(网络128.138.243.0,128.138.204.0和128.138.242.0)中的机器上运行任何命 令.在这些网络中,只有128.138.204.0明确的给出了掩码(用CIDR方式),指明这是个C类网.对CSNETS中的其他网络来说,如果本地主 机的掩码匹配的话将使用本地主机的掩码.
lisa CUNETS = ALL
用户lisa可以在CUNETS别名(B类网络128.138.0.0)中的任何主机上执行任何命令.
operator ALL = DUMPS, KILL, PRINTING, SHUTDOWN, HALT, REBOOT,\
/usr/oper/bin/
用户operator可以执行的命令被限制在一个简单的maintenance中.这里就是和备份,杀死进程,打印系统,关闭系统和任何在/usr/oper/bin/中的命令.
joe ALL = /usr/bin/su operator
用户joe只能su成operator.
pete HPPA = /usr/bin/passwd [A-z]*, !/usr/bin/passwd root
用户pete可以修改HPPA上除了root以外任何用户的口令.注意,这里人为passwd不会在命令行上使用多个用户名.
bob SPARC = (OP) ALL : SGI = (OP) ALL
用户可以在SPARC和SGI上以任何在OP Runas_Alias中列出的用户(root和operator)的身份运行任何命令.
jim +biglab = ALL
用户jim可以在biglab网络用户组中的机器上运行任何命令.因为biglab前面加上了前缀"+",所以sudo知道这是一个网络用户组.
+secretaries ALL = PRINTING, /usr/bin/adduser, /usr/bin/rmuser
网络用户组secretaries中的用户需要帮助管理打印机和进行增删用户的工作,所以需要允许他们在任何机器上执行这些命令.
fred ALL = (DB) NOPASSWD: ALL
用户fred可以在DB Runas_Alias (oracle 或 sybase)上以任何人的身份执行任何命令而不需要提供口令.
john ALPHA = /usr/bin/su [!-]*, !/usr/bin/su *root*
用户john可以在ALPHA上su成除root的任何人,但是不允许他给su加上任何标志执行.
jen ALL, !SERVERS = ALL
用户jen可以在除了SERVERS Host_Alias (master, mail, www 和 ns)以外的任何机器上执行任何命令.
jill SERVERS = /usr/bin/, !SU, !SHELLS
用户jill可以在任何SERVERS Host_Alias中的机器上运行/usr/bin/下除了属于SU和SHELLS Cmnd_Aliases外的任何命令.
steve CSNETS = (operator) /usr/local/op_commands/
用户steve可以在任何CSNETS中的机器上执行/usr/local/op_commands/中的所有命令,但是只能以operator的身份.
matt valkyrie = KILL
用户matt应该可以在他自己的工作站valkyrie上杀死挂起的进程.
WEBMASTERS www = (www) ALL, (root) /usr/bin/su www
任何在WEBMASTERS User_Alias (will, wendy,和 wim)中的用户都能够在主机www上以www(网页的所有者)的身份执行任何命令,或者简单的su成www.
ALL CDROM = NOPASSWD: /sbin/umount /CDROM,\
/sbin/mount -o nosuid\,nodev /dev/cd0a /CDROM
所 有的用户都应该能够在 CDROM Host_Alias (orion, perseus, hercules)中的主机上mount和unmount光盘驱动器而不需要输入口令.对用户来说敲入这么长的命令有些冗长而乏味,所以把它封装在一个 shell脚本中是一个最佳的选择.


安全要点

使用"!"来从ALL中减去命令一般来说不会产生预期作用.用户可以简单的通过把希望执行的命令改名执行的法子来绕过限制.例如:
bill ALL = ALL, !SU, !SHELLS
这 并不会阻止bill执行SU和SHELLS中列出的命令.他只需要把这些命令改一个名字,或者从一个编辑器或者其他程序中escape到shell(译 注:原文是use a shell escape from an editor or other program)就可以运行了.所以这种类型的限制至少应该经过深思熟虑(并从策略上加强它).

CAVEATS

sudoers必需总是使用visudo命令来编辑,因为它会锁定文件并且进行语法检察.这强制sudoers摆脱语法错误,因为sudoers有语法错误时sudo是不会运行的.
当使用机器的网络用户组时(与用户相反),如果您在网络用户组中存贮了完整的主机名(这经常是事实),您需要让主机名像hostname命令的输出一样是完整的或者在sudoers中使用fqdn选项.


文件

/etc/sudoers 谁能作什么的一个列表
/etc/group 本地组文件
/etc/netgroup 网络组文件


 在玩C以前玩过一段时间的PHP,哪个时候需要用PHP来运行root命令,一直未果,直到有一天搜索到了super这个插件.

  随着玩C的日子多了.发现可以用C语言来包裹要运行的外部命令.实验了一下.成功了.

  不需要任何外部工具就可以实现用PHP执行root命令.

  我下面就把方法发布给大家,有需求用php来运行root命令的朋友可以不用发愁了.

  平台:Linux.实验命令iptables当前的目录是/var/www/html/http

  写程序的时候用root用户

  大家都知道iptables非root用户不能运行.

  首先写个C程序

  命名为:ipt.c

  #include<stdio.h>

  #include<stdlib.h>

  #include<sys/types.h>

  #include<unistd.h>

  intmain()

  {

  uid_tuid,euid;

  charcmd[1024];

  uid=getuid();

  euid=geteuid();

  printf("myuid:%u\n",getuid());//这里显示的是当前的uid可以注释掉.

  printf("myeuid:%u\n",geteuid());//这里显示的是当前的euid

  if(setreuid(euid,uid))//交换这两个id

  perror("setreuid");

  printf("aftersetreuiduid:%u\n",getuid());

  printf("afersertreuideuid:%u\n",geteuid());

  system("/sbin/iptables-L");//执行iptables-L命令

  return0;

  }

  [/CODE]

  编译该文件gcc-oipt-Wallipt.c

  在该路径下生成ipt这个可执行文件.

  如果现在用PHP网页调用该ipt的话,即使setreuid了也是不行的.

  接下来要做的是chmodu+s./ipt

  ls一下

  -rwsr-xr-x1rootroot5382Jul221:45ipt

  s位已经设置上了.

  再写一个php页面调用它.

  <?php

  echo'<pre>';

  $last_line=system('/var/www/html/http/ipt',$retval);

  echo'

  </pre>

  <hr/>Lastlineoftheoutput:'.$last_line.'

  <hr/>Returnvalue:'.$retval;

  ?>

  在浏览器中浏览.

  [color=Red]ChainINPUT(policyACCEPT)

  targetprotoptsourcedestination

  ChainFORWARD(policyDROP)

  targetprotoptsourcedestination

  ACCEPTall--anywhereanywherestateRELATED,ESTABLISHED

  ChainOUTPUT(policyACCEPT)

  targetprotoptsourcedestination[/color]

  [color=Blue]myuid:48

  myeuid:0

  aftersetreuiduid:0

  afersertreuideuid:48[/color]

  --------------------------------------------------------------------------------

  Lastlineoftheoutput:afersertreuideuid:48

  --------------------------------------------------------------------------------

  Returnvalue:0

  该命令执行成功..

  众所周知:apache的uid为48.调用setreuid后将有效用户id和实际用户id互换了.(必须在chmodu+s生效的情况下)使apache当前的uid为0这样就能执行root命令了。

  大家只需要更改C文件中的system所要执行的命令就可以实现自己的PHP执行root命令了.
  评论这张
 
阅读(912)| 评论(0)
|      
推荐 转载

历史上的今天

最近读者

热度

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2014