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
JavaScript
// 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}`);
});