简介:这是一套轻量级PHP Web应用层入侵检测与防御系统(IDPS)源码,面向Web安全初学者、PHP开发者及渗透测试学习者,聚焦SQL注入、XSS等常见攻击的实时识别与拦截。资源共6个文件,含核心防护逻辑脚本(waf.php)、攻击规则库(waf.rules)、运行日志(waf.log)、说明文档(README.md)、计数统计文件(count.txt)及开源许可证(LICENSE),总大小仅11KB,结构精简,便于快速部署与代码级理解。已有344人下载学习,适合用于本地环境搭建WAF实验、分析规则匹配机制、调试请求过滤流程,或作为教学案例深入剖析PHP层面的安全防护实现原理。 做安全运维这些年,我手里管着几十个业务站点,其中不少是PHP的老项目。站点一多,各种幺蛾子就都来了:凌晨两三点被脚本批量扫接口、后台登录接口被人拿字典撞库、论坛里突然冒出一堆机器注册的垃圾账号、某个页面莫名被刷了几十万次请求直接把数据库拖垮。以前我的处理方式很原始——发现问题,手动去查nginx日志,定位IP,加到防火墙黑名单,运气好能撑几天,运气不好隔天又换一批IP来。直到我拿到这样一份"基于PHP的入侵检测与防御系统源码",才真正把"被动救火"变成了"自动防御"。
这个项目最打动我的地方,是它把检测、告警、封禁、解封这一整套流程全部用PHP实现了,不依赖昂贵的硬件WAF,也不需要单独部署一套ELK,只要你的生产环境有PHP和MySQL/Redis,就能把它架起来。对于预算有限的中小团队、个人站长,或者想在应用层做一层纵深防御的运维同学来说,这份源码的参考价值非常高。本文我会从系统设计思路、核心模块实现、部署配置细节、上线后的踩坑排障到二次开发方向,逐层拆开来讲,尽量让看完的人能自己动手改出一套顺手的东西。
1. 先搞清楚这系统到底解决什么问题
1.1 中小型PHP站点的安全痛点到底是什么
与其讨论那些天马行空的APT攻击,不如先看看中小站点每天真实面临的压力。根据我这几年看过的访问日志,威胁大概能分成这么几类:一是扫描探测,就是那种UA很随意、每秒发几十个请求、专门试探/admin、/.env、/wp-login.php这类路径的东西;二是身份威胁,比如登录接口被刷、验证码被绕过、撞库尝试;三是业务滥用,注册机刷账号、秒杀脚本抢库存、爬虫把价格接口抓穿;四是请求层面的漏洞利用尝试,比如在URL参数里拼接特殊字符、提交超长字段、上传伪装文件等。
传统做法里,Nginx层能做的配置有限,limit_req可以限制IP速率,但太粗粒度,误伤很严重;云服务商的安全组能挡大流量攻击,但对应用层这种小流量高频率的"小动作"基本没感知。这套PHP系统的定位恰好弥补了这个空档:它站在应用入口和日志数据之上,用规则加行为的方式识别异常,再执行动态封禁。它不是要把攻击者一网打尽,而是要大大抬高攻击成本,让脚本小子跑到你站点时感受到明显阻力。
1.2 为什么检测层适合用PHP来写,而不是Java或Go
看到"PHP写的入侵检测系统",很多人第一反应是不靠谱。性能呢?常驻内存呢?其实这取决于系统架构。这套源码的核心思路并不是用PHP去抓网络流量包,那确实不是PHP的强项;它的正确打开方式是做"应用请求链路的旁路分析"和"轻量前置拦截"。
一方面,PHP天然就在每个Web请求的调用链上,通过auto_prepend_file或者在框架入口处加载一个检测脚本,它能拿到最完整的上下文:当前请求的URI、全部参数、请求头、Cookie、用户会话状态。这些信息比Nginx层的WAF模块要丰富得多。另一方面,很多中小站点的技术栈就是LNMP,团队里没有专职的安全工程师,但一定有人会PHP——那维护这套系统的门槛就大大降低了。日志格式改一下、规则文件加一条正则、封禁名单导出,这些操作用PHP处理非常顺手。
注意,这里说的"检测"是防御性质的请求分析和异常识别,面向的是保护你自己的站点,所有规则和动作都以阻断恶意流量、保护业务数据为目标。
1.3 这套源码适合什么人、能落到什么场景
如果你属于下面这几类人,这份源码值得认真读一遍:
- 管着若干PHP站点但没有预算上商业WAF的运维/全栈工程师;
- 被爬虫、扫描器、撞库搞得头大的个人站长;
- 想给自己的PHP框架(比如ThinkPHP、Laravel)加一层安全中间件的开发者;
- 安全方向入门,想搞清楚"检测系统到底是怎么运作"的学习者。
我是在一台2核4G的测试服务器上把它完整跑起来的,后面又接入了两个日活不到两万的小站点做压测。结论是:在合理配置下,它对正常业务请求的延迟影响可以控制在几毫秒级别,完全可接受。接下来我详细拆一下它的核心设计。
2. PHP做检测和防御,技术上到底依赖什么
2.1 检测数据从哪来:Access日志、请求参数、行为指纹
没有数据,检测就是空中楼阁。这套系统的数据来源主要有三个通道,需要重点关注。
第一个通道是Web服务器访问日志。Nginx的access_log天生就是一座金矿——每条记录里有客户端IP、请求时间、请求行、状态码、UA、Referer。系统通过一个定时任务去增量读取日志文件,解析后写入MySQL或者直接推给检测引擎分析。日志分析的优势是可以做"事后追查",比如发现某个IP在过去五分钟内请求了200次、404比例超过60%、命中规则库三次,这种状态型判断只有把日志聚合起来才能做。
第二个通道是请求时实时拦截数据。系统通过PHP的auto_prepend_file机制,在每个请求执行业务代码之前先跑一遍检测逻辑。在这个阶段,可以拿到$_SERVER、$_GET、$_POST、$_COOKIE、$_FILES这些超全局变量,实时判断当前请求是否带有明显的恶意特征,比如可疑的脚本执行片段、异常编码、路径穿越模式、极端参数长度等。命中高危规则时直接http_response_code(403)并终止请求,实现秒级阻断。
第三个通道是业务侧的"行为指纹"。这部分不是源码默认的,但它留了接口。比如登录模块可以在密码错误连续三次后调用检测系统接口,上报LOGIN_FAIL事件;订单模块可以上报ORDER_ABNORMAL事件。检测系统把这些事件当成离散信号,汇入IP行为画像库——当某个IP同时具备"高频访问+大量登录失败+命中扫描特征"三个条件时,自动触发封禁策略。这就是行为检测的基本形态,比单纯特征匹配高级不少。
2.2 规则引擎的匹配思路:正则规则库与黑白名单
这套系统的核心之一是规则引擎。源码里默认带了一份rules.php配置文件,里面按照攻击类别分组成数组,每组包含规则名、匹配目标字段、正则表达式、危险等级、处置动作。例如:
return [ 'scan' => [ ['name' => '敏感路径探测', 'field' => 'uri', 'pattern' => '/\.(env|git|svn|bak|sql)$/i', 'level' => 5, 'action' => 'block'], ['name' => '常见管理路径', 'field' => 'uri', 'pattern' => '/\/(admin|phpmyadmin|manager|console)(\/|$)/i', 'level' => 3, 'action' => 'challenge'], ], 'inject' => [ ['name' => 'SQL注入特征', 'field' => 'query', 'pattern' => '/(\bselect\b.*\bfrom\b)|(\bunion\b.*\bselect\b)|(\binsert\b.*\binto\b)/i', 'level' => 8, 'action' => 'block'], ['name' => 'XSS尝试', 'field' => 'all', 'pattern' => '/(<script|javascript:|onerror\s*=)/i', 'level' => 6, 'action' => 'filter'], ], ];引擎在匹配时会对每个请求的URI、QueryString、POST关键字段逐一跑这些正则。命中后根据规则等级决定是放行、记录、弹验证码还是直接封禁。这套设计看起来不复杂,但胜在"规则即配置",改起来非常灵活。比如某天公司业务需要一个正常访问/admin的内部工具,管理员直接在配置里把那条规则的action改成log即可,不用动代码。
2.3 动态防御闭环:识别、告警、封禁、解封
我认为这套系统最有价值的设计,是它把整个处置流程闭环了,而不是只做警报器。完整链路可以概括为:
- 数据采集层把请求信息和日志信息汇聚到检测队列;
- 检测引擎按规则和策略给每个请求/IP打分;
- 分数超过阈值或命中高危规则的,写入Redis的封禁集合,也就是动态黑名单;
- 封禁不是永久的,默认策略下IP会被封15分钟、1小时、24小时,时间到了自动解封。对于永久放黑名单的,可以人工在后台确认;
- 所有处置动作都会生成告警事件,通过邮件、钉钉/企微机器人推给管理员。
这个闭环解决了传统方法的痛点:以前封IP靠手工,封完容易忘,误封了用户也没处申诉。有了自动解封机制,封禁操作变得可逆、可审查,误伤造成的影响降到最低。
3. 源码核心模块拆解与关键实现
拿到源码后,我们先看目录结构。一个典型版本大概是这样的:
. ├── agent/ # 部署在业务服务器上的采集/拦截端 │ ├── prepend.php # auto_prepend_file 加载的入口 │ └── send_event.php # 事件上报接口 ├── server/ # 中心服务器(或同机部署) │ ├── analyzer/ # 检测引擎 │ │ ├── rule_engine.php │ │ ├── score_calc.php │ │ └── behavior.php │ ├── action/ # 处置执行器 │ │ ├── blocker.php │ │ ├── challenger.php │ │ └── notifier.php │ ├── console/ # Web管理后台 │ │ ├── index.php │ │ ├── rules.php │ │ └── events.php │ └── cron/ # 定时任务 │ ├── parse_log.php │ └── auto_unblock.php ├── config/ │ ├── config.php │ └── rules.php └── install.sql下面挑几个关键模块说明实现思路。
3.1 请求采集层:prepend.php 的前置拦截
这个文件是整个系统的第一道哨兵。通过在PHP配置文件中设置:
auto_prepend_file = /data/www/ids-agent/prepend.php每个PHP请求执行前都会加载它。采集层动作要尽量轻,只做三件事:快速检查Redis中当前IP是否在黑名单中;收集IP、UA、URI、关键参数摘要;调用检测接口进行快速匹配。如果Redis读取不到,就走"先放行、异步分析"的策略,绝不让业务因为检测系统故障而挂掉。这里要提醒,线上环境建议用Redis的GET命令,一次网络开销大概0.1毫秒级,连查带判不会拖慢接口。
<?php // 简化版 prepend.php 核心流程 $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; // 1. 检查黑名单缓存 $blocked = $redis->get('ids:block:' . $ip); if ($blocked) { http_response_code(403); header('Content-Type: application/json; charset=utf-8'); echo json_encode(['code' => 403, 'message' => 'Request blocked by security policy']); exit; } // 2. 快速规则检测(只跑高危规则集) $risk = fast_check($ip, $_SERVER['REQUEST_URI'] . '?' . http_build_query($_GET)); if ($risk >= 8) { $redis->setex('ids:block:' . $ip, 900, 1); // 记录事件 $redis->lpush('ids:events', json_encode([ 'ip' => $ip, 'time' => time(), 'rule' => $risk['name'], 'action' => 'block' ])); http_response_code(403); exit; }3.2 检测评估层:多维度打分,不只是命中一条规则
单纯的规则命中很容易产生误报。比如一个正常用户搜索"select from users",可能因为带了select和from就被误判为SQL注入尝试。这个源码的处理方式是引入了"多维度打分":单个低危特征命中不动作,但多个特征叠加时,风险分数就会累积。
打分维度包括:
- 规则命中次数与命中等级;
- 单位时间内的请求频次和请求分布(比如连续请求20次以上、资源类型高度集中);
- 请求熵值异常度,比如URL参数名全是随机字符串;
- 历史黑名单关联度,比如该IP曾有过封禁记录,这次又出现异常行为。
分数超过阈值后,系统会升级处置动作。这个思路非常实用。实际生产环境里,把单条规则阈值调低会导致大量误报,调高则漏报严重;而多维度打分是降低误报漏报的有效折中方案。我在部署时把默认阈值从10调整到了12,业务误伤明显减少。
3.3 响应处置层:封禁策略与验证码挑战
处置动作是分级设计的。源码里我印象比较深的是"三级响应":
log只记录,用于收集可疑事件;challenge返回一个JS挑战页(类似简易验证码),需要访问者执行一段JS计算出结果后才能继续访问。这能有效拦掉绝大多数简单脚本,同时不打扰真实用户;block直接拒绝并封禁IP一段时间。
这里值得说说challenge的设计。它本质上是一个"人类行为证明",但不需要用户输入复杂的验证码,而是页面自动执行JS、几毫秒后跳转回原地址。真实浏览器用户无感知,而不会执行JS的爬虫脚本就会被卡住。源码里这部分是用PHP生成一段带token的JS逻辑,token有五分钟有效期,服务端用HMAC做签名校验。
3.4 管理后台与报表:知道系统在干什么
管理后台做得比较朴素,但功能很全:实时事件流、IP封禁列表、规则管理、告警记录、基础统计。我在实际运营中最常用的是两个页面:事件流和封禁列表。事件流能告诉我"此刻有哪些IP在撞墙",封禁列表能让我快速甄别有没有误封正常用户。
这里提醒一点:如果有条件,建议把管理后台单独放到一个内网地址或者加访问白名单,不要暴露在公网。我在测试时图省事直接开了公网访问,结果第二天后台登录页就收到了几百次密码尝试——安全系统本身成了攻击目标,这就很讽刺了。
4. 部署时的配置细节,直接影响检测效果
部署这套系统不难,但要让它真正好用,有几个细节必须花时间调。
4.1 Nginx日志格式与日志解析对齐
日志解析模块能不能发挥威力,取决于access_log格式。源码自带了一个解析器,它假设日志的字段顺序是固定的。如果直接套用默认配置,很容易出现字段错位。我建议在Nginx里单独定义一个日志格式,专门给检测系统用:
log_format ids_log '$remote_addr [$time_local] "$request" ' '$status $body_bytes_sent "$http_user_agent" "$http_referer"';然后在检测系统的config.php里把字段索引配置好:0是IP,1是时间,2是请求行,3是状态码,等等。这样解析的时候直接按索引切割字符串,效率最高。
日志解析的定时任务频率也要根据访问量来设。我建议用cron每30秒跑一次(避免太频繁),然后用filepos变量记录上次读到的文件偏移量,这样每次只解析新增部分,不会重复消耗IO。
4.2 规则库调整的优先级:先处理误报,再追求检出率
配置规则的顺序建议遵循"先消除误报,再提高检出"的原则。我在第一次上线时直接启用了全部规则,结果半小时内收到大量告警,一看全是误报:
- 公司内部监控系统每10秒访问一次健康检查接口,被当成高频访问;
- 运营同事在后台管理页面搜索带
%符号的订单号,被当成SQL特征; - 某个客户用老版本IE访问,UA里带了奇怪的编码,被当成异常UA。
后来我把处理顺序调整为:先把所有action=block的规则临时降为log,跑两天,分析日志中命中的真实样本,把正常业务请求的规则命中样本全部加白名单或禁用,再把真正的恶意流量收敛为challenge或block。这样迭代了两周,误报率降到了可以接受的水平。
4.3 Redis与MySQL各司其职
这套系统的数据存储分两层:Redis做热数据存储,MySQL做持久化归档。Redis里主要放IP封禁集合、事件队列、请求计数滑动窗口;MySQL里存规则配置历史、封禁历史、告警记录。为什么这样分?因为封禁判断是高频操作,必须用内存级存储;而审计和报表是低频操作,可以落到磁盘。部署时要注意给Redis设置合理的maxmemory和过期策略,否则滑动窗口的数据会积累太多。我设置为64MB上限、allkeys-lru淘汰策略,运行了几个月没有出过问题。
5. 上线后一定会踩的坑和我的排查经验
老实讲,真正让这套系统"能用"的不是源码本身,而是上线后那段不断踩坑、调优的过程。我把最值得分享的几个问题列出来,希望能帮后来人省点时间。
5.1 误封正常用户:当用户的网络出口IP是个大池子
这是最头疼的问题,没有之一。移动、电信家宽的出口IP经常是几百人共享的,一旦其中一个人触发封禁,整个池子的用户都访问不了你的站点。我当时遇到的情况是:某个地区的用户集中反馈站点打不开,排查发现系统把这个地区的一个出口IP段封了。
处理办法是加"白名单旁路"逻辑——对国内主要运营商的家宽IP段,不做block,只做challenge。因为正常用户即使被challenge也能立刻继续访问,而脚本攻击者遇到challenge基本就失败了。这个机制有效降低了投诉量。源码里没有这个设计,我是改了action/blocker.php判断逻辑加上的。
5.2 PHP进程被日志解析拖死:别在Web请求里干重活
我第一次把日志解析任务放在了一个页面路由里,想着手动触发方便。结果访问量上来后,解析几十万行日志用掉了五六秒,直接把那个PHP-FPM进程占满了,后续请求排队,站点变慢。后来我把解析逻辑改成独立的CLI脚本,通过php cli/parse_log.php方式运行,设置30秒超时,并允许与Web进程池完全隔离。这才彻底解决。
这里面有个通用原则:凡是耗时任务,都要搬出Web请求链路,用CLI、消息队列或Cron来实现。检测系统尤其要注意,它本身是保护业务、降低延迟的,自己不能成为瓶颈。
5.3 规则配置与业务逻辑冲突:正则匹配到的内容有业务含义
你的业务里总有那么一些不走寻常路的字段。我遇到过用户昵称里带<script的(单纯觉得酷),订单备注里带union select的,还有上传文件名里带.php的(测试用例)。这些全部会被规则引擎拦下来。排查时我打开事件列表,发现又是同一批请求,进一步对比会看到命中规则名称密密麻麻。
针对这类问题,我给参数检测模块加了一个"字段白名单字典":某些业务字段跳过规则检测,但依然做长度和类型校验。同时把检测目标分成两层:全局强制规则(URL路径、UA、频次)和保护特定接口的规则(登录、上传、支付)。这样一来,业务字段的误报被隔离在局部,不会影响全局拦截能力。
5.4 为什么只看规则是不够的:规则之外的异常模式
纯规则引擎最大的盲区是"看起来正常的恶意行为"。比如攻击者用一个正常UA、每次访问间隔随机、每次只试探一个路径,按特征规则它完全不命中任何一条,但综合看它每天的访问路径分布非常分散、从不停留在真实页面、只访问不存在的高价值路径——这种模式用规则很难表达,但用行为统计可以看出来。
源码默认只做了最基本的请求频次统计。我的二次改造里增加了一个简单的"路径熵分析":为每个IP建立一个访问路径集合,如果集合内路径的命名模式熵值异常高——也就是路径之间几乎没有语义关联,且大量404——就标记为扫描行为。这种方法抓到了好几只漏网之鱼。
6. 基于源码的二次开发方向上的一些建议
如果只是按部就班跑起来,这套系统能提供基本的检测封禁能力;但要做深做强,还得根据自己业务环境做二次开发。下面几个方向我觉得最值得投入。
6.1 引入轻量基线学习,降低规则维护成本
规则库是静态的,业务流量是动态的。与其频繁手动调规则,不如加一个简单的基线模块:每周自动统计站点各接口的正常流量特征(平均QPS、TOP100路径、UA分布),生成基线数据;检测引擎在判断异常时,先对比基线。如果某条规则的命中在业务高峰时间窗口内且来自多个正常用户,就自动降权;如果来自单IP且偏离基线,就升权。这样规则能自适应,误报会明显减少。我实现了一个简化版,用的是RPOPLPUSH式的滑动窗口,再加一个KMeans雏形做IP行为聚类,效果比纯静态规则提升了一个量级。
6.2 与现有运维工具的联动
检测系统最烦人的是封禁动作分散在各业务服务器上,没有统一入口。我建议把封禁执行器从"写Redis"抽象成一个可插拔的Interface,实现类可以对接不同的运维平台:
- 云服务器安全组API,封禁直接下发到网关层;
- 自建Nginx集群,通过共享内存或者推送配置到OpenResty的
lua_shared_dict; - 堡垒机/运维系统,形成封禁工单。
这样封禁能力从单机Redis升级成了整个基础设施的安全能力,也方便和安全审计流程对接。
6.3 把告警通知做准:减少噪音比增加通知更重要
运维久了就明白,告警消息太多等于没有告警。源码默认的告警逻辑是所有等级3以上的事件都推送,结果一天能推几百条,群里消息直接被刷屏。我给告警模块加了"事件聚合"逻辑:相同IP、相同规则的告警,在10分钟内合并成一条,并在消息里附带这个IP累计触发的次数、最早和最晚时间。这样管理员看到的是一条有聚合信息的告警,而不是刷屏。这条逻辑改动虽然不大,但实际体验好了非常多。
最后聊点实际的体会
整套系统从部署到稳定跑了两个多月,我最深的感受是:不要迷信任何安全产品,包括自己搭的这套东西。它真正帮你解决问题的方式,是把你从"手工救火"中解放出来,但前提是你得理解它的判断逻辑和边界。PHP写入侵检测系统,听起来不像主流方案,但在中小业务场景下,它确实能以很低的成本换来明显提升的安全水位。你不需要一上来就上很重的架构,先用这份源码把"检测-封禁-告警"的闭环跑通,再逐步把事情做得更细,这条路是走得通的。
如果你也准备在自己的站点上试试这套系统,我的建议是:先在测试环境完整跑两天,把所有规则调成log模式,看看它会抓到什么;然后挑出真正的威胁,逐条调整处置动作;等你对规则库有了手感,再切到block和challenge模式。安全系统的价值不在于拦住了多少东西,而在于它帮你挡住了那些原本会悄悄消耗你精力、数据甚至信誉的麻烦——这个过程,值得你亲自动手试一遍。
本文还有配套的精品资源,点击获取