| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- <?php
- declare(strict_types=1);
- namespace app\middleware;
- use think\facade\Db;
- use think\facade\Log;
- use think\facade\Cache;
- use think\Request;
- use think\Response;
- /**
- * 安全防护中间件
- */
- class SecurityMiddleware
- {
- // 配置参数
- protected $config = [
- 'enable_logging' => 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, string $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\b[^>]*>(.*?)<\/script>/is',
- '/javascript\s*:/i',
- '/on(load|error|click|mouse|key|focus|blur|submit)\s*=/i',
- // 事件处理
- '/<\w+\s+[^>]*on\w+\s*=[^>]*>/i',
- // 数据协议
- '/data\s*:/i',
- '/vbscript\s*:/i',
- // 框架/对象
- '/<iframe\b[^>]*>/i',
- '/<object\b[^>]*>/i',
- '/<embed\b[^>]*>/i',
- '/<applet\b[^>]*>/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*\(/i',
- // 管道符
- '/\|\s*\w+/',
- '/;\s*\w+/',
- // 危险命令
- '/\b(?:rm\s+-|cat\s+\/|wget\s+|curl\s+|nc\s+)\b/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 = $request->ip();
- // 记录攻击日志
- foreach ($attacks as $attack) {
- $this->logAttack($ip, $request, $attack);
- }
- // 检查是否达到封禁阈值
- if ($this->config['enable_blocking']) {
- $this->checkAndBlockIp($ip);
- }
- }
- /**
- * 记录攻击日志到数据库
- */
- 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()
- ->json([
- 'code' => 403,
- 'message' => '访问被拒绝',
- 'data' => null
- ])
- ->code(403)
- ->header([
- 'Content-Type' => 'application/json',
- 'Retry-After' => 3600
- ]);
- }
- /**
- * 错误响应
- */
- protected function errorResponse(): Response
- {
- return response()
- ->json([
- 'code' => 400,
- 'message' => '请求参数错误',
- 'data' => null
- ])
- ->code(400);
- }
- }
|