UNPKG

api-decooyy

Version:

A plug-and-play security gateway that detects malicious traffic and redirects it to a decoy API

293 lines (250 loc) 9.28 kB
// app.js const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const morgan = require('morgan'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); // Configuration const PORT = process.env.PORT || 3000; const API_SERVICE_URL = process.env.API_URL || "http://localhost:8080"; // Real API const DECOY_SERVICE_URL = process.env.DECOY_URL || "http://localhost:8081"; // Decoy API // Initialize suspicious IP storage const suspiciousIPs = new Set(); const suspiciousRequests = []; // Create Express app const app = express(); // Logging app.use(morgan('combined')); // Security headers app.use(helmet()); // Enable CORS app.use(cors()); // Request body and URL parsing middleware app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs standardHeaders: true, legacyHeaders: false, handler: (req, res, next) => { console.log(`Rate limit exceeded for IP: ${req.ip}`); suspiciousIPs.add(req.ip); res.status(429).send('Too many requests'); } }); app.use(limiter); // Admin dashboard for viewing logs app.get('/admin/dashboard', (req, res) => { res.send(` <html> <head> <title>Security Proxy Dashboard</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } tr:nth-child(even) { background-color: #f9f9f9; } </style> </head> <body> <h1>Security Proxy Dashboard</h1> <h2>Suspicious IPs</h2> <ul> ${Array.from(suspiciousIPs).map(ip => `<li>${ip}</li>`).join('')} </ul> <h2>Suspicious Requests</h2> <table> <tr> <th>Timestamp</th> <th>IP</th> <th>Method</th> <th>URL</th> <th>Reason</th> </tr> ${suspiciousRequests.map(req => ` <tr> <td>${req.timestamp}</td> <td>${req.ip}</td> <td>${req.method}</td> <td>${req.url}</td> <td>${Object.entries(req.reason) .filter(([k, v]) => v === true) .map(([k]) => k) .join(', ')} </td> </tr> `).join('')} </table> </body> </html> `); }); // Threat detection middleware - IMPROVED VERSION const threatDetection = (req, res, next) => { // Get full URL including query parameters const fullUrl = req.originalUrl || req.url; // Get all parameters (from URL, query string, and body) const params = { ...req.params, ...req.query, ...(typeof req.body === 'object' ? req.body : {}) }; // Convert params to string for easier pattern matching const paramsString = JSON.stringify(params); // Function to check pattern in multiple places const checkPattern = (pattern) => { return pattern.test(fullUrl) || pattern.test(paramsString); }; // More comprehensive patterns const hasSqlInjection = checkPattern(/(\%27)|(\')|(\-\-)|(\%23)|(#)/i) || checkPattern(/((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(:))/i) || checkPattern(/union\s+select/i) || checkPattern(/exec(\s|\+)+(s|x)p\w+/i) || checkPattern(/SLEEP\(/i) || checkPattern(/SELECT\s+.*\s+FROM/i); const hasXssAttempt = checkPattern(/<script.*>.*<\/script>/i) || checkPattern(/<.*on\w+\s*=.*>/i) || checkPattern(/javascript:/i) || checkPattern(/alert\s*\(/i) || checkPattern(/eval\s*\(/i); const hasPathTraversal = checkPattern(/(\.\.\/)|(\.\.\\)/g) || checkPattern(/\/etc\/passwd/i); const hasCommandInjection = checkPattern(/(\;|\||\`|\&|\$\()/g) || checkPattern(/(wget|curl|bash|sh|nc|netcat)\s/i); // Log suspicious request details for debugging if (hasSqlInjection || hasXssAttempt || hasPathTraversal || hasCommandInjection) { console.log('--- ATTACK DETECTED ---'); console.log(`URL: ${fullUrl}`); console.log(`Type: ${hasSqlInjection ? 'SQL Injection' : hasXssAttempt ? 'XSS' : hasPathTraversal ? 'Path Traversal' : 'Command Injection'}`); console.log(`IP: ${req.ip}`); console.log('------------------------'); } // Check if IP is already suspicious const isKnownSuspicious = suspiciousIPs.has(req.ip); // Log suspicious activity and determine if we should redirect if (hasSqlInjection || hasXssAttempt || hasPathTraversal || hasCommandInjection || isKnownSuspicious) { console.log(`Suspicious request detected from IP: ${req.ip}`); // Add to suspicious IPs suspiciousIPs.add(req.ip); // Log the suspicious request suspiciousRequests.push({ timestamp: new Date().toISOString(), ip: req.ip, method: req.method, url: fullUrl, headers: req.headers, body: req.body, reason: { sqlInjection: hasSqlInjection, xssAttempt: hasXssAttempt, pathTraversal: hasPathTraversal, commandInjection: hasCommandInjection, knownSuspicious: isKnownSuspicious } }); // Set a flag to redirect to decoy req.useDecoy = true; } next(); }; // Dynamic patching middleware const dynamicPatching = (req, res, next) => { // Add random request ID header req.headers['x-request-id'] = uuidv4(); // Add timestamp header req.headers['x-request-timestamp'] = Date.now().toString(); // Buffer to collect response data let responseBody = []; // Capture the original write and end methods const originalWrite = res.write; const originalEnd = res.end; // Override the write method res.write = function(chunk) { responseBody.push(chunk); return originalWrite.apply(res, arguments); }; // Override the end method res.end = function(chunk) { if (chunk) { responseBody.push(chunk); } // Try to modify JSON responses if (res.getHeader('content-type') && res.getHeader('content-type').includes('application/json')) { try { const buffer = Buffer.concat(responseBody); const text = buffer.toString('utf8'); const json = JSON.parse(text); // Add honeypot data json._trace_id = uuidv4(); json._sec_timestamp = new Date().toISOString(); // Convert back to Buffer and update content length const modifiedBody = Buffer.from(JSON.stringify(json)); res.setHeader('content-length', modifiedBody.length); // Call original end with modified body return originalEnd.call(res, modifiedBody); } catch (error) { console.error('Error modifying response:', error); } } // Fall back to original behavior return originalEnd.apply(res, arguments); }; next(); }; // Apply middleware app.use(threatDetection); app.use(dynamicPatching); // Setup proxy middleware - SPLIT INTO TWO ROUTES for real API and decoy API // Route to real API app.use('/', (req, res, next) => { // Skip this middleware if we're redirecting to decoy if (req.useDecoy) { return next(); } console.log(`Proxying to REAL API: ${req.url}`); createProxyMiddleware({ target: API_SERVICE_URL, changeOrigin: true, pathRewrite: { [`^/`]: '/', }, onProxyRes: (proxyRes, req, res) => { // Add honeypot headers proxyRes.headers['x-powered-by'] = 'PHP/7.4.3'; // False information proxyRes.headers['server'] = 'Apache/2.4.38'; // False information } })(req, res, next); }); // Route to decoy API (only used if useDecoy is true) app.use('/', (req, res, next) => { // Only use this middleware if we're redirecting to decoy if (!req.useDecoy) { return next(); } console.log(`Redirecting to DECOY API: ${req.url}`); createProxyMiddleware({ target: DECOY_SERVICE_URL, changeOrigin: true, pathRewrite: { [`^/`]: '/', }, onProxyRes: (proxyRes, req, res) => { console.log(`Successfully redirected suspicious request to decoy`); } })(req, res, next); }); // Start the server app.listen(PORT, () => { console.log(`Security proxy started on port ${PORT}`); console.log(`Routing regular traffic to: ${API_SERVICE_URL}`); console.log(`Routing suspicious traffic to: ${DECOY_SERVICE_URL}`); });