从捕获的PHPwebshell学习PHP的调试与免杀

PHPStorm环境配置

PHP解释器

首先可以先配一个PHP环境,这样省的以后写代码还得启动个PHPStudy了

image-20220806150343953

选择到php.exe版本自选,我这里用最新的7.3.4

image-20220806150426743

之后就可以直接点击浏览器图标直接访问这个PHP了

Debug

启动一个phpinfo,并把内容直接复制,粘贴到https://xdebug.org/wizard.php这里

会自动解析PHP版本,并提供合适的dll供你下载

复制到你PHP某个版本的ext文件夹下

并在PHP.ini后添加:

image-20220806185959731

image-20220806151956654

image-20220806183656728

image-20220806183720402

样本分析

设备抓到了这个Webshell,我们也来分析分析,这个webshell该怎么用。

首先说一下,前面有点这个标题党,实际上似乎用不到调试,就能分析完成。

源代码如下:

一时间还是有点蒙逼的,粗略上看出来应该是做了代码混淆+部分加密来完成的。

去混淆

首先肯定是去掉那些恶心人的异或:

可以用echo xxx来完成:

即可获得这些东西的真实面目

image-20220807083803204

之后我们需要看一下,到底是用什么东西在传参。

分析传参

Webshell总是需要一个可以传参的点,打眼一看,似乎就只有:$_SERVER["HTTP_TOKEN"]

$_SERVER['HTTP_X_CSRF_TOKEN']可以接受外部的参数。我们一个函数一个函数的看。

Token

先分析$_SERVER["HTTP_TOKEN"]在这里都干了什么:

在一开始分析的时候,没看到这个header('Token:'.$_SESSION['token'])导致一直不知道一个随机出来的东西,该怎么传入一个固定的加密的值(如果不理解这句话,可以看加密函数那里的分析)

这个header('Token:'.$_SESSION['token'])会将生成的Token展示在Header的Token位置,此时你将获取生成好的截取了16位的MD5。

X-CSRF-Token

当我们已经了Token之后,就迎来了真正的传参处。因为上文已经分析了,只是说请求包里存在Token,那么这个Webshell会自动生成一个Token,所以实际上这个传参的具体并无大碍。

那只剩下这个可以获取外部的参数了,我们来看看这个请求头又做了什么呢?

这里拿出处理$v的片段:

至此,我们至少分析出传参的位置是X-CSRF-Token了,传递的参数至少要经过base64_encode()的结果

最后就是激动人心的代码执行部分:

没什么难度,还是老一套这个异或加密。

最终执行

new一个E对象出来,传递的$p为set_token函数处理后的结果,也就是说,最终我们需要看一下这个函数set_function

加密函数

首先,我并没有先试图分析这个加密的具体流程究竟是什么,而是先弄清传递的两个东西,都是些什么:

$v:用户可控的参数

$_SESSION['token']:随机生成的

我直接随机生成了个值,然后随便传递了个123进去,再用返回的值代替$v的值,结果返回123.这证明一件事:加密与解密同逻辑。

加密:

image-20220807085812270

解密:

image-20220807090224146

所以不需要我们专门写一个与之对应的解密函数,只需要调用这个就行了。

使用方式

首先请求包里携带Token:

image-20220807090540527

返回包会返回服务端生成的Token和COOKIE

接着把这个Token放到刚才写好的脚本当中,生成命令:

image-20220807090725456

请求包中,将COOKIE填充,删除Token这个头部,并添加X-CSRF-Token

image-20220807090856881

成功!

总结与反思

本次Webshell我觉得对方是想利用Token与X-CSRF-Token这种字母数字组合的头部来规避掉流量设备,可以说是非常精彩的一种思路,但是不足之处是我感觉X-CSRF-Token应该不会出现=这种的东西吧...其次就是能否利用第一次设置Token的时候,将一些东西传入进来,使传入进来的参数与现有的构成完整的一个代码。这样可能会阻止安全设备的拦截。第三就是在代码层次上尝试解决X-CSRF-Token不出现=的情况,应该来说不难实现...。

PHP的base64_decode()函数,去掉=也不会有任何问题,因此可以直接去掉这个特征=

二开

经过上面的总结后,我们开始解决其中提到的一些问题

规避eval关键字

规避关键字技术细节

我在这里不得不提一些问题:

1、为什么不拼接eval

2、为什么不使用assert

我先回答第二个问题:php官方在php7中更改了assert函数。在php7.0.29之后的版本不支持动态调用。

也就是说,他不能在通过拼接等方式来动态调用

接着我们来说一下为什么eval不能拼接,实际上答案就在php.net里写明

image-20220905235430629

我们继续深入,什么是可变函数

可变函数即变量名加括号,PHP系统会尝试解析成函数,如果有当前变量中的值为命名的函数,就会调用。如果没有就报错。 可变函数不能用于例如 echo,print,unset(),isset(),empty(),include,require eval() 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。

这样的特性让我们无法再去动态调用eval,但是这也给网站安全人员带来了新的挑战:

由于php的eval函数并不是系统组件函数,因此我们在php.ini中使用disable_functions是无法禁止它的。

好了,说了这么多前置知识,你应该了解为什么我选择使用system来改写了。

改写

使用system进行规避,但需要对system进行改写

首先运用到上面提到的将第一次传递的Token利用起来:

接受Token:

写一个利用的方法:

其实算法很简单,通过枚举来"解密"MD5

通过这个,我们可以找到字母s(这里传递s是因为system存在两个s比较方便重复利用)

接着我们开始找y的表达,通过异想天开的想法,我发现了一个非常"有意思"的事情,LinuxWindows的中间两位居然是相同的,这意味着我们可以通过这个手法来凑出想要的关键字

规避异或

异或是亮点吗?我觉得不一定。现在很多的Agent,你只要Unicode编码,甭管是正常业务还是webshell流量规避,统统按照后门给你告警。所以,尽量规避掉这些看似花里胡哨,实则存在巨大问题的东西。

我们可以把base64_decode给他通过外部传参进去:

16进制传递进去,但不知道为什么,hex解密后base64_decode居然会出现一些奇奇怪怪的字符,所以得正则提取一下...

更改版使用

在原先不变的情况下,Token需要传递:03c7c0ace395d801

之后根据返回的Token生成需要的命令和对应的base64_decode这俩

发包:

image-20220807170458122

没找到什么好的名称,回来再找找,争取继续规避。

最后看一眼Webshell查杀效果:

原版:

image-20220808161505243

结果并不意外,毕竟还是存在eval这种高危的函数,外加接收外部参数及异或加密。叠的debuff实在太多了

二开:

image-20220808161704114

还是那句话,化繁为简,别玩那些眼花缭乱的操作,最后只会把原本能过的webshell变得过不了,那可就真的可惜了。

python脚本自动化连接

完成了上面的免杀改写操作以后,对应的需要我们进行最后一步的操作:武器化

于是我使用python+PHP来完成(主要懒得转写那个加密函数了)

PHP端:

python

最后成果:

image-20220906210304741

总结

本文分析了捕获的Webshell是如何连接的,并对捕获的Webshell进行改写,以通过webshell引擎的检测,最后对复杂的连接过程使用python进行武器化编写,完整的完成了整套流程。