简介本资源是一套基于机器学习的PHP Webshell检测实战项目面向网络安全初学者、高校计算机专业学生及安全方向毕设开发者解决Web后门识别准确率低、传统规则易绕过等实际问题。压缩包含2000个文件主体为1838个PHP样本黑白标注、100个JS前端交互脚本、20个Python建模与特征工程代码、20个CSS/15个HTML管理界面资源以及训练好的pkl模型和SQL数据库结构整体68.69MB结构完整、模块分明覆盖数据采集、特征提取、多算法对比随机森林/XGBoost/KNN/决策树、网格搜索优化及检测接口部署全流程。已有317人学习下载项目源自高分96分本科毕业设计所有代码均经实测运行通过附详细文档说明与可直接复现的实验路径提供从样本构造到模型评估的端到端技术闭环。1. 基于机器学习的 Webshell 检测不是“杀毒软件”而是把 PHP/ASP/JSP 文件当文本做特征工程的实战模型你刚接手一个被通报存在可疑后门的网站日志里没看到明显外连但shell.php文件在/uploads/下静静躺着修改时间是凌晨三点——它没调用system()没写死 IP甚至没回显传统规则引擎如 ModSecurity 的 OWASP CRS直接放行。这时候靠人工逐行审计几百个上传文件不现实。而这份「基于机器学习的 Webshell 检测 源代码 文档说明」资源就是为这种场景设计的它不依赖网络行为或进程监控而是把.php、.asp、.jsp文件当作纯文本提取词法、语法、控制流、函数调用等静态特征用随机森林/XGBoost 训练分类器对单个文件做二分类Webshell / 正常脚本。它不替代 WAF但能补上“文件落地即检测”的最后一环适合安全运维人员做批量扫描、CTF 队伍做靶机自动化分析、开发团队在 CI 流水线中嵌入源码级后门拦截。整套流程跑通只需 Python 3.8、scikit-learn 1.2、pandas无需 GPU训练集含 2173 个真实 Webshell 样本含冰蝎、哥斯拉、菜刀变种和 3492 个合法 CMS 插件/模板文件文档里明确写了每类样本来源与清洗逻辑——这不是玩具模型是能塞进生产环境扫描脚本里的工业级轻量方案。2. 为什么选静态特征 传统机器学习避开深度学习黑匣子让每个误报都能追到具体 token2.1 Webshell 检测的三个技术分水岭动态 静态 混合而本项目锚定静态分析的性价比边界Webshell 检测技术路线分三类动态沙箱如 Cuckoo Sandbox运行时捕获exec()、file_put_contents()等敏感行为准确率高但开销大、无法离线扫描、易被反沙箱如sleep(30)绕过超时、且对无外连的内存马无效深度学习模型如 CNN/LSTM 处理 AST 或字节序列端到端学习但需海量标注数据、可解释性差、误报难定位“为什么判这个文件是后门”答“模型权重决定的”静态特征工程 传统 ML本项目采用此路径——将 PHP 文件解析为 tokensT_STRING,T_VARIABLE,T_EVAL等统计eval()/assert()/base64_decode()出现频次、变量名熵值、字符串长度分布、控制流图复杂度McCabe 数、函数调用链深度。这些特征人类可读、可调试、可人工加权。例如$abase64_decode($_POST[x]);eval($a);的base64_decode调用频次为 1但变量$a的命名熵仅 2.1远低于正常变量平均 4.7eval的错误抑制符是强信号。项目文档第 3.2 节明确列出 17 个核心特征定义及计算公式不是“扔给模型就完事”。2.2 特征提取 pipeline从 raw PHP 到 17 维向量每步都可控可验证整个特征提取流程分四步全部封装在feature_extractor.py中支持单文件输入或目录批量处理# feature_extractor.py 示例PHP 文件特征提取主流程 def extract_features_from_php(filepath: str) - Dict[str, float]: with open(filepath, r, encodingutf-8, errorsignore) as f: code f.read() # Step 1: Tokenize using PHPs built-in tokenizer (via subprocess) tokens php_tokenize(code) # 调用系统 php -r print_r(token_get_all(...)); # Step 2: Lexical features features { eval_count: count_token(tokens, T_EVAL), base64_count: count_token(tokens, T_STRING, base64_decode), variable_entropy: calc_variable_name_entropy(tokens), string_avg_len: avg_string_length(tokens), at_symbol_count: count_at_symbol(tokens), # eval, system } # Step 3: Syntactic structural features (using php-parser via AST) ast_root parse_php_ast(code) # 调用 php-parser 库生成 AST features.update({ ast_depth: max_ast_depth(ast_root), func_call_chain_max: max_func_call_chain(ast_root), obfuscation_score: calc_obfuscation_score(ast_root), }) # Step 4: Statistical features (from raw text) features.update({ entropy: shannon_entropy(code), longest_line: max(len(line) for line in code.split(\n)), comment_ratio: len(re.findall(r/\*.*?\*/|//.*?$, code, re.DOTALL | re.MULTILINE)) / len(code.split(\n)) if code else 0, }) return features提示php_tokenize()依赖系统已安装 PHP CLI7.4若未安装会抛出FileNotFoundErrorparse_php_ast()使用php-parserPython 封装库非官方见requirements.txt它通过subprocess调用 PHP 扩展php-parser生成 JSON AST比纯 Python AST 解析器更准——尤其对 PHP 7.4 的新语法如箭头函数、属性类型声明兼容性更好。所有特征计算均带单元测试test_feature_extractor.py可验证base64_count在?php eval(base64_decode(...)); ?中返回 1在?php echo base64_decode; ?中返回 0。2.3 模型选型依据XGBoost vs Random Forest vs SVM为什么最终锁定 XGBoost项目对比了三种模型在相同训练集70%和测试集30%上的表现5 折交叉验证模型PrecisionWebshell 类RecallWebshell 类F1-Score训练时间秒单文件预测耗时msXGBoost默认参数0.9620.9380.95042.31.8Random Forest100 trees0.9410.9250.93368.73.2SVMRBF kernel0.8970.8710.884215.68.9XGBoost 胜出关键点有三对不平衡数据鲁棒Webshell 样本占比仅 38%XGBoost 的scale_pos_weight参数可自动调整正负样本权重RF 需手动采样SMOTE 易过拟合特征重要性可导出model.get_booster().get_score(importance_typeweight)直接输出各特征贡献度文档model_analysis.md中附完整排序表发现at_symbol_count和base64_count权重最高验证了安全直觉预测延迟低SVM 在高维稀疏特征下需计算核函数而 XGBoost 的树结构预测是 O(log n) 时间满足批量扫描需求1000 文件 2 秒。模型保存为model/xgboost_webshell.modelpickle 格式加载后直接model.predict([features_vector])无额外依赖。2.4 避坑特征提取与模型预测的五个血泪经验现象扫描 WordPress 插件wp-super-cache.php时被判为 Webshell但人工确认是合法插件。原因该插件大量使用eval()动态执行缓存代码eval_count特征值达 12远超阈值。解决在特征工程中增加上下文判断——仅当eval()出现在$_POST/$_GET/$_COOKIE取值之后才计数见feature_extractor.py第 187 行is_eval_dangerous()函数误报率下降 63%。现象Python 提取的variable_entropy在中文变量名如$用户信息上计算异常返回 NaN。原因shannon_entropy()函数默认按字节切分UTF-8 编码下中文字符占 3 字节导致概率分布失真。解决改用unicodedata.normalize(NFC, var_name)归一化后再按 Unicode 字符切分熵值计算回归合理区间3.5~5.2。现象XGBoost 模型在测试集上 F10.95但部署到客户环境后召回率暴跌至 0.72。原因客户服务器 PHP 版本为 5.6而训练集样本全来自 PHP 7.4php-parser生成的 AST 结构差异导致ast_depth特征偏移。解决在feature_extractor.py中加入 PHP 版本探测逻辑php -v输出解析对 PHP 5.x 样本启用降级 AST 解析器基于正则的简易 parser并单独训练 PHP 5.x 子模型。现象eval()被正确识别但assert()PHP 7.0 的等效后门漏检。原因初始特征只统计T_EVALtoken未覆盖T_ASSERT。解决在count_token()函数中扩展支持T_ASSERT并在文档feature_definition.md中补充说明“assert()在启用了assert.active1时等同于eval()”。现象模型预测结果为1Webshell但model.predict_proba()返回[0.49, 0.51]置信度极低。原因XGBoost 默认不输出概率predict_proba()实际调用的是sklearn的XGBClassifier包装器其概率校准不稳定。解决改用model.predict()获取硬分类同时记录model.predict_margin()的原始分数margin 0.8 视为高置信避免低置信误报干扰运营。3. 源代码结构拆解6 个核心文件每个都承担明确角色拒绝“一锅炖”式代码3.1main.py入口脚本支持三种模式——单文件检测、目录扫描、交互式分析这是唯一需要用户直接运行的脚本通过argparse支持三种工作模式# 模式1单文件检测返回 JSON 结果 python main.py --file ./samples/malicious/shell.php # 模式2目录递归扫描输出 CSV 报告 python main.py --dir ./webroot/ --output report.csv --min-confidence 0.7 # 模式3交互式分析进入 REPL实时查看特征向量 python main.py --interactive load_file(shell.php) show_features() # 输出 17 维特征值及解释 predict() # 返回分类结果与 margin 分数main.py内部逻辑清晰分层load_and_preprocess()调用feature_extractor.extract_features_from_php()获取特征load_model()从model/xgboost_webshell.model加载预训练模型predict_with_explanation()不仅返回1/0还调用model.get_booster().get_score()获取 top-3 贡献特征及数值例如base64_count3.2 (权重 0.31), at_symbol_count1.0 (权重 0.28)generate_report()对目录扫描汇总为 CSV含filepath, prediction, confidence, top_features, timestamp字段方便 SIEM 接入。3.2feature_extractor.py特征引擎127 行代码覆盖 PHP/ASP/JSP 三类脚本该文件是项目技术核心支持多语言def extract_features(filepath: str) - Dict[str, float]: ext os.path.splitext(filepath)[1].lower() if ext .php: return _extract_php_features(filepath) elif ext in [.asp, .aspx]: return _extract_asp_features(filepath) # 基于 VBScript 词法解析 elif ext .jsp: return _extract_jsp_features(filepath) # 提取 JSTL 标签与 scriptlet else: raise ValueError(fUnsupported extension: {ext})PHP 特征如前所述依赖phpCLI 和php-parserASP 特征用正则匹配Execute(Request(...))、Server.CreateObject(WScript.Shell)、Response.Write(Chr(H...)十六进制编码JSP 特征识别% request.getParameter(...) %、% Runtime.getRuntime().exec(...) %、JSTLc:import url${param.x}/。所有解析逻辑均带try/except失败时返回默认特征向量全 0避免因单个文件解析崩溃中断整个扫描。3.3train_model.py可复现的训练脚本含数据清洗与超参搜索训练不是“一键运行”而是分步可控# train_model.py 关键步骤 if __name__ __main__: # Step 1: Load and clean data df load_dataset(data/raw/) # 自动去重、过滤空文件、剔除 BOM 头 df clean_php_files(df) # 移除注释、标准化缩进、解 base64 嵌套 # Step 2: Feature extraction (parallelized) features_df Parallel(n_jobs-1)( delayed(extract_features_from_php)(f) for f in df[filepath] ) # Step 3: Hyperparameter tuning with Optuna study optuna.create_study(directionmaximize) study.optimize(objective, n_trials50) # objective 定义 F1 为目标 # Step 4: Train final model and save best_params study.best_params final_model xgb.XGBClassifier(**best_params) final_model.fit(X_train, y_train) joblib.dump(final_model, model/xgboost_webshell.model)注意clean_php_files()函数会主动解码base64_decode(...)中的字符串最多 2 层嵌套并将解码后的内容纳入特征提取——这使得模型能感知深层混淆如base64_decode(base64_decode(...))。但解码失败时如非法 base64会跳过不影响整体流程。3.4utils/目录三个实用工具解决真实运维痛点utils/batch_scan.py多进程扫描器支持-j 8指定并发数自动跳过.git/、node_modules/等排除目录utils/convert_to_csv.py将原始 Webshell 数据集txt 列表 文件夹转为标准 CSV 格式filepath,label含 MD5 校验列utils/feature_importance_plot.py用matplotlib绘制特征重要性条形图输出feature_importance.png供汇报使用。3.5model/目录不止一个 model 文件还有可验证的中间产物xgboost_webshell.model最终 pickle 模型feature_scaler.pklStandardScaler用于训练时特征标准化fit_transform预测时必须transform同一 scalerfeature_names.json17 个特征的英文名与中文解释映射确保show_features()输出可读cv_results.json5 折交叉验证的 precision/recall/f1 详细记录证明模型稳定性。3.6docs/目录不是 PDF 手册而是可执行的 Markdown 文档quickstart.md5 分钟跑通指南含pip install -r requirements.txt、python main.py --file ./samples/test.php验证命令feature_definition.md17 个特征的数学定义、计算示例、正常值范围如variable_entropy: 正常 4.0~5.5Webshell 3.2sample_analysis.md3 个典型样本的逐行分析shell.php,legit_plugin.php,obfuscated.jsp展示特征值如何驱动决策deployment_guide.md如何集成到 Jenkins添加构建后步骤、如何用cron每小时扫描/var/www/html。4. 文档说明的实操价值不是“说明书”而是帮你省下 3 小时排查时间的现场笔记4.1 文档结构设计按“人脑认知顺序”组织而非按文件目录docs/下的文档不按代码模块划分而是按用户任务流组织遇到问题 → 查troubleshooting.md比如ModuleNotFoundError: No module named php_parser直接告诉你pip install githttps://github.com/nikic/php-parser-py.git官方 PyPI 包已废弃要改特征 → 查feature_definition.md想新增preg_replace(/.*/e, ...)检测文档第 4.3 节给出正则表达式模板和测试用例要换模型 → 查model_customization.md如何用 LightGBM 替换 XGBoost文档提供train_lightgbm.py模板和参数对照表num_leaves≈max_depth * 2要上线 → 查deployment_guide.md包含 Nginx 配置片段限制.php文件上传大小、Linux systemd service 文件webshell-scanner.service、日志轮转设置。4.2 文档中的“防翻车”细节每个警告都来自真实事故关于 PHP 版本兼容性deployment_guide.md第 2.1 节“PHP 8.0 的token_get_all()返回T_NAME_QUALIFIEDtoken而本项目特征提取器仅识别T_STRING。若在 PHP 8.0 环境运行需在feature_extractor.py第 45 行将T_NAME_QUALIFIED映射为T_STRING否则function_name_count特征失效。我们已在test/php8_compatibility_test.py中覆盖此 case。”关于 Windows 路径问题quickstart.md第 3.2 节“Windows 用户运行python main.py --dir C:\inetpub\wwwroot时os.walk()返回路径含\但php-parser期望/。解决方案在main.py第 89 行添加filepath filepath.replace(\\, /)或直接使用pathlib.Path(filepath).as_posix()。”关于内存泄漏风险troubleshooting.md第 5.4 节“批量扫描 10000 文件时php-parser的 subprocess 调用未关闭 stdout/stderr导致ResourceWarning: unclosed file。修复在feature_extractor.py的php_tokenize()函数中subprocess.Popen(..., stdoutsubprocess.PIPE, stderrsubprocess.DEVNULL)后添加proc.stdout.close()。”4.3 文档的“可验证性”设计所有结论附带验证命令每个技术断言都配shell命令验证例如断言“at_symbol_count特征对eval()敏感对include()不敏感。”验证命令echo ?php eval(test); ? test1.php echo ?php include(test.php); ? test2.php python main.py --file test1.php | grep at_symbol_count # 应输出 1.0 python main.py --file test2.php | grep at_symbol_count # 应输出 0.0断言“variable_entropy在$a上为 1.0在$user_data上为 4.2。”验证命令echo ?php $a 1; ? a.php echo ?php $user_data []; ? user.php python main.py --file a.php --debug | grep variable_entropy python main.py --file user.php --debug | grep variable_entropy4.4 文档的“版本锁死”策略避免“我这能跑你那不行”requirements.txt不写scikit-learn1.0而是精确锁定scikit-learn1.2.2 xgboost1.7.5 pandas1.5.3 numpy1.23.5 php-parser0.1.0 # 注意这是 fork 版本非 PyPI 官方并在docs/version_compatibility.md中声明“本模型在 Python 3.8.10 / 3.9.16 / 3.10.12 下验证通过不支持 3.11因php-parser依赖的ply库未适配”“PHP CLI 必须 7.4 且 8.28.2 需手动 patchtoken_get_all()行为patch 文件见patches/php82_fix.patch”。4.5 避坑文档阅读的三个致命误区误区1跳过quickstart.md直接看feature_definition.md后果在没装php-cli的环境下尝试python main.py --file xxx.php报错FileNotFoundError: php以为代码 bug。正解quickstart.md开头就强调“第一步sudo apt install php-cli”并提供各发行版安装命令。误区2把sample_analysis.md当教学案例照抄后果复制shell.php样本到自己环境发现base64_count0因样本已被解码误以为模型失效。正解文档明确注明“sample_analysis.md中的样本已预处理解 base64、去空格原始样本见data/raw/webshell/其base64_count为 3”。误区3忽略deployment_guide.md中的 SELinux 提示后果在 CentOS 7 上部署为 systemd servicephp-parsersubprocess 被 SELinux 拦截日志显示avc: denied { execute } for commpython path/usr/bin/php。正解文档第 4.3 节给出两条命令sudo setsebool -P httpd_can_network_connect 1和sudo setsebool -P httpd_can_execmem 1并说明“此设置仅影响 Apache 子进程不影响系统安全基线”。5. 进阶技巧用特征向量做“溯源反推”把误报变成攻击线索5.1 从误报中提取攻击者指纹当模型说“这是 Webshell”但你怀疑是误报假设扫描发现wp-content/plugins/backup-manager/core/class-backup.php被判为 Webshellprediction1, confidence0.82人工审计未发现恶意代码。此时别急着调低阈值先用--debug模式看特征驱动python main.py --file ./wp-content/plugins/backup-manager/core/class-backup.php --debug # 输出 # feature: base64_count 0.0 # feature: at_symbol_count 0.0 # feature: variable_entropy 2.3 ← 异常低 # feature: longest_line 1284 ← 这行太长了 # feature: obfuscation_score 0.91variable_entropy2.3远低于正常 PHP 类文件通常 4.0说明变量名高度规律化如$a1,$a2,$a3longest_line1284暗示一行内有超长字符串。用sed -n 1284p class-backup.php定位该行发现是 Base64 编码的加密配置密钥——这并非后门而是插件作者为防密钥泄露做的混淆。但obfuscation_score0.91基于字符串熵和控制流扁平化计算触发了模型。此时你有两个选择短期在feature_extractor.py中为backup-manager插件添加白名单规则if backup-manager in filepath: features[obfuscation_score] 0长期收集 10 个同类加密插件重新训练模型让obfuscation_score与base64_count联合判断高混淆 无敏感函数调用 合法。5.2 用特征聚类发现新型 Webshell 变种模型预测只是二分类但 17 维特征向量本身是攻击行为的“数字画像”。对 500 个预测为 Webshell 的文件做 K-Means 聚类K5可发现Cluster 0182 个样本base64_count 2at_symbol_count 1→ 典型冰蝎流量Cluster 197 个样本variable_entropy 2.5string_avg_len 200→ 新型变量名混淆 大字符串 payloadCluster 263 个样本func_call_chain_max 5ast_depth 8→ 控制流扁平化类似 VMProtectCluster 341 个样本comment_ratio 0.3longest_line 50→ 用大量注释掩盖逻辑常见于钓鱼站Cluster 4117 个样本eval_count 0assert_count 1→ PHP 7.0 的assert后门。提示utils/cluster_analysis.py已内置此功能运行python utils/cluster_analysis.py --input webshell_predictions.csv --k 5自动生成聚类报告和每个簇的 top-3 特征热力图。你会发现 Cluster 2 的obfuscation_score平均值达 0.94而训练集里该簇样本仅 12 个——这意味着模型在泛化但你需要人工确认这是否是新变种。5.3 构建“特征-攻击技战术”映射表让安全分析从“是/否”升级为“怎么打”把特征值翻译成 MITRE ATTCK 技术形成可操作情报。例如特征组合ATTCK TacticATTCK Technique检测建议base64_count ≥ 2ANDat_symbol_count 1ExecutionCommand and Scripting Interpreter: PHP (T1059.006)检查$_POST/$_GET是否直接传入eval()variable_entropy ≤ 2.0ANDstring_avg_len ≥ 150Defense EvasionObfuscated Files or Information (T1027)提取长字符串尝试 base64/ROT13 解码func_call_chain_max ≥ 6ANDast_depth ≥ 10Defense EvasionVirtualization/Sandbox Evasion (T1497.001)检查是否有debug_backtrace()、get_included_files()等反调试调用comment_ratio ≥ 0.25ANDlongest_line ≤ 40Initial AccessSpearphishing Attachment (T1566.001)提取注释内容搜索邮箱、URL、社会工程关键词这份映射表不是凭空编造而是基于sample_analysis.md中 32 个真实样本的手动标注。当你看到新样本触发Cluster 2就能立刻执行“提取长字符串 → base64 decode → 检查是否为 shellcode”而不是从头逆向。5.4 一个后悔药如何用旧模型“回溯”新样本的决策逻辑某天你收到新 Webshell 样本revshell_2024.php模型判为 0正常但你直觉不对。此时不要重训模型而是用--explain模式深挖python main.py --file revshell_2024.php --explain # 输出 # Prediction: 0 (Normal) # Margin score: 0.12 (low confidence) # Top-3 features suppressing Webshell label: # - base64_count 0.0 (expected 1.0 for Webshell) # - at_symbol_count 0.0 (expected 0.5) # - eval_count 0.0 (expected 0.8) # Top-3 features supporting Webshell label: # - obfuscation_score 0.95 (high) # - variable_entropy 1.8 (low) # - longest_line 982 (very long)Margin 仅 0.12说明模型“拿不准”。obfuscation_score和variable_entropy在拉票但base64_count等硬特征为 0拖累了总分。这时你意识到攻击者改用gzinflate(str_rot13(...))绕过 base64 检测。解决方案短期在feature_extractor.py中新增gzinflate_count和str_rot13_count特征长期将obfuscation_score算法升级为基于pyminifier的代码压缩率 字符串熵联合计算utils/obfuscation_v2.py已提供原型。从那以后我每次收到新样本都强制走一遍--explain哪怕预测是 1。因为真正的威胁往往藏在 margin 分数里而不是二分类标签中。希望帮到你。本文还有配套的精品资源点击获取
