免责声明 本文全部实验操作均在本人完全可控的自建本地靶场环境完成,文章仅用于网络安全原理学习与技术研究。根据《中华人民共和国网络安全法》,未经授权对任何第三方系统进行探测、命令执行、文件写入等操作属于违法行为。
严禁复制、使用本文中的 Payload、代码应用到未获得授权的设备上。若读者将文中内容用于非法用途,一切法律责任由行为人本人承担,与本文作者无关。
个人水平有限,如有不足欢迎指正。
webshell绕过:网站存在 WAF、文件上传校验、代码检测、杀毒软件,会拦截 WebShell 文件上传或者直接查杀后门。绕过就是想方设法躲过检测,成功把后门上传到服务器并且正常执行。
1.缓存导致的绕过
HIDS
主机入侵检测,HIDS主要通过部署在各主机上的 HIDS Agent 采集主机上的文件、进程、日志、网络连接等信息,并将可疑文件或行为上报到中心管理节点,由中心节点统一调度检测引擎进行分析,最终生成检测结果并保存。
攻击者上传变形一句话木马 → HIDS Agent 捕获新增 php 文件 → 上报 Master 进入队列 → 分拣器无缓存 → 送入多引擎检测 → 机器学习 / Yara 识别为 WebShell → 结果入库并产生安全告警。

1. HIDS节点(Agent)
HIDS Agent 部署在被监控的服务器上。它负责对主机进行实时监控,例如服务器的 Web 目录突然出现:shell.php。Agent 可以发现这个文件,并将文件信息或文件内容提交给中心管理节点进行进一步分析。
2. master节点
master节点是整个系统的管理中心。
它负责:接收 Agent 上报的数据,进行任务调度,分发检测任务,获取检测结果,保存检测结果
因此可以把 master 理解成整个 HIDS 系统的控制中心 。多个 HIDS Agent 不需要自己管理所有检测逻辑,而是统一由 master 进行协调。
3. 队列
队列主要解决的是异步任务处理和并发问题。例如短时间内有 100 个 Agent 同时发现可疑文件:
Agent 1 ─┐
Agent 2 ─┤
Agent 3 ─┤
Agent 4 ─┼──→ 队列 ──→ 分拣器
... │
Agent 100┘
如果直接同时进行检测,可能导致检测引擎压力过大。通过队列,可以把任务暂时保存起来,再由后面的模块按照一定速度进行处理。
4. 分拣器
分拣器的作用是:根据待检测文件的类型和特征,决定应该交给哪些检测引擎。
例如:
test.php经过分拣器到达PHP检测引擎
test.jsp经过分拣器到达Java检测引擎
也可以根据检测需求,同时交给多个引擎。所以分拣器相当于:检测任务的路由器。
5. WebShell检测引擎
这是整个流程中负责实际检测分析 的部分。内容检测
PHP引擎
xxx.php到达PHP引擎,分析PHP代码特征,判断是否具有WebShell特征
Java引擎
主要针对 JSP 等 Java Web 文件进行检测。
xxx.jsp,Java引擎,分析Java/JSP代码
Yara引擎
YARA 更偏向于基于规则进行匹配。
例如预先定义一些特征规则:某些特征组合:根据YARA规则匹配文件,判断是否命中特征
机器学习引擎
机器学习引擎可以利用训练好的模型,根据文件的代码特征、行为特征等进行判断。
因此多个引擎可以形成:
规则检测+语言特征检测+特征匹配+机器学习,实现综合判断
6. Redis缓存
主要用于缓存检测任务和检测结果 。对文件内容做 Hash(MD5/SHA256)作为 Redis 缓存 Key。
例如某个文件已经检测过 :文件A查询Redis**,发现已经检测过,直接返回缓存结果** 这样就不需要每次都重新调用检测引擎。可以减少重复检测,提高系统处理速度。 (也就容易造成缓存导致的webshell绕过)
第一次检测
文件 → 检测引擎 → 结果保存 → Redis
再次检测
文件 → 查询Redis → 直接获得结果
文件名/扩展名 → 主要用于分拣、确定检测类型;文件内容 → 主要用于实际 WebShell 检测;Redis → 缓存检测任务或检测结果,避免重复检测。
7. 数据库
数据库主要负责持久化存储。
例如:主机信息、Agent信息、检测规则、检测结果、告警记录、历史记录
Redis 更偏向:临时缓存、快速查询
数据库更偏向:长期保存、持久化存储
利用第6点缓存的特点产生的思路
例如,首先上传一个 1.jsp 文件 ,其文件内容为 <?php phpinfo(); ?>。系统首先根据文件扩展名将该文件分配给 Java 检测引擎 。由于文件内容实际为 PHP 代码,与 Java/JSP 的语法特征不匹配,Java 检测引擎未检测出恶意特征 ,因此该文件被判定为正常并放行,同时将该文件的检测结果及其内容对应的哈希值写入 Redis 缓存。
随后再次上传一个 1.php 文件,但其文件内容与之前的 1.jsp 完全相同 。此时,如果系统的 Redis 缓存是以文件内容哈希作为缓存键,而没有进一步区分文件扩展名或实际使用的检测引擎,那么系统计算出相同的内容哈希后,会直接命中之前 1.jsp 的缓存记录,并复用之前的"正常/放行"检测结果,而不会再次调用 PHP 检测引擎对该文件进行检测。
因此,虽然第二次上传的文件扩展名已经从 .jsp 变成了 .php,理论上应该进入 PHP 检测引擎,但由于相同文件内容命中了此前由 Java 引擎产生的缓存结果 ,最终导致 1.php 继承了第一次检测的放行结果,从而出现检测引擎与缓存结果不匹配导致的检测绕过。
防御:计算文件缓存时带上了文件名
绕过思路:生成一个正常文件和一个 webshell,两个文件的 hash 完全相同 ,再让他们文件名相同,这样就可以命中同一个缓存了。
hash碰撞
基础概念:哈希算法:输入任意长度数据 → 输出固定长度哈希摘要(MD5、SHA1、SHA256)。理想状态:不同内容,一定产出不同哈希值。
哈希碰撞:两段内容完全不一样的文件 / 数据,经过同一个哈希函数计算,得到完全相同的哈希值。A ≠ B,但是 hash (A) = hash (B)
思路:
例如:创建1.php和2.php文件,第一个php文件内容设置为
<?php
evam($_POST[123]);
第二个php文件内容为
<?php
eval($_POST[123]);
首先将 1.php 提交给 PHP 检测引擎,由于 evam() 并不是 PHP 中实际存在的函数,检测引擎未识别出其中的危险代码特征,因此将其判定为正常并放行,并将该文件内容对应的 Hash 及检测结果写入 Redis 缓存。
随后,通过构造使 2.php 的文件内容与 1.php 产生相同的 Hash 值 ,再将该文件重新命名为 1.php 后上传。由于 HIDS 的缓存机制在查询缓存时使用文件内容 Hash 作为重要依据,重新命名后的 2.php 会以 1.php 的文件名进入检测流程,并命中此前 1.php 对应的正常缓存记录。此时系统直接复用之前的检测结果,而不会重新对 eval() 进行检测,从而使修改后的恶意文件被错误放行。
这里用到了这样的一个工具进行哈希碰撞
cr-marcstevens/HashClash:Project HashClash - MD5 和 SHA-1 密码分析
cpc.sh 是Chosen-Prefix Collision(选择前缀碰撞)脚本,在这里适用

最终成功生成了两文件内容不同,但是hash值相同的文件,接下就可以将第二个php文件改名为第一个php文件名,再次提交就可以绕过。