ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PHP原生CC防护系统:请求指纹+动态限流+验证码闭环

PHP原生CC防护系统:请求指纹+动态限流+验证码闭环 简介这是一套面向PHP开发者与Web安全初学者的轻量级CC攻击防护实践源码聚焦于解决PHP网站在高并发场景下易遭模拟请求式DDoS即CC攻击导致服务瘫痪的问题。资源共14个文件含7个核心PHP脚本如anti_ddos.php、securitecode.php、Verify_your_identity.php等分别承担请求限频、验证码校验、身份验证与黑名单拦截功能、6个说明类TXT文档含部署配置与使用教程、1个CSS样式文件用于验证页面UI整体仅39KB便于快速集成与二次开发。目前已有167人学习下载适合中小型PHP站点快速部署基础层防护或作为Web安全课程中“应用层攻击防御”模块的教学案例。读者可直接复用其IP频率控制逻辑、多级验证流程设计及异常行为识别思路并结合config.php灵活调整阈值参数深入理解从流量监控到响应拦截的完整防护链路。1. 这不是WAF而是一套可嵌入、可调试、可审计的PHP层CC防御逻辑链你刚上线一个用ThinkPHP写的会员系统凌晨三点收到告警首页QPS从80飙到2400但真实用户数为0Nginx access.log里全是/login.php?usernametestpassword123的重复GET请求User-Agent却写着“Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36”和你昨天测试时一模一样——这不是流量突增是有人在用Python脚本轮询撞库。此时打开宝塔面板看PHP进程php-fpm子进程占满CPU但netstat -an | grep :80显示连接数并不高。传统CDN或云WAF对这种应用层慢速攻击响应滞后而你手头只有FTP权限和一个能改index.php的后台。这套“简洁实用的PHP防被CC攻击网站系统源码”就是为这种场景设计的它不依赖外部服务不修改PHP核心配置仅靠include一行就能接入任意PHP站点所有防护逻辑运行在$_SERVER[REQUEST_TIME_FLOAT]精度下用file_put_contents()写日志、用session_start()做状态跟踪、用imagepng()动态生成验证码——它不是黑盒中间件而是你能逐行var_dump()、加断点、改阈值、导出日志的防御代码。它解决的是「合法请求洪流」场景下的资源耗尽问题当攻击者用100个IP每秒各发5次搜索请求总QPS才500远低于服务器承载上限但每个请求都触发完整MySQL查询模板渲染数据库连接池瞬间打满。这类攻击绕过基于IP频次的Nginx限流因IP分散也骗过基于User-Agent的简单过滤因UA伪造得像真浏览器。本源码通过三重校验闭环应对首次访问放行但埋securitecode.php种子二次高频请求触发Verify_your_identity.php弹验证码三次失败则写入config.php维护的内存级黑名单。整个流程不依赖Redis或Memcached纯PHP原生函数实现连gd扩展都做了降级兼容——哪怕你的虚拟主机禁用了imagettftext()它也能回退到字符拼接式验证码。适合两类人一是运维能力有限但需快速止血的中小站点开发者你只需把anti_ddos.php放在入口文件顶部调整config.php里的$max_requests_per_minute 30;即可生效二是安全研究者源码中core2.php封装了请求指纹生成算法md5($_SERVER[HTTP_USER_AGENT].substr($_SERVER[REMOTE_ADDR],0,7).$_SERVER[REQUEST_URI])start.php展示了如何用register_shutdown_function()捕获超时请求这些细节在商业WAF文档里从不会公开。2. 请求指纹建模与动态阈值控制为什么单纯IP限流在CC攻击中失效2.1 CC攻击的本质是“合法行为”的规模化滥用传统DDoS攻击靠UDP洪水或SYN泛洪消耗带宽和连接数而CC攻击Challenge Collapsar专攻应用层。它不追求高并发连接而是模拟人类操作节奏比如每3秒发起一次商品详情页请求每次携带不同utm_source参数User-Agent轮换Chrome/Firefox/Safari甚至加入随机Referer。Nginx的limit_req zonecc burst5 nodelay;对此类攻击效果极差——因为burst值设小了误杀正常用户如手机端加载图片JSCSS共4次请求设大了形同虚设攻击者开10个线程每个线程每分钟发60次请求总QPS才10远低于阈值。本源码放弃IP维度单一限流转而构建多维请求指纹核心逻辑在core2.php第42行function generate_request_fingerprint() { $ua $_SERVER[HTTP_USER_AGENT] ?? unknown; $ip $_SERVER[REMOTE_ADDR] ?? 0.0.0.0; $uri parse_url($_SERVER[REQUEST_URI], PHP_URL_PATH) ?: /; $query $_SERVER[QUERY_STRING] ?? ; // 关键剔除会频繁变动的参数保留业务关键路径 $clean_query preg_replace(/(utm_[^]|_t[^])/, , $query); $fingerprint md5($ua . substr($ip, 0, 7) . $uri . $clean_query); return $fingerprint; }提示substr($ip, 0, 7)取IP前7位而非全量是为了应对同一局域网用户如公司WiFi被误判。若你的站点主要面向国内移动用户建议改为substr($ip, 0, 3)匹配C类网段减少指纹碰撞。该指纹将“同一用户在相同UA下访问相同路径”映射为唯一字符串比单纯IP更精准。例如攻击脚本用100个代理IP刷/product?id123但UA固定为curl/7.68.0指纹全部相同触发统一限流而真实用户用不同手机访问/product?id123utm_sourcewechat和/product?id123utm_sourceqq因clean_query剔除utm参数指纹仍一致避免误伤。2.2 动态滑动窗口计数器的PHP原生实现anti_ddos.php第68行调用check_request_rate()函数其内部使用file_get_contents()读取./logs/rate_limit.log非数据库按指纹分组统计最近60秒请求数。关键在于它不依赖redis INCR而是用PHP数组文件锁实现原子计数function check_request_rate($fingerprint, $max_requests 30, $window_seconds 60) { $log_file ./logs/rate_limit.log; $now time(); $cutoff $now - $window_seconds; // 1. 读取并清理过期记录 $records []; if (file_exists($log_file)) { $content file_get_contents($log_file); $lines explode(\n, trim($content)); foreach ($lines as $line) { if (empty($line)) continue; list($ts, $fp) explode(|, $line, 2); if ((int)$ts $cutoff $fp $fingerprint) { $records[] (int)$ts; } } } // 2. 写入当前请求时间戳 file_put_contents($log_file, $now|$fingerprint\n, FILE_APPEND | LOCK_EX); // 3. 判断是否超限 return count($records) $max_requests; }注意FILE_APPEND | LOCK_EX确保多进程写入不覆盖。但高并发下file_get_contents()可能读到部分写入的脏数据因此实际生产环境建议将$window_seconds设为120秒用时间换一致性。若需更高性能可将此逻辑迁移到apcu_store()但需确认服务器启用了APCu扩展。该计数器与Nginx限流的根本区别在于它感知的是“业务行为”而非“网络连接”。当/api/order/create接口被刷单攻击时即使攻击者分散在1000个IP只要UA和路径相同所有请求共享同一个计数器立即触发防护。而Nginx对每个IP单独计数需配置limit_req_zone $binary_remote_addr配合limit_req指令且无法识别UA伪装。2.3 阈值参数的业务化调优方法config.php中定义了三组可调参数需根据业务特征调整参数名默认值调优依据典型场景示例$max_requests_per_minute30单个用户每分钟合理操作次数电商站搜索页设为15用户很少1分钟搜15次论坛发帖页设为3防灌水$captcha_trigger_count5触发验证码的连续请求数登录页设为3防爆破静态文章页设为20防爬虫$blacklist_duration_minutes1440黑名单保留时长分钟临时攻击设为60已知恶意IP段设为100807天调整后需验证效果用ab -n 100 -c 10 http://yoursite.com/模拟并发观察./logs/attack_log.txt中是否出现[BLOCKED] fingerprintxxx ip192.168.1.100。若正常用户被误拦检查generate_request_fingerprint()是否误剔除了关键参数如支付接口的order_id必须保留若攻击未被拦截增大$captcha_trigger_count并检查Verify_your_identity.php是否成功返回验证码图片用浏览器直接访问该文件测试。3. 验证码挑战机制与人机识别闭环设计3.1securitecode.php无依赖的GD库降级方案验证码是CC防护的关键闸门但很多共享主机禁用imagettftext()需指定字体文件路径或imageantialias()。securitecode.php采用三层降级策略首选方案用imagecreatefrompng()加载预置的./assets/bg.png作为背景imagestring()绘制字符无需字体文件备选方案若背景图不存在用imagecreate(120,40)生成纯色背景imagefilledrectangle()画底色兜底方案若GD扩展完全禁用返回纯文本验证码$_SESSION[captcha_code]明文写入前端用span显示核心代码在securitecode.php第89行// 尝试加载背景图 $bg_path ./assets/bg.png; if (file_exists($bg_path) function_exists(imagecreatefrompng)) { $im imagecreatefrompng($bg_path); } else { // 创建纯色背景 $im imagecreate(120, 40); $bg_color imagecolorallocate($im, 240, 240, 240); imagefilledrectangle($im, 0, 0, 120, 40, $bg_color); } // 绘制干扰线仅当GD可用时 if (function_exists(imageline)) { for ($i 0; $i 5; $i) { $line_color imagecolorallocate($im, rand(150, 200), rand(150, 200), rand(150, 200)); imageline($im, rand(0, 120), rand(0, 40), rand(0, 120), rand(0, 40), $line_color); } } // 绘制字符 $code generate_captcha_code(); // 生成4位字母数字 $_SESSION[captcha_code] strtolower($code); for ($i 0; $i 4; $i) { $char_color imagecolorallocate($im, rand(50, 100), rand(50, 100), rand(50, 100)); imagestring($im, 5, 25 $i * 20, 12, $code[$i], $char_color); } // 输出图像 header(Content-Type: image/png); imagepng($im); imagedestroy($im);提示imagestring()的字体大小参数5对应内置字体无需额外文件。若发现验证码文字模糊将imagestring($im, 5, ...)改为imagestring($im, 4, ...)减小字号或增加imagesetthickness($im, 2)加粗线条。3.2Verify_your_identity.php带会话绑定的挑战流程验证码不是独立页面而是嵌入在防护链中的环节。当check_request_rate()返回trueanti_ddos.php会重定向到Verify_your_identity.php?redirect_url/target.php该文件执行三步操作会话校验检查$_SESSION[fingerprint]是否匹配当前请求指纹防止攻击者绕过挑战生成调用securitecode.php输出验证码并将$_SESSION[captcha_time] time()记录生成时间表单渲染输出含隐藏字段input typehidden namefingerprint value? $fingerprint ?的HTML表单关键安全设计在Verify_your_identity_LASTCHANCE.php最后一道防线当用户提交验证码后不仅校验$_POST[captcha]是否等于$_SESSION[captcha_code]还检查time() - $_SESSION[captcha_time] 3005分钟有效期且$_POST[fingerprint] $_SESSION[fingerprint]。这杜绝了验证码被截获后重放攻击。3.3 前端集成最小侵入式接入方案无需修改现有HTML只需在入口文件如index.php顶部插入?php // 防CC攻击入口 require_once ./anti_ddos.php; ?anti_ddos.php会自动检测请求路径对敏感接口/login.php,/api/等启用防护对静态资源.css,.js,.jpg跳过。若需自定义保护路径在config.php中修改$protected_paths [ /login.php, /register.php, /api/order, /search.php ];前端无需任何JS适配——验证码表单提交后Verify_your_identity.php处理成功则header(Location: {$_GET[redirect_url]})跳回原页面失败则刷新验证码。整个过程对用户透明仅增加一次302跳转延迟约50ms。4. 黑名单持久化与日志分析实战从防御到溯源4.1config.php驱动的内存文件双模黑名单黑名单存储在config.php定义的$blacklist_file ./config/blacklist.txt;中格式为IP|timestamp|reason例如192.168.1.100|1715234567|captcha_failed_3_times 203.204.205.206|1715234589|exceed_rate_limitanti_ddos.php在block_ip()函数中写入黑名单function block_ip($ip, $reason unknown) { $blacklist_file ./config/blacklist.txt; $entry $ip| . time() . |$reason\n; file_put_contents($blacklist_file, $entry, FILE_APPEND | LOCK_EX); // 同时写入内存数组本次请求有效 $_SESSION[blocked_ips][] $ip; }注意内存数组$_SESSION[blocked_ips]仅在当前会话有效重启PHP-FPM后清空文件黑名单永久生效。两者结合既保证实时性又避免重启丢失。4.2 日志解析用Linux命令快速定位攻击源./logs/attack_log.txt记录所有拦截事件格式为[TIMESTAMP] [ACTION] message。用以下命令快速分析# 统计近24小时被拦截最多的IP awk -F\\| /BLOCKED/ $1 systime()-86400 {print $3} ./logs/attack_log.txt | sort | uniq -c | sort -nr | head -10 # 查看某IP的详细攻击路径 grep 192.168.1.100 ./logs/attack_log.txt | tail -20 # 统计验证码触发次数判断是否被绕过 grep CAPTCHA_SHOWN ./logs/attack_log.txt | wc -l若发现某IP在blacklist.txt中存在但仍在请求检查其是否使用代理池轮换IP——此时需在config.php中启用$enable_user_agent_fingerprint true;将UA哈希加入黑名单键值。4.3 攻击模式识别从日志中提取业务层特征真正的CC攻击往往有业务特征。例如电商站被刷优惠券日志中会出现大量/api/coupon/use?codeABC123论坛被灌水则/api/post/create?content...高频出现。用grep提取关键参数# 提取所有coupon code grep /api/coupon/use ./logs/attack_log.txt | sed -n s/.*code\([^]*\).*/\1/p | sort | uniq -c | sort -nr # 提取POST内容长度判断是否机器生成 awk -F\\| /BLOCKED/ /POST/ {print length($4)} ./logs/attack_log.txt | sort -n | tail -5若发现code字段高度重复如ABC123出现500次说明攻击者未随机化参数可在core2.php的generate_request_fingerprint()中加入parse_str($_POST, $post_data); $fingerprint . md5(json_encode($post_data));强化指纹。5. 生产环境部署 checklist 与性能压测验证5.1 五步上线检查清单步骤操作验证方式风险提示1. 权限检查确认./logs/和./config/目录可写touch ./logs/test rm ./logs/test若失败chmod 755 logs config切勿7772. GD扩展验证运行php -m | grep gd若无输出联系主机商启用GD无GD则验证码降级为文本安全性下降3. 会话配置检查phpinfo()中session.save_path是否可写ls -ld $(php -r echo ini_get(session.save_path);)若不可写修改php.ini或在anti_ddos.php开头加session_save_path(./tmp);4. 敏感路径注册在config.php的$protected_paths中添加业务接口访问/login.php应触发防护访问/style.css不应触发漏加路径防护缺口多加静态资源影响性能5. 日志轮转创建logrotate配置/etc/logrotate.d/php-anti-ddoslogrotate -d /etc/logrotate.d/php-anti-ddos避免attack_log.txt无限增长撑爆磁盘5.2 Apache Bench压测对比防护开启前后的QPS变化用ab工具实测防护效果测试环境CentOS 7 PHP 7.4 Nginx# 关闭防护时基准测试 sed -i s/define(ANTI_DDOS_ENABLED, true)/define(ANTI_DDOS_ENABLED, false)/ anti_ddos.php ab -n 1000 -c 50 http://localhost/login.php # 开启防护后测试同一脚本 sed -i s/define(ANTI_DDOS_ENABLED, false)/define(ANTI_DDOS_ENABLED, true)/ anti_ddos.php ab -n 1000 -c 50 http://localhost/login.php典型结果无防护QPS 120平均响应时间 85ms错误率 0%有防护QPS 95-21%平均响应时间 112ms32%错误率 0%但./logs/attack_log.txt新增12条[BLOCKED]记录关键结论性能损耗集中在验证码生成环节securitecode.php的GD运算若QPS下降超30%需启用$enable_captcha_cache true;在config.php中让同一指纹5分钟内复用验证码图片减少GD调用。5.3 误报率验证用真实用户行为脚本模拟编写test_normal_user.php模拟正常操作?php // 模拟用户首页-搜索-查看详情-加入购物车 $urls [ http://localhost/, http://localhost/search.php?qphone, http://localhost/product.php?id123, http://localhost/cart.php?actionaddid123 ]; foreach ($urls as $url) { $ch curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_COOKIEJAR, cookie.txt); curl_setopt($ch, CURLOPT_COOKIEFILE, cookie.txt); curl_exec($ch); curl_close($ch); usleep(200000); // 间隔200ms } ?运行10次检查./logs/attack_log.txt中是否有NORMAL_USER_BLOCKED记录。若有说明$max_requests_per_minute设得太低需上调至业务允许的峰值。最后打开./logs/attack_log.txt找到最新一条[BLOCKED]记录复制其IP然后执行grep 192.168.1.100 ./logs/attack_log.txt | head -5 | awk -F| {print $4} | sort | uniq -c | sort -nr | head -1这条命令将输出该IP最常访问的URL路径它很可能就是攻击者瞄准的业务接口。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进