Swoole协程中SQL注入扫描更难拦截,因协程复用Worker导致IP限流失效、日志异步冲刷掩盖攻击特征;须在onRequest统一提取所有输入、白名单正则过滤高危模式、立即403响应,并禁用PDO模拟预处理、用Redis协程客户端做IP频控。

为什么Swoole协程里SQL注入扫描更难拦
协程环境本身不放大注入风险,但会让扫描行为更隐蔽、更难被传统WAF识别:协程复用Worker进程,IP限流在连接层失效;攻击者用100个协程并发发SLEEP(3)或UNION SELECT 1,2,3类探测请求,单个IP每秒几十次,却不会触发Nginx层的limit_req——因为Swoole根本没走Nginx的请求队列,它直接接管了TCP连接。
更麻烦的是,协程内日志打点常被异步冲刷,攻击特征(如SELECT * FROM users WHERE name = 'admin' OR '1'='1')可能还没落盘就被下一个请求覆盖。
onRequest里必须做的三件事
所有SQL注入扫描都始于HTTP请求,onRequest是唯一能统一拦截的入口。别等进路由再处理——那时PDO可能已经prepare了。
- 提取全部用户输入字段:
$request->get、$request->post、$request->server['QUERY_STRING'],连Host和Referer头也扫一遍(有人往Host里塞localhost' UNION SELECT @@version--) - 用白名单正则快速过滤高危模式:匹配
/[\'\"\`]\s*(union|select|insert|update|delete|drop|sleep|benchmark|information_schema)/i,注意加\s*防空格绕过 - 对命中规则的请求,立刻
$response->status(403)->end(''),不记录详细错误,也不调任何DB操作——避免给攻击者反馈
PDO预处理必须关掉模拟模式
即使你在onRequest漏了一条,PDO::ATTR_EMULATE_PREPARES = false仍是最后防线。Swoole协程中若开启模拟预处理,PHP会把参数拼进SQL字符串再发给MySQL,等于自己造了个注入口。
检查你的PDO实例初始化代码:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false, // 必须为false
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);常见坑:
- 用
execute([$value])传参时,PHP可能把null或数字字符串当字符串处理,绕过类型约束 → 改用bindValue($param, $value, PDO::PARAM_STR) - 表名、字段名动态拼接未走白名单校验 →
in_array($table, ['user', 'order'], true)比正则/^[a-z_]+$/可靠得多
高频扫描的实时响应要靠Redis计数器
单纯靠onRequest正则只能拦已知模式,对变形编码(如%27%20UNION%20SELECT)或分段注入无效。得用行为分析补位。
在onRequest开头加一段协程安全的计数逻辑:
$ip = $request->server['remote_addr'];
$key = "sqlscan:{$ip}";
$cnt = $redis->incr($key);
$redis->expire($key, 60); // 60秒窗口
if ($cnt > 15) {
$response->status(429)->end('Too Many Requests');
return;
}关键点:
- 别用
file_get_contents或本地数组计数——协程间不共享内存,计数会失效 - Redis必须用Swoole协程版客户端(如
Swoole\Coroutine\Redis),否则阻塞整个Worker - 阈值设15不是拍脑袋:正常用户手动测试最多3~5次,扫描器首轮探测通常10~20次
真正容易被忽略的是连接池里的PDO实例复用——一个被污染的PDO连接如果没重置状态,后续协程拿去执行prepare()可能直接报MySQL server has gone away,反而暴露了服务端细节。每次从连接池取连接后,务必先$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)重置行为。

















