phpwebshell从基础到深入变种
php
1 |
|
位置:C:\Users\e’e’t\Desktop\沙箱\被杀马\php\shell.php
Shell01.php
这个特征感觉有两个
一个是主要的是时间问题,它是一个在野较长的冰蝎码(大概率是这个原因)
第二个是因为它利用的openssl的AES加密 再加上使用了eval函数,造成查杀?(小概率是这个原因)
Caidao01.php C:\Users\e’e’t\Desktop\沙箱\被杀马\php\caidao01.php
这个码可以过d盾和安全狗。但是不能过火绒
但是火绒杀毒都不会说出问题在哪
我们先分析它
不再使用eval而改用assert。(这是个优点)
使用了destruct析构函数
析构函数的作用和构造函数正好相反,析构函数只有在对象被垃圾收集器收集前(即对象从内存中删除之前)才会被自动调用。析构函数允许我们在销毁一个对象之前执行一些特定的操作,例如关闭文件、释放结果集等。
在 PHP 中有一种垃圾回收机制,当对象不能被访问时就会自动启动垃圾回收机制,收回对象占用的内存空间。而析构函数正是在垃圾回收机制回收对象之前调用的。
在 PHP 中析构函数并不是很常用,它属于类中可选的一部分,只有需要的时候才在类中声明。
所以这个函数可能是造成被杀的特征之一
Call_user_func和序列化和反序列化也是造成的原因之一
还有就是$_POST[]被完整写出来了,这个原因之一
然后套用一些最近好用的phpshell的方法
1 |
|
这里只用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的用法
这里有个疑惑点。这段代码使用的是直接输入命令ipconfig
而我们的目的是上传一个webshell,但是在这里的用法看来是主动运行命令。
这样的话我们确实可以利用这个去执行命令。
比如执行echo一个webshell到新的php文件。
但是这样有违我们的初衷。我们要做的是进行webshell的免杀。如果这样嵌套一个新的webshell的话。还需要对webshell进行免杀
那有没有做成webshell的方法吗?
暂时没找到方法
C:\Users\e’e’t\Desktop\沙箱\被杀马\php\1.php
继续用我们的“22(代码执行函数)”吧
把上面哪个红框换成arrest或者eval
过狗还是相当容易的。但是过火绒不行
①. *可以利用php的反射机制,获取注释的内容,然后拼凑出assert,从而动态执行*
慢慢调试
接下来就是在_POST前加上$和在其后加上[‘a’]
接下来就是拼接调用
参考大佬 用到了@$assret($a=$a); $a=$_POST[‘a’]
火绒过了?
难以置信
果然有问题
==不能执行的话就别讲免杀了!!!==
看看问题出现在哪
我感觉是@$assret($a=$a);用不了了
现在我们可以肯定的是
$dd = assert
$ee = $_POST[‘a’]
我感觉是因为我在写的时候是没把它实例化出来
就是说得写个function去执行它
不然系统会认为它是个文本
有报错说明是好事
t_variable是PHP的一种内部标识,通常用在错误信息中。
出现该错误是因为在不该出现变量的地方出现了变量,或者变量名不合法。
就是说$dd后不能再加一个变量
再次访问
有点问题
直接改成$_REQUEST[‘x’]试试
反引号无敌、用反引号试试
再加个print扰乱一下规则
= = = == = = = == == === = = = == = =
自闭
是不是我这个方法不对?
不能$dd和$ee只能用一个?
网上很多都是只用一个
那就写一个试试
无语 不搞了 要学会放弃 下一个
*③**静态函数调用普通函数,或者普通函数调用静态函数。**(**冰蝎码启发**)**:其他类静态函数();*
*⑤**我们可不可以把webshell隐藏在多个正常的php文件中形成一个调用链,当然这种形式已经跳出了上传的场景,更加偏向于权限维持。我们也可以将webshell隐藏在php扩展中来绕过一些限制敏感函数执行的场景。其实这个思路已经有很多大佬做过了。也有一些开源的东西。*
*⑥使用脚本把免杀shell中的函数名,类名,以及变量名进行随机化处理。把敏感函数eval,assert进行异或处理*
不建议异或处理。现在大部分异或处理已经很难过杀了
*⑦函数+异或*
1 |
|
异或太难了。可以说是即难又很不容易过waf的方法
*⑧利用空格和tab数构造字符*
Pro版:如果将空格与 tab 分别用 2 个不同的不可见字符替换
就是unicode的17B4和17B5,这两个字符大部分不可见
大佬有些相关的脚本(使用的话必须php文本够长,不够的话脚本会自动填充,造成底部产生很多空格)
*⑨传统的绕过最终都会进行语法解析;**但是如果语法报错的话,就可能导致解析失败了**;**就可以利用这个来执行命令*
参考………….
先基础的
1.利用\特殊符号来引起报错
环境php5.2可以执行
其他的都不行
2.高版本php语法不换行来执行命令
1 |
|
适合7.3
在php7中不认为是数字,php5则依旧为数字
经过测试 5.3 和5.5可以成功执行命令,5.2和php7无法执行
1 |
|
这里我们就用高版本的来
连接看看
看一下D盾
太直接了
接下来就是免杀
我们先用marcr0phag3大佬的字符转空白脚本试一下
用不了
这个脚本的payload格式只能是命令执行函数。不难添加其他的
那我们使用它本来那个payload看下怎么样
还是报错??
那就不搞这个了 尝试别的免杀
好的变成1了 基本上这个时安全狗都是直接过
提示我们内藏变量函数$str
那我们看一下能不能用别的字符去替代$str
改名字也没用
这就证明了这个是被逻辑查杀的
需要用其他方法
直接用环境变量
D盾免杀
火绒也过 真爽!
对于关键词的后传入对免杀安全狗,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 |
|
2.php php5.3.3
create_function 函数会创建一个匿名函数(lambda
样式),在第一个echo
中显示出名字,并在第二个echo
语句中执行了此函数。
1 |
|
这里$a为函数,’ ‘为参数
那么可以看作为
1 |
|
传入payload
1 |
|
在function函数中即
1 |
|
后面的内容注释掉了,即执行命令
3.php
跟上面一样,虽然有一点变形,但是再$b的打印上没有特殊之处,所以payload:
1 |
|
4.php
没什么特别之处,assert直接作为函数执行,payload:
1 |
|
5.php
*call_user_func()*函数的特点,知道后面的后面的*为参数,前面的*a**为函数即可
1 |
|
6.php
基本上属于3的内容加强版,重点就是需要进行闭合,,代码变多了,payload没有出入,重点还是再**$code**的位置
1 |
|
7.php
属于加强版本,**$sort_function的内容进行闭合,也就是sort_by**的参数值要实现闭合,构造payload:
1 |
|
8.php
1 |
|
一句话木马写入2.php
也有其它解法直接进行命令执行。
手动带他们进行分析
20220405.php
1 |
|
看不出来
证明我们直接写的b就是引用了 create_function(null,fun2());
所以这个array是解析不出来引用的create_function,所以这个就需要extract的注释。而这个extrct估计就是来绕过的
因为extract的原因所以看不了
根据p神文章自写木马
利用修饰符e php5.6.40
PHP旧版本的preg类函数中存在一个修饰符 e ,==增加了这个修饰符后,替换后的结果将会被放进eval执行==。利用这个方法,即可构造一个不带eval关键字的Webshell,比如:
1 |
|
其实原理很简单,我们查看PHP文档可以发现, preg_replace 的第一个参数是支持传入字符串或数组的:
而我猜测检测引擎后端是只考虑了字符串的情况。所以,我使用下面这个简单的样本就绕过了QT引擎:
111.php?2=phpinfo();
1 |
|
1 |
|
那么我们也可以利用e修饰符。
样本简化之后如下:==php 5.6.9==
1 |
|
222.php
?cmd=phpinfo();®exp=/(.*)/e
原理很简单, php 将各类 preg 函数包装成了一个正则的迭代器类
从底层代码上看,当 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 |
|
因此我猜测可能在动态检测的时候由于⽆法知道参数的值,动态执⾏的时候也会爆出此错误,导致代码
不能执⾏下去,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 |
|
?mem=1&cmd=phpinfo();
pcre_get_compiled_regex_cache 函数
我很快关注到了这个 pcre_get_compiled_regex_cache 函
数,这个函数用于处理 preg_replace 的第一个参数
555.php?2333=phpinfo();
我省略了很多,因为这些代码前面的注释足以说明他们的作用。
第一段注释,说明了解析正则的时候会忽略掉正则前面所有的空白字符
第二段注释,说明delimiter不能是字母、数字或者反斜线。
30delimiter就是正则里的分隔符,比如 /.*/e 这个正则,它的delimiter是 / 。很显然,delimiter是
有两个的,分别是start_delimiter和end_delimiter。
第三段注释,当 start_delimiter == end_delimiter 时,分隔符只有一个符号。
第四段注释,当 start_delimiter != end_delimiter 时,分隔符有两个符号。
第四段就是有趣的点了,我们平时日常开发或审计的过程中,通常遇到的正则表达式分隔符,要不就是
斜线 / ,要不就是竖线 | ,也有见过井号 # 、波浪线 ~ 之类的,但甭管怎样他们都属于
start_delimiter == end_delimiter 这种情况。
但PHP的正则是支持使用“括号”这种成对出现的符号作为分隔符的,只要正则两侧的分隔符能够组成一对都是合法分隔符,比如:
这种比较奇葩的正则表达式分隔符,如果Webshell检测引擎没有正确地进行解析,就有可能被绕过。
1 |
|
以在多个修饰符间,添加一些没意义的空白字符
实际上是阅读底层函数 pcre_get_compiled_regex_cache 的代码,来到后面一个while循环中:
这个switch语句显然就是用来处理各种修饰符的。
可以观察到,switch里对空格和换行进行了匹配,如果遇到这两个字符直接break忽略,进入下一次循环
也就是说,我们可以在多个修饰符间,添加一些没意义的空白字符,比如:
1 |
|
666.php?2333=phpinfo();
RegexIterator 这个类
这个SPL类用于将一个普通迭代器变成一个具有正则功能的迭代
器。而正则的模式、方法等都是可以在这个类对象中指定的。
1 |
|
这种是连不了的 因为这个函数没有回显 request就是在封装的时候用的 当get和post 不能用的时候可以用request
如果你要当成连接的话 就需要使用eval 那就要做eval的免杀 那还用这个干嘛
666.php 多重异或
PHP代码下面的“^”是异或运算符,按二进制位进行异或运算(XOR)
1 |
|
C:\Users\e’e’t\Desktop\红队安全开发\免杀马的制作\php\备课\20220523写\666.php
多次异或过waf 基本上配合其他方式二次开发 都能
按位取反
这么明显的一句话 只爆了一级。
我们在随便加个函数调用一下
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 |
|
首先我们需要利用技巧(PHP本身的特性),来阻断污点追踪的过程,我在fuzz测试的时候发现了array_map()这个函数==存在callback并且能够逃避检测==
那么首先的能够bypass的污点汇集点已经有了==,接下里来就是寻找其他函数来将变量”洗白==”,我选择了array_diff()
这样就可以==利用该函数拼凑出一个system函数==,再==利用array_map()的callback====来做命令执行==
这样就完成了最简单的一次bypass
样本2
1 |
|
__FILE__是PHP的一个魔术常量,它会返回当前执行PHP脚本的完整路径和文件名,我们利用substr()函数逆着截取,就能获得system再利用变量做函数的方式,打断了污点追踪的过程,进行命令执行,也可以成功bypass掉牧云引擎。
样本3
1 |
|
这个套了一层反序列化,隐藏污点汇集点的方法与样本一相同,利用数组差级构造system后利用原生类Closure的fromCallable函数
进行命令执行(在牧云中array_diff([“s”,”a”,”b”,”ys”,”te”,”m”],[“a”,”b”]);这种方式会被check,索性换成动态控制,这样也能打断污点追踪)
样本4
1 |
|
该样本需要一些条件,前提是开启了php-xml拓展才可以,其原理就是用XML去注册一个registerPHPFunctions,也就是我们想要执行的system再利用getClosure去触发该方法而构成的webshell,其中即利用到了PHP的特性,利用registerNamespace和registerPHPFunctions来中断污点追踪,从而RCE
==打断污点追踪前执行函数就要设定好,之后再拼接==