diff --git a/src/middleware.ts b/src/middleware.ts index a562ef1..6379e72 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,7 +1,119 @@ -import { type NextRequest } from 'next/server'; +import { type NextRequest, NextResponse } from 'next/server'; import { updateSession } from '@/utils/supabase/middleware'; +// In-memory store for tracking IPs (use Redis in production) +const ipAttempts = new Map(); +const bannedIPs = new Set(); + +// Suspicious patterns that indicate malicious activity +const MALICIOUS_PATTERNS = [ + /web-inf/i, + /\.jsp/i, + /\.php/i, + /puttest/i, + /WEB-INF/i, + /\.xml$/i, + /perl/i, + /xampp/i, + /phpwebgallery/i, + /FileManager/i, + /standalonemanager/i, + /h2console/i, + /WebAdmin/i, + /login_form\.php/i, + /%2e/i, + /%u002e/i, + /\.%00/i, + /\.\./, + /lcgi/i, +]; + +// Suspicious HTTP methods +const SUSPICIOUS_METHODS = ['TRACE', 'PUT', 'DELETE', 'PATCH']; + +const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute +const MAX_ATTEMPTS = 10; // Max suspicious requests per window +const BAN_DURATION = 30 * 60 * 1000; // 30 minutes + +const getClientIP = (request: NextRequest): string => { + const forwarded = request.headers.get('x-forwarded-for'); + const realIP = request.headers.get('x-real-ip'); + + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + + if (realIP) { + return realIP; + } + + return request.ip ?? 'unknown'; +}; + +const isPathSuspicious = (pathname: string): boolean => { + return MALICIOUS_PATTERNS.some((pattern) => pattern.test(pathname)); +}; + +const isMethodSuspicious = (method: string): boolean => { + return SUSPICIOUS_METHODS.includes(method); +}; + +const updateIPAttempts = (ip: string): boolean => { + const now = Date.now(); + const attempts = ipAttempts.get(ip); + + if (!attempts || now - attempts.lastAttempt > RATE_LIMIT_WINDOW) { + ipAttempts.set(ip, { count: 1, lastAttempt: now }); + return false; + } + + attempts.count++; + attempts.lastAttempt = now; + + if (attempts.count > MAX_ATTEMPTS) { + bannedIPs.add(ip); + // Clean up the attempts record + ipAttempts.delete(ip); + + // Auto-unban after duration (in production, use a proper scheduler) + setTimeout(() => { + bannedIPs.delete(ip); + }, BAN_DURATION); + + return true; + } + + return false; +}; + export const middleware = async (request: NextRequest) => { + const { pathname } = request.nextUrl; + const method = request.method; + const ip = getClientIP(request); + + // Check if IP is already banned + if (bannedIPs.has(ip)) { + console.log(`🚫 Blocked banned IP: ${ip} trying to access ${pathname}`); + return new NextResponse('Access denied.', { status: 403 }); + } + + // Check for suspicious activity + const isSuspiciousPath = isPathSuspicious(pathname); + const isSuspiciousMethod = isMethodSuspicious(method); + + if (isSuspiciousPath || isSuspiciousMethod) { + console.log(`⚠️ Suspicious activity from ${ip}: ${method} ${pathname}`); + + const shouldBan = updateIPAttempts(ip); + + if (shouldBan) { + console.log(`🔨 IP ${ip} has been banned for suspicious activity`); + return new NextResponse('Access denied - IP banned', { status: 403 }); + } + + // Return 404 to not reveal the blocking mechanism + return new NextResponse('Not Found', { status: 404 }); + } return await updateSession(request); };