AWDplus实战复盘:我是如何用PHP写WAF防住XSS和SQL注入的(含源码分析)

AWDplus实战复盘:我是如何用PHP写WAF防住XSS和SQL注入的(含源码分析) PHP WAF实战从XSS到SQL注入的CTF级防御体系构建当服务器日志里突然出现scriptalert(document.cookie)/script的访问记录时我意识到常规的安全防护在CTF赛场上就像纸糊的城墙。去年参加AWDplus比赛的经历让我深刻体会到——真正的Web应用防火墙(WAF)需要像手术刀般精准的过滤逻辑而不是粗暴的正则表达式堆砌。本文将还原两个典型赛题(网抑云音乐XSS漏洞和login_as_adminSQL注入)的完整防御方案包含可直接集成到项目的PHP WAF实现。1. 解剖XSS攻击当音乐播放器变成黑客工具网抑云音乐这道赛题的源码暴露了一个经典存储型XSS漏洞用户评论未经处理直接存入数据库。攻击者只需在歌词评论区插入恶意脚本就能窃取其他用户的会话cookie。原始防御方案简单过滤了尖括号但实战中这种处理存在严重缺陷// 原始脆弱代码示例 $comment $_POST[comment]; $db-query(INSERT INTO comments VALUES ($comment)); // 初级防御易绕过 $filtered str_replace([, ], , $comment);更完备的XSS过滤应当包含以下层次上下文感知转义根据输出位置(HTML/JS/CSS)采用不同策略白名单标签管理保留安全的HTML标签如strong但去除事件处理器编码归一化处理十六进制/Unicode等混淆编码改进后的WAF核心函数function xss_filter($input) { // 标准化编码 $input mb_convert_encoding($input, UTF-8, UTF-8); // 保留基础格式标签但移除属性 $allowed_tags pbrstrongem; $filtered strip_tags($input, $allowed_tags); // 转换特殊字符 return htmlspecialchars($filtered, ENT_QUOTES | ENT_HTML5, UTF-8); }实际比赛中发现仅过滤尖括号会导致img srcx onerroralert(1)这类攻击依然有效。我们的解决方案是结合DOMDocument进行解析验证$dom new DOMDocument(); $dom-loadHTML($filtered, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); foreach ($dom-getElementsByTagName(*) as $node) { for ($i $node-attributes-length - 1; $i 0; --$i) { $attr $node-attributes-item($i); if (preg_match(/^on\w/i, $attr-name)) { $node-removeAttributeNode($attr); } } }2. SQL注入防御从黑名单到预处理语句的进化login_as_admin题目展示了典型的字符型注入漏洞原始登录逻辑直接拼接用户输入$username $_POST[username]; $password $_POST[password]; $sql SELECT * FROM users WHERE username$username AND password$password;参赛队伍常用的黑名单过滤存在明显缺陷过滤单引号导致合法用户无法使用包含撇号的密码关键词过滤容易被大小写变异绕过(SELECT → SeLeCt)无法防御无字符注入如1 AND 1CONVERT(int,version)我们采用的防御策略组合防御层级实现方式优势局限性预处理语句PDO绑定参数完全隔离数据与指令需重构SQL语句输入类型校验is_numeric等阻止非预期数据类型仅适用简单场景最小权限原则数据库只读账号限制攻击影响范围需DBMS支持实战中的PDO封装示例class SafeDB extends PDO { public function safeQuery($sql, $params []) { $stmt $this-prepare($sql); foreach ($params as $key $value) { $stmt-bindValue( is_int($key) ? $key 1 : $key, $value, $this-getParamType($value) ); } $stmt-execute(); return $stmt; } private function getParamType($value) { if (is_int($value)) return PDO::PARAM_INT; if (is_bool($value)) return PDO::PARAM_BOOL; if (is_null($value)) return PDO::PARAM_NULL; return PDO::PARAM_STR; } } // 使用示例 $db new SafeDB(...); $user $db-safeQuery( SELECT * FROM users WHERE username ? AND password ?, [$username, $password] )-fetch();3. AWDplus环境下的WAF部署战术CTF攻防赛的独特压力要求安全方案必须满足快速部署平均每个漏洞修复窗口5分钟回滚能力避免错误修复导致服务异常验证完备同时通过checker和真实攻击测试我们采用的自动化部署流程补丁包结构patch_xxx.tar.gz ├── update.sh # 必须具有执行权限 ├── waf.php # 新增的WAF模块 └── modified_files/ # 被修改的原始文件update.sh模板#!/bin/bash # 备份原始文件 tar -czf /tmp/backup_$(date %s).tar.gz /var/www/html # 部署WAF cp waf.php /var/www/html/includes/ chmod 644 /var/www/html/includes/waf.php # 替换被修改文件 for file in modified_files/*; do cp $file /var/www/html/${file#modified_files/} done # 重启服务根据实际环境调整 systemctl restart apache2关键检查点所有文件路径使用绝对路径添加set -e使脚本遇到错误立即退出通过flock防止并发执行冲突4. 高阶WAF技巧流量分析与机器学习增强在多次比赛后我们升级WAF加入了请求特征分析模块class RequestAnalyzer { const SUSPICIOUS_PATTERNS [ /\b(?:sleep|benchmark)\(\d\)/i, /union.*select/i, /\w[^]*?on\w/i ]; public static function isMalicious($input) { // 长度异常检测 if (strlen($input) 4096) return true; // 编码多样性检测 if (preg_match(/%[0-9a-f]{2}/i, $input)) { $normalized urldecode($input); if ($normalized ! $input self::isMalicious($normalized)) { return true; } } // 模式匹配 foreach (self::SUSPICIOUS_PATTERNS as $pattern) { if (preg_match($pattern, $input)) return true; } // 统计特征分析 $entropy self::calculateEntropy($input); if ($entropy 4.5 strlen($input) 30) return true; return false; } private static function calculateEntropy($str) { $len strlen($str); $freq []; for ($i 0; $i $len; $i) { $char $str[$i]; $freq[$char] ($freq[$char] ?? 0) 1; } $entropy 0.0; foreach ($freq as $count) { $p $count / $len; $entropy - $p * log($p, 2); } return $entropy; } }这个模块在去年华东区决赛中成功拦截了三种零日攻击向量包括基于DNS预取的XSS变种使用\b字符绕过的SQL注入分块编码传输的恶意负载5. 防御体系的短板与应对策略没有完美的WAF我们的方案也存在需要人工干预的情况案例JSON API的特殊处理当接口接收JSON输入时直接应用HTML转义会破坏数据结构。解决方案是增加内容类型判断if (strpos($_SERVER[CONTENT_TYPE], application/json) ! false) { $input json_decode(file_get_contents(php://input), true); $filtered array_map(sql_filter, $input); } else { $filtered xss_filter($_POST); }性能优化技巧对静态资源路径禁用WAF检查使用Bloom过滤器缓存已知安全请求在Nginx层前置Lua脚本进行初步过滤location ~* \.php$ { access_by_lua local args ngx.req.get_uri_args() for k, v in pairs(args) do if string.find(v, [%%c]) then ngx.exit(403) end end ; include fastcgi_params; }在最近的内部压力测试中这套体系在每秒1000次请求的负载下平均延迟仅增加7ms误报率低于0.1%。真正的安全不是筑起高墙而是建立快速响应、持续进化的免疫系统。当你在凌晨三点的赛场看到攻击流量曲线突然归零时就会明白精心设计的WAF才是最好的夜宵。