true, // 是否记录攻击日志 'enable_blocking' => true, // 是否自动封禁攻击IP 'max_param_length' => 500, // 参数最大长度(超过部分截断) 'max_logs_per_ip' => 100, // 单个IP最大攻击记录数 'block_duration' => 86400, // 封禁时长(秒),默认24小时 'attack_threshold' => 10, // 攻击阈值(超过此次数自动封禁) ]; /** * 处理请求 */ public function handle(Request $request, \Closure $next) { // 检查IP是否已被封禁 if ($this->isIpBlocked($request->ip())) { $this->logBlockedAccess($request); return $this->blockResponse(); } // 安全检查 $attackDetected = $this->checkSecurity($request); if ($attackDetected) { // 记录攻击并可能封禁IP $this->handleAttack($request, $attackDetected); // 返回通用错误,不泄露信息 return $this->errorResponse(); } return $next($request); } /** * 安全检查 */ protected function checkSecurity(Request $request): array { $attacks = []; // 检查GET参数 foreach ($request->get() as $key => $value) { if ($this->detectAttack($value, $key)) { $attacks[] = [ 'type' => $this->detectAttackType($value), 'param' => $key, 'value' => $value ]; } } // 检查POST参数 foreach ($request->post() as $key => $value) { if ($this->detectAttack($value, $key)) { $attacks[] = [ 'type' => $this->detectAttackType($value), 'param' => $key, 'value' => $value ]; } } // 检查请求头(可选) if ($this->checkHeaders($request)) { $attacks[] = [ 'type' => 'header_injection', 'param' => 'headers', 'value' => json_encode($request->header(), JSON_UNESCAPED_UNICODE) ]; } return $attacks; } /** * 检测攻击 */ protected function detectAttack($value, $key): bool { if (!is_string($value)) { return false; } // 检查SQL注入 if ($this->detectSqlInjection($value)) { return true; } // 检查XSS攻击 if ($this->detectXss($value)) { return true; } // 检查命令注入 if ($this->detectCommandInjection($value)) { return true; } // 检查路径遍历 if ($this->detectPathTraversal($value)) { return true; } // 检查文件包含 if ($this->detectFileInclusion($value)) { return true; } return false; } /** * 检测SQL注入 */ protected function detectSqlInjection(string $input): bool { $patterns = [ // Union注入 '/union\s+(all\s+)?select/i', // 注释符 '/--\s+|\/\*.*?\*\//s', // 延时注入 '/sleep\s*\(\s*\d+\s*\)/i', '/benchmark\s*\(.*?\)/i', '/waitfor\s+delay/i', // 条件语句 '/or\s+[\'"]?[\d\w][\'"]?\s*=\s*[\'"]?[\d\w][\'"]?/i', '/and\s+[\'"]?[\d\w][\'"]?\s*=\s*[\'"]?[\d\w][\'"]?/i', // 堆叠查询 '/;\s*(select|insert|update|delete|drop|create|alter|exec)/i', // 错误注入 '/extractvalue\s*\(|updatexml\s*\(/i', // 盲注 '/if\s*\(.*?,\s*.*?,\s*.*?\)/i', ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $input)) { return true; } } return false; } /** * 检测XSS攻击 */ protected function detectXss(string $input): bool { $patterns = [ // 脚本标签 '/]*>(.*?)<\/script>/is', '/javascript\s*:/i', '/on(load|error|click|mouse|key|focus|blur|submit)\s*=/i', // 事件处理(注意:属性名必须是完整的 onXXX,避免匹配到属性值中的 actionArg 等) '/<\w+\s+[^>]*\bon\w+\s*=\s*["\']?[^>]*>/i', // 数据协议 '/data\s*:/i', '/vbscript\s*:/i', // 框架/对象 '/]*>/i', '/]*>/i', '/]*>/i', '/]*>/i', ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $input)) { return true; } } return false; } /** * 检测命令注入 */ protected function detectCommandInjection(string $input): bool { $patterns = [ // 系统命令函数调用(更精确的匹配) '/(?:system|exec|shell_exec|passthru|proc_open|popen|pcntl_exec)\s*\(\s*[\'\"\$\w]/i', // 管道符后跟命令(避免匹配单纯的|字符) '/\|\s*(?:rm\s|cat\s|ls\s|wget\s|curl\s|nc\s|sh\s|bash\s)/i', // 分号后跟命令(避免匹配HTML实体如 ) '/;\s*(?:rm\s|cat\s|ls\s|wget\s|curl\s|nc\s|sh\s|bash\s|\$\w+)/i', // 危险命令(更精确的匹配) '/\b(?:rm\s+-(?:rf?|i)\b|cat\s+\/\w|wget\s+http|curl\s+http|nc\s+-[lv])\b/i', // 反引号包裹命令(避免匹配代码块中的反引号) '/`\s*(?:rm\s|cat\s|ls\s|wget\s|curl\s|nc\s|sh\s|bash\s)/i', ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $input)) { return true; } } return false; } /** * 检测路径遍历 */ protected function detectPathTraversal(string $input): bool { $patterns = [ '/\.\.\//', '/\.\.\\\/', '/\/etc\/passwd/i', '/\/proc\/self/i', '/\.\.%2f/i', '/\.\.%5c/i', ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $input)) { return true; } } return false; } /** * 检测文件包含 */ protected function detectFileInclusion(string $input): bool { $patterns = [ '/include\s*\(|require\s*\(|include_once|require_once/i', '/php:\/\/filter/i', '/phar:\/\//i', '/zip:\/\//i', '/expect:\/\//i', ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $input)) { return true; } } return false; } /** * 检查请求头 */ protected function checkHeaders(Request $request): bool { $suspiciousHeaders = [ 'x-forwarded-for' => '/^[\d\.\,]+$/', 'user-agent' => '', // 可以为空,但可以检查恶意UA 'referer' => '', // 检查可疑referer ]; foreach ($suspiciousHeaders as $header => $pattern) { $value = $request->header($header); if ($value && $pattern && !preg_match($pattern, $value)) { return true; } } return false; } /** * 检测攻击类型 */ protected function detectAttackType(string $input): string { if ($this->detectSqlInjection($input)) { return 'sql_injection'; } if ($this->detectXss($input)) { return 'xss'; } if ($this->detectCommandInjection($input)) { return 'command_injection'; } if ($this->detectPathTraversal($input)) { return 'path_traversal'; } if ($this->detectFileInclusion($input)) { return 'file_inclusion'; } return 'unknown'; } /** * 处理攻击 */ protected function handleAttack(Request $request, array $attacks): void { $ip = $this->getRealIp($request); // 记录攻击日志 foreach ($attacks as $attack) { $this->logAttack($ip, $request, $attack); } // 检查是否达到封禁阈值 if ($this->config['enable_blocking']) { $this->checkAndBlockIp($ip); } } /** * 获取真实客户端IP(支持Swoole模式) */ protected function getRealIp(Request $request): string { $ip = '0.0.0.0'; // 1. 优先从Swoole的header中获取 if ($this->isSwooleMode()) { // Swoole模式下,真实IP通常在x-real-ip或x-forwarded-for中 $ip = $request->header('x-real-ip', ''); if (empty($ip)) { // 如果有多个代理,x-forwarded-for是逗号分隔的列表,取第一个 $xff = $request->header('x-forwarded-for', ''); if (!empty($xff)) { $ips = explode(',', $xff); $ip = trim($ips[0]); } } } // 2. 如果Swoole模式获取失败,尝试常规方法 if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) { $ip = $request->ip(); } // 3. 验证IP格式 return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0'; } /** * 检查是否运行在Swoole模式 */ protected function isSwooleMode(): bool { return extension_loaded('swoole') && defined('SWOOLE_VERSION') && (php_sapi_name() === 'cli' || isset($_SERVER['SWOOLE_SERVER'])); } /** * 记录攻击日志到数据库 */ protected function logAttack(string $ip, Request $request, array $attack): void { if (!$this->config['enable_logging']) { return; } try { // 限制参数值长度 $paramValue = mb_substr($attack['value'], 0, $this->config['max_param_length'], 'UTF-8'); // 获取用户代理 $userAgent = $request->header('user-agent', ''); // 获取其他信息 $requestData = [ 'method' => $request->method(), 'url' => $request->url(), 'full_url' => $request->url(true), 'get_params' => json_encode($request->get(), JSON_UNESCAPED_UNICODE), 'post_params' => json_encode($request->post(), JSON_UNESCAPED_UNICODE), ]; // 使用参数绑定防止二次注入 Db::name('security_log')->insert([ 'attack_type' => $attack['type'], 'ip_address' => $ip, 'param_name' => $attack['param'], 'param_value' => $paramValue, 'user_agent' => mb_substr($userAgent, 0, 500, 'UTF-8'), 'request_method' => $request->method(), 'request_url' => mb_substr($request->url(), 0, 500, 'UTF-8'), 'request_data' => json_encode($requestData, JSON_UNESCAPED_UNICODE), 'create_time' => date('Y-m-d H:i:s'), 'update_time' => date('Y-m-d H:i:s') ]); // 更新IP攻击计数 $this->incrementIpAttackCount($ip); } catch (\Exception $e) { // 数据库写入失败时记录到文件日志 Log::error('安全日志记录失败: ' . $e->getMessage()); } } /** * 增加IP攻击计数 */ protected function incrementIpAttackCount(string $ip): void { $key = 'security:attack_count:' . $ip; $count = Cache::inc($key, 1); // 设置24小时过期 if ($count === 1) { Cache::expire($key, 86400); } } /** * 检查并封禁IP */ protected function checkAndBlockIp(string $ip): void { $key = 'security:attack_count:' . $ip; $count = Cache::get($key, 0); if ($count >= $this->config['attack_threshold']) { $blockKey = 'security:blocked_ip:' . $ip; Cache::set($blockKey, 1, $this->config['block_duration']); // 记录封禁日志 Log::warning('IP被封禁', [ 'ip' => $ip, 'attack_count' => $count, 'block_duration' => $this->config['block_duration'] ]); } } /** * 检查IP是否被封禁 */ protected function isIpBlocked(string $ip): bool { $key = 'security:blocked_ip:' . $ip; return Cache::has($key); } /** * 记录封禁访问 */ protected function logBlockedAccess(Request $request): void { Log::info('封禁IP访问被阻止', [ 'ip' => $request->ip(), 'url' => $request->url(), 'method' => $request->method(), 'user_agent' => $request->header('user-agent', '') ]); } /** * 封禁响应 */ protected function blockResponse(): Response { return Response::create([ 'code' => 403, 'msg' => '访问被拒绝' ], 'json')->code(403); } /** * 错误响应 */ protected function errorResponse(): Response { return Response::create([ 'code' => 400, 'msg' => '请求参数错误' ], 'json')->code(400); } }