
简介这是一份面向网络安全初学者与毕设学生的WebShell检测工具源码包基于Python实现可用于学习恶意脚本识别、特征匹配与服务器安全巡检等场景。压缩包共21个文件以19个py脚本为主辅以1个md说明文档和1个html报告页面整体仅12KB结构轻量、便于阅读与二次开发。核心模块涵盖文件遍历、敏感词过滤、WebShell扫描与HTML报告生成plugins目录下按PHP危险函数分类提供多个检测插件如eval/assert、preg_replace、call_user_func、include_file、packshell等便于理解特征码匹配思路。已有55人学习下载适合作为毕业设计参考或安全工具入门练手项目读者可从中掌握插件化检测架构、递归扫描流程与报告输出方式并在此基础上扩展机器学习识别或实时监控能力。1. 从一次应急响应说起这个 Python WebShell 检测工具能帮你做什么凌晨两点被叫起来处理一台被上传了 webshell 的服务器这种事干过运维或者应急响应的同行应该都不陌生。日志里能看到明显的 POST 请求但翻遍网站目录几万个 PHP 文件里到底哪个是马、哪个是正常业务代码靠肉眼一个个看根本不现实。这时候一个能自动遍历目录、按特征匹配可疑文件的脚本价值就体现出来了。今天要拆的这个资源就是一套基于 Python 开发的 WebShell 检测工具拿到手是一个 zip 包解压后能直接跑核心逻辑围绕特征码匹配和插件化检测展开。它解决的核心问题很明确给定一个网站根目录快速筛出疑似 WebShell 的文件并生成一份可读的 HTML 报告。适合谁用做应急响应查杀的、做毕设需要一套完整安全工具代码的、以及想研究 webshell 特征匹配思路的开发者。工具本身不依赖复杂的机器学习模型走的是规则匹配路线好处是轻量、可解释、跑得快缺点是面对高度混淆的变种会有漏报这一点后面会具体讲。整个项目结构清晰main.py是入口scanShell.py负责扫描调度plugins目录下按 PHP 危险函数拆成了十几个检测插件这种设计让扩展新规则变得很容易。2. 工具架构拆解从 main.py 到 plugins 的检测链路2.1 目录结构与模块职责先把 zip 解压后的目录结构理清楚这决定了你后面改代码时该动哪个文件。整个项目没有用第三方框架纯标准库加少量常见库部署成本很低。python0324/ ├── main.py # 程序入口解析参数、启动扫描 ├── scanShell.py # 扫描核心遍历目录、调用插件 ├── filterShell.py # 过滤逻辑排除白名单文件 ├── sensitiveWord.py # 敏感词定义与匹配 ├── getFileTime.py # 获取文件时间信息辅助判断 ├── createHtml.py # 生成 report.html 报告 ├── report.html # 扫描结果输出文件 ├── readme.md # 使用说明 ├── directory/ │ └── __init__.py ├── plugins/ │ ├── __init__.py │ ├── php_eval_assert-plugin.py │ ├── php_array_map-plugin.py │ ├── php_preg_replace-plugin.py │ ├── php_call_user_func-plugin.py │ ├── php_include_file-plugin.py │ ├── php_dynamic_function-plugin.py │ ├── php_packshell-plugin.py │ ├── php_zendencode-plugin.py │ ├── php_flowload-plugin.py │ └── php_ddos_cc-plugin.py └── webshell.py # webshell 相关基础类或常量main.py负责接收命令行参数比如目标目录路径、是否递归、输出报告路径等。scanShell.py是真正干活的地方它递归遍历目标目录下的所有文件对每个文件依次调用plugins里的检测函数。每个插件文件对应一类 PHP 危险函数的特征匹配比如php_eval_assert-plugin.py专门检测eval和assert的调用模式php_preg_replace-plugin.py检测preg_replace配合/e修饰符的代码执行漏洞。filterShell.py的作用是白名单过滤避免把正常使用这些函数的框架文件误报成 webshell。createHtml.py把扫描结果渲染成 HTML 表格方便非技术人员查看。2.2 插件化检测的设计理由为什么要把检测规则拆成一个个独立插件而不是写在一个大文件里这是这个工具比较聪明的地方。WebShell 的变种极多但归根结底逃不出那几类危险函数的组合调用。把每类特征独立成插件后新增一种检测规则只需要在plugins目录下加一个文件不用动核心扫描逻辑。插件之间互不干扰某个插件误报率高直接禁用对应文件即可。常见做法是每个插件暴露一个统一的检测接口比如detect(file_content, file_path)返回布尔值或匹配详情。scanShell.py在遍历文件时动态导入plugins目录下所有-plugin.py结尾的模块逐个调用。这种动态加载机制让工具具备了不错的可扩展性你完全可以照着现有插件的格式加一个检测system、exec、shell_exec的插件进去。2.3 扫描流程的代码走读下面这段伪代码还原了scanShell.py的核心扫描逻辑实际代码结构类似我按可读性做了整理import os import importlib.util def load_plugins(plugin_dir): 动态加载 plugins 目录下所有 -plugin.py 文件 plugins [] for fname in os.listdir(plugin_dir): if fname.endswith(-plugin.py): path os.path.join(plugin_dir, fname) spec importlib.util.spec_from_file_location(fname[:-3], path) mod importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) plugins.append(mod) return plugins def scan_directory(target_dir, plugins, whitelist): 递归遍历目录对每个文件调用所有插件 results [] for root, dirs, files in os.walk(target_dir): for f in files: fpath os.path.join(root, f) # 跳过白名单文件 if fpath in whitelist: continue try: with open(fpath, r, encodingutf-8, errorsignore) as fp: content fp.read() except Exception: continue for plugin in plugins: hit plugin.detect(content, fpath) if hit: results.append({ file: fpath, plugin: plugin.__name__, detail: hit }) return results逻辑说明load_plugins用importlib动态导入插件避免了手动维护插件列表。scan_directory用os.walk递归遍历对每个文件读取内容后依次喂给所有插件。参数方面target_dir是你要扫描的网站根目录whitelist是从filterShell.py里加载的白名单路径集合。注意文件读取用了errorsignore因为 webshell 文件可能包含二进制混淆内容直接读会抛编码异常忽略错误字符能保证扫描不中断。2.4 特征匹配的核心思路每个插件的detect函数内部本质上是正则匹配加启发式判断。以php_eval_assert-plugin.py为例它要检测的是类似eval($_POST[cmd])或assert($_REQUEST[pass])这种典型一句话木马结构。但直接匹配eval会误报大量正常代码所以需要组合条件函数名出现位置、参数是否为超全局变量、是否在文件开头附近、文件是否最近被修改等。sensitiveWord.py里维护了一份敏感词列表包括eval、assert、system、passthru、shell_exec、popen、base64_decode、gzinflate等。getFileTime.py获取文件的创建和修改时间辅助判断——正常业务文件通常不会在凌晨三点被修改。这些信号综合起来才能把误报压到一个可接受的水平。3. 跑起来环境准备、参数配置与首次扫描3.1 Python 环境与依赖确认这个工具对 Python 版本要求不苛刻Python 3.6 以上都能跑。如果你机器上还没装 Python去官网下载安装包安装时记得勾选“Add Python to PATH”否则命令行里敲python会提示找不到命令。装完后验证一下python --version # 输出类似 Python 3.10.4 即可依赖方面这个项目基本只用标准库os、re、importlib、html这些都不需要额外安装。如果你在readme.md里看到提到了requests那是用于后续可能的远程扫描扩展本地目录扫描用不到。所以理论上解压就能跑不需要pip install一堆东西这对毕设场景很友好——评委老师拿到手也能直接运行。3.2 命令行参数与扫描目标指定main.py是入口常见用法是传入目标目录路径。假设你把工具解压到了/home/user/python0324要扫描的网站目录是/var/www/html那么命令大致是这样cd /home/user/python0324 python main.py -d /var/www/html -o report.html参数说明-d指定要扫描的目录必填-o指定报告输出路径不填默认生成report.html在当前目录。有些版本可能用位置参数而不是-d具体以readme.md为准。如果目标目录文件数量很大比如超过十万个文件建议先拷贝一个子集测试确认规则误报率可接受后再全量跑。3.3 白名单配置降低误报的关键一步filterShell.py里定义了白名单逻辑。正常业务代码里也会出现eval、assert这些词比如某些模板引擎、缓存组件、单元测试文件。如果不加过滤扫描结果会是一大堆误报根本没法用。白名单通常按文件路径或文件名模式配置比如排除vendor/、node_modules/、tests/这些目录。我一般会先跑一遍全量扫描看看哪些正常文件被误报了然后把它们的路径加到白名单里再跑第二遍。这个过程可能需要迭代两三次但一旦白名单稳定下来后续扫描就干净了。注意白名单不要加得太宽泛比如直接排除整个wp-content/目录那攻击者把马传到这个目录下你就查不到了。白名单要精确到具体文件或特定模式。3.4 首次扫描与报告解读扫描完成后createHtml.py会生成report.html。用浏览器打开通常是一个表格列出可疑文件路径、命中的插件名称、匹配到的具体代码片段。报告里还会带上文件修改时间方便你判断是不是最近被上传的。解读报告时重点关注几类命中多个插件的文件、修改时间异常的文件、位于上传目录或缓存目录的文件。单一插件命中可能是误报但一个文件同时触发eval和base64_decode两个插件基本可以确定有问题。报告里的代码片段能帮你快速确认是不是一句话木马比如看到eval($_POST[x])这种直接删文件就行。提示首次扫描建议在测试环境或网站备份副本上进行避免误删正常文件。确认规则准确后再上生产环境。4. 避坑与排查扫描结果不准时先查这几处4.1 扫描报编码错误导致中断现象跑扫描时程序抛出UnicodeDecodeError然后退出或者某些文件被跳过没扫到。原因webshell 文件经常包含混淆过的二进制内容或者用 GBK 编码保存用 UTF-8 读取就会报错。如果代码里没有做异常捕获整个扫描流程会中断。解决确认scanShell.py里读文件时加了errorsignore参数。如果原代码没加手动补上。另外可以在读取前先判断文件扩展名只扫.php、.jsp、.asp这些脚本文件跳过图片、压缩包等二进制文件既提速又避免编码问题。4.2 误报太多正常框架文件被标记现象报告里几百条结果大部分是vendor/或framework/目录下的正常文件。原因很多 PHP 框架内部确实会用到call_user_func、preg_replace这些函数特征匹配无法区分正常调用和恶意调用。白名单没配置好或者配置得太粗糙。解决先按目录排除把vendor/、node_modules/、tests/、cache/加进白名单。然后针对剩余误报按文件路径精确排除。如果某个插件误报率特别高比如php_call_user_func-plugin.py可以暂时禁用这个插件只保留eval、assert、packshell这几个高准确率的。4.3 漏报混淆过的 webshell 扫不出来现象明明知道服务器上有马但工具跑完报告是空的或者只报了几个不相关的文件。原因攻击者用了编码混淆比如base64_decode嵌套gzinflate或者把危险函数拆成变量拼接$aev.al; $a($_POST[x]);这种。纯正则匹配对这类变形无能为力。解决这个工具的定位是快速筛查不是万能查杀。面对混淆马需要配合其他手段查看 access log 里异常 POST 请求对应的文件、用find按修改时间排序找最近变动的文件、检查上传目录里文件名奇怪的 PHP 文件。工具能覆盖大部分常见一句话木马和已知变种但高级混淆需要人工介入分析。4.4 插件加载失败导致检测缺失现象扫描跑完了但报告里只命中了一两类特征其他插件好像没生效。原因plugins目录下的插件文件命名不符合-plugin.py后缀规则或者动态导入时因为语法错误静默失败。Python 的importlib在加载失败时如果不打印异常你根本不知道哪个插件没加载上。解决在load_plugins函数里加日志输出打印成功加载的插件数量和名称。跑一次扫描确认加载的插件数量与plugins目录下文件数量一致。如果少了检查对应文件是否有语法错误用python -m py_compile 插件文件名单独编译验证。4.5 报告文件路径写死导致覆盖现象每次扫描都覆盖同一个report.html想对比两次扫描结果时发现旧报告没了。原因createHtml.py里输出路径写死了或者main.py的-o参数没生效。解决给报告文件名加上时间戳比如report_20250101_1430.html。改createHtml.py里的输出逻辑用datetime.now().strftime()生成唯一文件名。这样每次扫描结果都保留方便追溯和对比。5. 进阶用法自定义插件与扫描策略调优5.1 照着现有插件写一个新检测规则假设你想加一个检测system和shell_exec命令执行函数的插件。在plugins目录下新建php_system_exec-plugin.py内容参考现有插件结构import re def detect(content, file_path): 检测 system/shell_exec/passthru 等命令执行函数 返回匹配详情字符串未命中返回 None pattern r(system|shell_exec|passthru|popen|proc_open)\s*\(\s*\$_(POST|GET|REQUEST|COOKIE) match re.search(pattern, content, re.IGNORECASE) if match: return f命中命令执行函数: {match.group(0)} return None逻辑说明正则匹配命令执行函数直接接收超全局变量作为参数的模式这是典型 webshell 写法。re.IGNORECASE忽略大小写防止攻击者用System绕过。返回的字符串会出现在报告里写清楚命中了什么方便人工确认。参数content是文件内容字符串file_path是文件路径虽然这个插件没用到路径但保持接口统一。写完后把文件放到plugins目录重新跑扫描新插件会自动被加载。你可以用同样的方式加检测file_put_contents、fopen写文件操作的插件覆盖更多 webshell 行为。5.2 扫描策略调优分级检测全量扫描大目录时如果每个文件都跑所有插件速度会慢。我一般会做分级第一级只跑高准确率、低误报的插件比如eval、assert、packshell快速筛出最可疑的文件第二级对第一级命中的文件再跑全部插件做详细分析。这样既保证速度又不漏掉细节。实现上可以在scanShell.py里加一个插件优先级列表第一轮只加载优先级高的插件第二轮再加载全部。或者简单点先按文件修改时间排序只扫描最近七天内修改过的文件因为 webshell 通常是新上传的老文件基本安全。5.3 验证工具效果构造测试样本想确认工具到底能不能检出自己造几个测试文件最直接。在测试目录下建几个 PHP 文件分别写入典型 webshell 代码?php eval($_POST[cmd]); ? ?php assert($_REQUEST[pass]); ? ?php preg_replace(/test/e, $_POST[code], test); ? ?php $a base64_decode($_POST[x]); eval($a); ?然后跑扫描看报告里是否全部命中。如果某个没报出来检查对应插件是否加载、正则是否覆盖了这种写法。这种自测方式比拿真实网站跑更安全也更能帮你理解每个插件的检测边界。我每次改完插件规则都会拿这几个样本回归一遍确认没有把之前能检出的漏掉。5.4 一个容易忽略的细节文件时间辅助判断getFileTime.py这个模块单独看好像没什么用但在实际排查中文件修改时间往往是最有效的辅助信号。一个正常运行的网站核心代码文件可能几个月甚至几年都没动过。如果扫描报告里某个文件命中特征同时它的修改时间是今天凌晨那基本可以确定是 webshell。我一般会在报告里按修改时间倒序排列优先看最近变动的文件。你可以在createHtml.py里加一列“距今天数”或者直接在扫描结果里过滤出最近三天修改过的文件单独输出一份。这个习惯帮我省了很多逐条确认的时间——从那以后我每次拿到扫描报告第一件事就是按时间排序先看最新的那几条再结合特征命中情况做判断。希望这个思路帮到你。本文还有配套的精品资源点击获取