462 lines
11 KiB
Markdown
462 lines
11 KiB
Markdown
|
|
# Guía de Seguridad Backend para Amayo
|
||
|
|
|
||
|
|
Esta guía contiene las mejoras de seguridad implementadas en el frontend y las recomendaciones para el backend para proteger la IP del servidor.
|
||
|
|
|
||
|
|
## 🛡️ Problema Identificado
|
||
|
|
|
||
|
|
Según el video de referencia (https://youtu.be/iXOlQszplC8), incluso con Cloudflare, un atacante puede:
|
||
|
|
1. Ver el código fuente del frontend y encontrar URLs del backend
|
||
|
|
2. Realizar timing attacks para encontrar la IP real del servidor
|
||
|
|
3. Bypassear Cloudflare usando técnicas de header manipulation
|
||
|
|
|
||
|
|
## ✅ Soluciones Implementadas en el Frontend
|
||
|
|
|
||
|
|
### 1. Servicio de Seguridad (`src/services/security.js`)
|
||
|
|
|
||
|
|
#### Características:
|
||
|
|
- **No expone URLs directamente en el código**: Las URLs se obtienen dinámicamente
|
||
|
|
- **Token de sesión único**: Genera un token por sesión para identificar clientes
|
||
|
|
- **Headers de seguridad**: Incluye timestamps y tokens en cada request
|
||
|
|
- **Rate limiting client-side**: Previene abuso desde el cliente
|
||
|
|
- **Validación de respuestas**: Verifica la autenticidad de las respuestas del servidor
|
||
|
|
|
||
|
|
### 2. Sistema de Rate Limiting
|
||
|
|
|
||
|
|
Implementado en `security.js`:
|
||
|
|
```javascript
|
||
|
|
- Login: 3 intentos por minuto
|
||
|
|
- API calls: 30 requests por minuto
|
||
|
|
- Default: 10 requests por minuto
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Protección CSRF
|
||
|
|
|
||
|
|
- State parameter en OAuth2
|
||
|
|
- Validación de state en callbacks
|
||
|
|
- Tokens de sesión únicos
|
||
|
|
|
||
|
|
### 4. Caché de Datos
|
||
|
|
|
||
|
|
- Stats del bot: 5 minutos
|
||
|
|
- Info del bot: 1 hora
|
||
|
|
- Reduce requests innecesarios
|
||
|
|
|
||
|
|
## 🔧 Recomendaciones para el Backend
|
||
|
|
|
||
|
|
### 1. Configuración de Cloudflare
|
||
|
|
|
||
|
|
#### A. Activar IP Anonymization
|
||
|
|
```
|
||
|
|
Cloudflare Dashboard > Security > Settings > Privacy > Enable IP Geolocation
|
||
|
|
```
|
||
|
|
|
||
|
|
#### B. Bot Fight Mode
|
||
|
|
```
|
||
|
|
Cloudflare Dashboard > Security > Bots > Enable Bot Fight Mode
|
||
|
|
```
|
||
|
|
|
||
|
|
#### C. Under Attack Mode (opcional)
|
||
|
|
Para protección extra cuando se detecte un ataque:
|
||
|
|
```
|
||
|
|
Cloudflare Dashboard > Security > Settings > Security Level > I'm Under Attack
|
||
|
|
```
|
||
|
|
|
||
|
|
#### D. Reglas de Firewall Personalizadas
|
||
|
|
|
||
|
|
```
|
||
|
|
# Bloquear acceso directo a la IP
|
||
|
|
- Si el request no viene de Cloudflare (validar CF-Connecting-IP)
|
||
|
|
- Bloquear requests sin User-Agent
|
||
|
|
- Bloquear requests sin X-Requested-With
|
||
|
|
|
||
|
|
# Rate Limiting Avanzado
|
||
|
|
- 30 requests/minuto por IP
|
||
|
|
- 100 requests/minuto por usuario autenticado
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Configuración del Servidor Backend (Express.js ejemplo)
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// middleware/security.js
|
||
|
|
import rateLimit from 'express-rate-limit';
|
||
|
|
import helmet from 'helmet';
|
||
|
|
|
||
|
|
// Verificar que el request viene de Cloudflare
|
||
|
|
export const cloudflareOnly = (req, res, next) => {
|
||
|
|
const cfIp = req.headers['cf-connecting-ip'];
|
||
|
|
|
||
|
|
// Lista de IPs de Cloudflare (actualizar periódicamente)
|
||
|
|
const cloudflareIPs = [
|
||
|
|
// https://www.cloudflare.com/ips/
|
||
|
|
'173.245.48.0/20',
|
||
|
|
'103.21.244.0/22',
|
||
|
|
// ... más IPs
|
||
|
|
];
|
||
|
|
|
||
|
|
if (!cfIp || !isCloudflareIP(req.ip, cloudflareIPs)) {
|
||
|
|
return res.status(403).json({ error: 'Direct access forbidden' });
|
||
|
|
}
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
|
||
|
|
// Rate limiting por endpoint
|
||
|
|
export const apiLimiter = rateLimit({
|
||
|
|
windowMs: 60 * 1000, // 1 minuto
|
||
|
|
max: 30, // 30 requests
|
||
|
|
message: 'Too many requests from this IP',
|
||
|
|
standardHeaders: true,
|
||
|
|
legacyHeaders: false,
|
||
|
|
skip: (req) => {
|
||
|
|
// Skip para requests autenticados con rate limit más alto
|
||
|
|
return req.user && req.user.premium;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
export const authLimiter = rateLimit({
|
||
|
|
windowMs: 60 * 1000,
|
||
|
|
max: 3, // Solo 3 intentos de login por minuto
|
||
|
|
skipSuccessfulRequests: true
|
||
|
|
});
|
||
|
|
|
||
|
|
// Headers de seguridad
|
||
|
|
export const securityHeaders = helmet({
|
||
|
|
contentSecurityPolicy: {
|
||
|
|
directives: {
|
||
|
|
defaultSrc: ["'self'"],
|
||
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||
|
|
scriptSrc: ["'self'"],
|
||
|
|
imgSrc: ["'self'", "data:", "https:"],
|
||
|
|
},
|
||
|
|
},
|
||
|
|
hsts: {
|
||
|
|
maxAge: 31536000,
|
||
|
|
includeSubDomains: true,
|
||
|
|
preload: true
|
||
|
|
},
|
||
|
|
referrerPolicy: { policy: 'same-origin' },
|
||
|
|
noSniff: true,
|
||
|
|
xssFilter: true,
|
||
|
|
frameguard: { action: 'deny' }
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Validación de Headers
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// middleware/validateRequest.js
|
||
|
|
export const validateSecurityHeaders = (req, res, next) => {
|
||
|
|
const requiredHeaders = [
|
||
|
|
'x-client-token',
|
||
|
|
'x-requested-with',
|
||
|
|
'x-timestamp'
|
||
|
|
];
|
||
|
|
|
||
|
|
// Verificar headers obligatorios
|
||
|
|
for (const header of requiredHeaders) {
|
||
|
|
if (!req.headers[header]) {
|
||
|
|
return res.status(400).json({
|
||
|
|
error: 'Missing security headers'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validar timestamp (prevenir replay attacks)
|
||
|
|
const timestamp = parseInt(req.headers['x-timestamp']);
|
||
|
|
const now = Date.now();
|
||
|
|
const maxAge = 5 * 60 * 1000; // 5 minutos
|
||
|
|
|
||
|
|
if (Math.abs(now - timestamp) > maxAge) {
|
||
|
|
return res.status(401).json({
|
||
|
|
error: 'Request expired'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Agregar server token a la respuesta
|
||
|
|
res.setHeader('X-Server-Token', generateServerToken());
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. CORS Configuración Estricta
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
import cors from 'cors';
|
||
|
|
|
||
|
|
const corsOptions = {
|
||
|
|
origin: (origin, callback) => {
|
||
|
|
const allowedOrigins = [
|
||
|
|
'https://docs.amayo.dev',
|
||
|
|
'https://amayo.dev'
|
||
|
|
];
|
||
|
|
|
||
|
|
// Permitir requests sin origin (mobile apps, etc)
|
||
|
|
if (!origin) return callback(null, true);
|
||
|
|
|
||
|
|
if (allowedOrigins.includes(origin)) {
|
||
|
|
callback(null, true);
|
||
|
|
} else {
|
||
|
|
callback(new Error('Not allowed by CORS'));
|
||
|
|
}
|
||
|
|
},
|
||
|
|
credentials: true,
|
||
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||
|
|
allowedHeaders: [
|
||
|
|
'Content-Type',
|
||
|
|
'Authorization',
|
||
|
|
'X-Client-Token',
|
||
|
|
'X-Requested-With',
|
||
|
|
'X-Timestamp'
|
||
|
|
],
|
||
|
|
exposedHeaders: ['X-Server-Token'],
|
||
|
|
maxAge: 86400 // 24 horas
|
||
|
|
};
|
||
|
|
|
||
|
|
app.use(cors(corsOptions));
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Ocultar Información del Servidor
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Remover headers que revelan información
|
||
|
|
app.disable('x-powered-by');
|
||
|
|
|
||
|
|
app.use((req, res, next) => {
|
||
|
|
res.removeHeader('Server');
|
||
|
|
res.removeHeader('X-Powered-By');
|
||
|
|
next();
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6. Sistema de API Keys para el Frontend
|
||
|
|
|
||
|
|
En lugar de exponer el endpoint directamente, usar API keys rotativas:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Generar API key para el frontend (rotar cada 24 horas)
|
||
|
|
const generateApiKey = () => {
|
||
|
|
const date = new Date().toISOString().split('T')[0];
|
||
|
|
const secret = process.env.API_KEY_SECRET;
|
||
|
|
return crypto
|
||
|
|
.createHash('sha256')
|
||
|
|
.update(date + secret)
|
||
|
|
.digest('hex');
|
||
|
|
};
|
||
|
|
|
||
|
|
// Middleware para validar API key
|
||
|
|
export const validateApiKey = (req, res, next) => {
|
||
|
|
const apiKey = req.headers['x-api-key'];
|
||
|
|
const validKey = generateApiKey();
|
||
|
|
|
||
|
|
if (apiKey !== validKey) {
|
||
|
|
return res.status(401).json({ error: 'Invalid API key' });
|
||
|
|
}
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7. Logging y Monitoreo
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
import winston from 'winston';
|
||
|
|
|
||
|
|
const logger = winston.createLogger({
|
||
|
|
level: 'info',
|
||
|
|
format: winston.format.json(),
|
||
|
|
transports: [
|
||
|
|
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||
|
|
new winston.transports.File({ filename: 'security.log' })
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
// Log de requests sospechosos
|
||
|
|
export const securityLogger = (req, res, next) => {
|
||
|
|
const suspicious =
|
||
|
|
!req.headers['cf-connecting-ip'] ||
|
||
|
|
!req.headers['user-agent'] ||
|
||
|
|
req.headers['user-agent'].includes('curl') ||
|
||
|
|
req.headers['user-agent'].includes('wget');
|
||
|
|
|
||
|
|
if (suspicious) {
|
||
|
|
logger.warn({
|
||
|
|
type: 'suspicious_request',
|
||
|
|
ip: req.ip,
|
||
|
|
headers: req.headers,
|
||
|
|
path: req.path,
|
||
|
|
timestamp: new Date()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 8. Implementación en el Servidor
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// server.js
|
||
|
|
import express from 'express';
|
||
|
|
import {
|
||
|
|
cloudflareOnly,
|
||
|
|
apiLimiter,
|
||
|
|
authLimiter,
|
||
|
|
securityHeaders,
|
||
|
|
validateSecurityHeaders,
|
||
|
|
securityLogger
|
||
|
|
} from './middleware/security.js';
|
||
|
|
|
||
|
|
const app = express();
|
||
|
|
|
||
|
|
// Aplicar middlewares de seguridad
|
||
|
|
app.use(securityHeaders);
|
||
|
|
app.use(cloudflareOnly); // IMPORTANTE: Solo aceptar requests de Cloudflare
|
||
|
|
app.use(securityLogger);
|
||
|
|
app.use(validateSecurityHeaders);
|
||
|
|
|
||
|
|
// Rate limiting
|
||
|
|
app.use('/api/', apiLimiter);
|
||
|
|
app.use('/api/auth/', authLimiter);
|
||
|
|
|
||
|
|
// Ocultar endpoint real
|
||
|
|
app.use('/api', (req, res, next) => {
|
||
|
|
// No revelar estructura interna en errores
|
||
|
|
res.locals.showStack = false;
|
||
|
|
next();
|
||
|
|
});
|
||
|
|
|
||
|
|
// Routes
|
||
|
|
app.use('/api/auth', authRoutes);
|
||
|
|
app.use('/api/bot', botRoutes);
|
||
|
|
|
||
|
|
// Error handler - no revelar información
|
||
|
|
app.use((err, req, res, next) => {
|
||
|
|
logger.error({
|
||
|
|
error: err.message,
|
||
|
|
stack: err.stack,
|
||
|
|
ip: req.ip,
|
||
|
|
path: req.path
|
||
|
|
});
|
||
|
|
|
||
|
|
res.status(500).json({
|
||
|
|
error: 'Internal server error',
|
||
|
|
// No incluir detalles en producción
|
||
|
|
...(process.env.NODE_ENV === 'development' && {
|
||
|
|
message: err.message
|
||
|
|
})
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🔒 Configuración de Variables de Entorno
|
||
|
|
|
||
|
|
### Frontend (.env)
|
||
|
|
```env
|
||
|
|
VITE_DISCORD_CLIENT_ID=your_client_id
|
||
|
|
VITE_APP_VERSION=1.0.0
|
||
|
|
# NO incluir URLs del backend aquí
|
||
|
|
```
|
||
|
|
|
||
|
|
### Backend (.env)
|
||
|
|
```env
|
||
|
|
PORT=3000
|
||
|
|
NODE_ENV=production
|
||
|
|
API_KEY_SECRET=your_random_secret_here
|
||
|
|
JWT_SECRET=your_jwt_secret_here
|
||
|
|
DISCORD_CLIENT_SECRET=your_client_secret
|
||
|
|
ALLOWED_ORIGINS=https://docs.amayo.dev,https://amayo.dev
|
||
|
|
|
||
|
|
# Database
|
||
|
|
DATABASE_URL=your_database_url
|
||
|
|
|
||
|
|
# Cloudflare
|
||
|
|
CLOUDFLARE_API_TOKEN=your_token
|
||
|
|
```
|
||
|
|
|
||
|
|
## 📋 Checklist de Seguridad
|
||
|
|
|
||
|
|
### Frontend ✅
|
||
|
|
- [x] Servicio de seguridad implementado
|
||
|
|
- [x] Rate limiting client-side
|
||
|
|
- [x] No URLs hardcodeadas
|
||
|
|
- [x] Protección CSRF
|
||
|
|
- [x] Validación de respuestas
|
||
|
|
- [x] Sistema de caché
|
||
|
|
|
||
|
|
### Backend (Por Implementar)
|
||
|
|
- [ ] Verificar requests de Cloudflare
|
||
|
|
- [ ] Rate limiting server-side
|
||
|
|
- [ ] Validación de headers de seguridad
|
||
|
|
- [ ] CORS estricto
|
||
|
|
- [ ] Ocultar información del servidor
|
||
|
|
- [ ] Sistema de API keys
|
||
|
|
- [ ] Logging y monitoreo
|
||
|
|
- [ ] Error handling seguro
|
||
|
|
|
||
|
|
### Cloudflare
|
||
|
|
- [ ] Bot Fight Mode activado
|
||
|
|
- [ ] Reglas de firewall configuradas
|
||
|
|
- [ ] Rate limiting configurado
|
||
|
|
- [ ] SSL/TLS en modo Full (strict)
|
||
|
|
- [ ] DNSSEC activado
|
||
|
|
- [ ] Page Rules configuradas
|
||
|
|
|
||
|
|
## 🚀 Despliegue
|
||
|
|
|
||
|
|
### 1. Actualizar Cloudflare
|
||
|
|
```bash
|
||
|
|
# Configurar reglas de firewall
|
||
|
|
# Dashboard > Security > WAF > Create firewall rule
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Actualizar el Backend
|
||
|
|
```bash
|
||
|
|
npm install helmet express-rate-limit cors winston
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Variables de Entorno
|
||
|
|
Asegúrate de configurar todas las variables de entorno en producción.
|
||
|
|
|
||
|
|
### 4. Monitoreo
|
||
|
|
Implementa un sistema de alertas para:
|
||
|
|
- Intentos de acceso directo a la IP
|
||
|
|
- Rate limiting excedido
|
||
|
|
- Errores de seguridad
|
||
|
|
- Requests sospechosos
|
||
|
|
|
||
|
|
## 📚 Recursos Adicionales
|
||
|
|
|
||
|
|
- [Cloudflare Security Best Practices](https://developers.cloudflare.com/fundamentals/security/)
|
||
|
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||
|
|
- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html)
|
||
|
|
- [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
|
||
|
|
|
||
|
|
## ⚠️ Notas Importantes
|
||
|
|
|
||
|
|
1. **Nunca expongas URLs del backend en el código del cliente**
|
||
|
|
2. **Siempre valida que los requests vengan de Cloudflare**
|
||
|
|
3. **Usa rate limiting tanto en cliente como en servidor**
|
||
|
|
4. **Monitorea logs constantemente**
|
||
|
|
5. **Mantén Cloudflare actualizado con las últimas reglas de seguridad**
|
||
|
|
6. **Rota API keys regularmente**
|
||
|
|
7. **Implementa un sistema de alertas**
|
||
|
|
|
||
|
|
## 🔄 Mantenimiento
|
||
|
|
|
||
|
|
### Semanal
|
||
|
|
- Revisar logs de seguridad
|
||
|
|
- Verificar rate limiting efectivo
|
||
|
|
- Actualizar reglas de firewall si es necesario
|
||
|
|
|
||
|
|
### Mensual
|
||
|
|
- Rotar API keys
|
||
|
|
- Actualizar lista de IPs de Cloudflare
|
||
|
|
- Revisar políticas de CORS
|
||
|
|
- Auditar accesos sospechosos
|
||
|
|
|
||
|
|
### Trimestral
|
||
|
|
- Realizar penetration testing
|
||
|
|
- Actualizar dependencias de seguridad
|
||
|
|
- Revisar y actualizar esta guía
|