Fuzzing
¿Qué es Fuzzing?
Fuzzing es una técnica de testing de seguridad automatizada que consiste en enviar datos aleatorios, malformados, inesperados o extremos a una aplicación, API o sistema para descubrir vulnerabilidades, crashes, comportamientos anómalos y fallas de seguridad. Es como bombardear una aplicación con millones de entradas diferentes para ver dónde se rompe.
¿Para qué se utiliza Fuzzing?
Fuzzing es utilizado por desarrolladores y pentesters para:
- Descubrir vulnerabilidades de seguridad antes que los atacantes.
- Encontrar bugs y crashes que causan inestabilidad en aplicaciones.
- Identificar problemas de validación de entrada y manejo de errores.
- Probar la robustez de APIs, formularios web y interfaces de usuario.
- Detectar buffer overflows, injection attacks y memory corruption.
- Automatizar el descubrimiento de edge cases que testing manual no encuentra.
¿Cómo funciona?
Fuzzing funciona como un mono con una metralleta de datos disparando millones de combinaciones aleatorias contra tu aplicación hasta que algo se rompe. Mientras que testing normal verifica que todo funcione correctamente, fuzzing busca activamente formas de hacer que las cosas fallen.
Tipos de Fuzzing
1. Black-box Fuzzing (Caja Negra)
¿Qué es? Testing sin conocimiento interno del código o arquitectura.
Ejemplo básico:
// Script básico de fuzzing para formularios web
const fuzzingPayloads = [
// Strings extremos
'A'.repeat(10000), // String muy largo
'', // String vacío
null,
undefined,
// Caracteres especiales
'\x00\x01\x02', // Null bytes
'../../../../etc/passwd', // Path traversal
'<script>alert("xss")</script>', // XSS
"' OR '1'='1' --", // SQL injection
// Números extremos
-2147483648, // INT_MIN
2147483647, // INT_MAX
999999999999999, // Número muy grande
-999999999999999, // Número muy negativo
0.1 + 0.2, // Precisión flotante
// Formatos malformados
'not-an-email', // Email inválido
'2023-13-45', // Fecha inválida
'rgb(300,300,300)', // Color inválido
];
async function fuzzForm(formSelector) {
const form = document.querySelector(formSelector);
const inputs = form.querySelectorAll('input, textarea, select');
for (const payload of fuzzingPayloads) {
for (const input of inputs) {
console.log(`Testing ${input.name} with payload: ${payload}`);
// Guardar valor original
const originalValue = input.value;
try {
// Inyectar payload
input.value = payload;
input.dispatchEvent(new Event('input'));
input.dispatchEvent(new Event('change'));
// Intentar enviar formulario
const formData = new FormData(form);
const response = await fetch(form.action, {
method: form.method || 'POST',
body: formData,
});
// Analizar respuesta
if (!response.ok) {
console.warn(`Potential issue: ${response.status} with payload: ${payload}`);
}
const responseText = await response.text();
// Buscar indicadores de vulnerabilidad
if (
responseText.includes('error') ||
responseText.includes('exception') ||
responseText.includes('stack trace')
) {
console.error(`Vulnerability found with payload: ${payload}`);
}
} catch (error) {
console.error(`Error with payload ${payload}:`, error);
} finally {
// Restaurar valor original
input.value = originalValue;
}
// Pausa para evitar rate limiting
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
}
// Uso
fuzzForm('#contact-form');
2. White-box Fuzzing (Caja Blanca)
¿Qué es? Testing con acceso completo al código fuente y arquitectura.
Ejemplo con cobertura de código:
// Fuzzer que rastrea cobertura de código
class CodeCoverageFuzzer {
constructor(targetFunction) {
this.targetFunction = targetFunction;
this.executedPaths = new Set();
this.crashingInputs = [];
this.interestingInputs = [];
}
generatePayload() {
const payloadTypes = [
() => Math.random().toString(36).substring(2, 15), // String random
() => Math.floor(Math.random() * 1000000), // Número random
() => Math.random() > 0.5, // Boolean random
() => null,
() => undefined,
() => [],
() => {},
() => 'A'.repeat(Math.floor(Math.random() * 1000)), // String variable
];
const randomType = payloadTypes[Math.floor(Math.random() * payloadTypes.length)];
return randomType();
}
async fuzzTarget(iterations = 10000) {
for (let i = 0; i < iterations; i++) {
const payload = this.generatePayload();
try {
// Instrumentar función para tracking
const result = await this.instrumentAndExecute(payload);
// Analizar resultado
if (this.isInteresting(result)) {
this.interestingInputs.push({
input: payload,
output: result,
iteration: i,
});
}
} catch (error) {
// Crash detectado
this.crashingInputs.push({
input: payload,
error: error.message,
stack: error.stack,
iteration: i,
});
console.error(`Crash found at iteration ${i}:`, error);
}
if (i % 1000 === 0) {
console.log(`Progress: ${i}/${iterations}, Crashes: ${this.crashingInputs.length}`);
}
}
return this.generateReport();
}
instrumentAndExecute(payload) {
// Simular instrumentación de código
const pathId = this.getExecutionPath(payload);
this.executedPaths.add(pathId);
return this.targetFunction(payload);
}
getExecutionPath(payload) {
// Simplificado: generar ID basado en tipo y valor
return `${typeof payload}_${JSON.stringify(payload).length}`;
}
isInteresting(result) {
// Criterios para resultados "interesantes"
return (
(result !== null && result !== undefined && typeof result === 'object') ||
(typeof result === 'string' && result.length > 100)
);
}
generateReport() {
return {
totalTests: this.executedPaths.size,
crashes: this.crashingInputs,
interestingCases: this.interestingInputs,
coverage: `${this.executedPaths.size} unique paths executed`,
};
}
}
// Función objetivo para testing
function vulnerableFunction(input) {
// Simulando vulnerabilidades comunes
if (typeof input === 'string') {
if (input.length > 1000) {
throw new Error('Buffer overflow simulation');
}
if (input.includes('../')) {
throw new Error('Path traversal detected');
}
if (input.includes('<script>')) {
return `Reflected: ${input}`; // XSS vulnerable
}
}
if (typeof input === 'number' && input > 999999) {
throw new Error('Integer overflow');
}
return `Processed: ${input}`;
}
// Ejecutar fuzzing
const fuzzer = new CodeCoverageFuzzer(vulnerableFunction);
fuzzer.fuzzTarget(5000).then((report) => {
console.log('Fuzzing Report:', report);
});
3. Grey-box Fuzzing (Caja Gris)
¿Qué es? Combinación de black-box y white-box, con información parcial del sistema.
// Fuzzer híbrido para APIs
class APIFuzzer {
constructor(baseURL, endpoints) {
this.baseURL = baseURL;
this.endpoints = endpoints;
this.vulnerabilities = [];
this.responsePatterns = new Map();
}
// Generar payloads basados en el contexto del endpoint
generateContextualPayloads(endpoint) {
const basePayloads = [
// SQL Injection
"' OR '1'='1",
"'; DROP TABLE users; --",
"1' UNION SELECT * FROM admin --",
// NoSQL Injection
{ $ne: null },
{ $gt: '' },
{ $where: 'function(){return true}' },
// XSS
"<script>alert('xss')</script>",
"javascript:alert('xss')",
"\"><img src=x onerror=alert('xss')>",
// Command Injection
'; ls -la',
'| whoami',
'&& cat /etc/passwd',
// LDAP Injection
'*)(uid=*',
'admin)(&(password=*))',
// XXE
"<?xml version='1.0'?><!DOCTYPE root [<!ENTITY test SYSTEM 'file:///etc/passwd'>]><root>&test;</root>",
];
// Personalizar payloads según el tipo de endpoint
if (endpoint.includes('login') || endpoint.includes('auth')) {
return [
...basePayloads,
{ username: 'admin', password: 'admin' },
{ username: "' OR 1=1 --", password: 'anything' },
];
}
if (endpoint.includes('search')) {
return [
...basePayloads,
'A'.repeat(10000), // Buffer overflow
'../../../etc/passwd', // Path traversal
];
}
return basePayloads;
}
async fuzzEndpoint(endpoint, method = 'POST') {
const payloads = this.generateContextualPayloads(endpoint);
const url = `${this.baseURL}${endpoint}`;
console.log(`Fuzzing ${method} ${url}`);
for (const payload of payloads) {
try {
const response = await this.sendRequest(url, method, payload);
await this.analyzeResponse(endpoint, payload, response);
// Rate limiting
await this.sleep(50);
} catch (error) {
this.logVulnerability(endpoint, payload, 'REQUEST_ERROR', error.message);
}
}
}
async sendRequest(url, method, payload) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'SecurityFuzzer/1.0',
},
};
if (method !== 'GET') {
options.body = JSON.stringify(payload);
}
const response = await fetch(url, options);
const text = await response.text();
return {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: text,
time: Date.now(),
};
}
async analyzeResponse(endpoint, payload, response) {
// Detectar errores de base de datos
const dbErrors = [
'mysql_fetch_array',
'ORA-00933',
'Microsoft OLE DB Provider',
'SQLServer JDBC Driver',
'PostgreSQL query failed',
];
for (const error of dbErrors) {
if (response.body.includes(error)) {
this.logVulnerability(
endpoint,
payload,
'SQL_INJECTION',
`Database error exposed: ${error}`
);
}
}
// Detectar XSS reflejado
if (typeof payload === 'string' && response.body.includes(payload)) {
if (payload.includes('<script>') || payload.includes('javascript:')) {
this.logVulnerability(
endpoint,
payload,
'XSS_REFLECTED',
'Payload reflected without sanitization'
);
}
}
// Detectar information disclosure
const sensitiveInfo = [
'/etc/passwd',
'root:x:0:0',
'Administrator',
'stack trace',
'exception',
];
for (const info of sensitiveInfo) {
if (response.body.includes(info)) {
this.logVulnerability(
endpoint,
payload,
'INFO_DISCLOSURE',
`Sensitive information leaked: ${info}`
);
}
}
// Detectar respuestas anómalas
if (response.status >= 500) {
this.logVulnerability(endpoint, payload, 'SERVER_ERROR', `Server error ${response.status}`);
}
// Analizar tiempo de respuesta (posible DoS)
const responseTime = Date.now() - response.time;
if (responseTime > 5000) {
// 5 segundos
this.logVulnerability(
endpoint,
payload,
'SLOW_RESPONSE',
`Potentially slow response: ${responseTime}ms`
);
}
}
logVulnerability(endpoint, payload, type, details) {
const vulnerability = {
endpoint,
payload,
type,
details,
timestamp: new Date().toISOString(),
};
this.vulnerabilities.push(vulnerability);
console.warn(`[VULN] ${type} in ${endpoint}:`, details);
}
async sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async runFullScan() {
console.log('Starting comprehensive API fuzzing...');
for (const endpoint of this.endpoints) {
await this.fuzzEndpoint(endpoint, 'GET');
await this.fuzzEndpoint(endpoint, 'POST');
await this.fuzzEndpoint(endpoint, 'PUT');
await this.fuzzEndpoint(endpoint, 'DELETE');
}
return this.generateReport();
}
generateReport() {
const report = {
summary: {
totalEndpoints: this.endpoints.length,
totalVulnerabilities: this.vulnerabilities.length,
criticalIssues: this.vulnerabilities.filter((v) =>
['SQL_INJECTION', 'XSS_REFLECTED'].includes(v.type)
).length,
},
vulnerabilities: this.vulnerabilities,
recommendations: this.generateRecommendations(),
};
console.log('\n=== FUZZING REPORT ===');
console.log(`Endpoints tested: ${report.summary.totalEndpoints}`);
console.log(`Vulnerabilities found: ${report.summary.totalVulnerabilities}`);
console.log(`Critical issues: ${report.summary.criticalIssues}`);
return report;
}
generateRecommendations() {
const vulnTypes = [...new Set(this.vulnerabilities.map((v) => v.type))];
const recommendations = [];
if (vulnTypes.includes('SQL_INJECTION')) {
recommendations.push('Implement parameterized queries and input validation');
}
if (vulnTypes.includes('XSS_REFLECTED')) {
recommendations.push('Implement proper output encoding and CSP headers');
}
if (vulnTypes.includes('INFO_DISCLOSURE')) {
recommendations.push('Configure proper error handling and logging');
}
return recommendations;
}
}
// Uso del API Fuzzer
const apiFuzzer = new APIFuzzer('https://api.ejemplo.com', [
'/api/login',
'/api/users',
'/api/search',
'/api/upload',
'/api/admin',
]);
apiFuzzer.runFullScan().then((report) => {
console.log('Final report:', report);
});
Fuzzing de Archivos y Protocolos
// Fuzzer para archivos de diferentes formatos
class FileFuzzer {
constructor() {
this.fileFormats = {
image: {
extensions: ['jpg', 'png', 'gif', 'bmp'],
magicBytes: [
[0xff, 0xd8, 0xff], // JPEG
[0x89, 0x50, 0x4e, 0x47], // PNG
[0x47, 0x49, 0x46, 0x38], // GIF
],
},
document: {
extensions: ['pdf', 'doc', 'xls'],
magicBytes: [
[0x25, 0x50, 0x44, 0x46], // PDF
[0xd0, 0xcf, 0x11, 0xe0], // MS Office
],
},
};
}
generateMalformedFile(format, size = 1024) {
const buffer = new ArrayBuffer(size);
const view = new Uint8Array(buffer);
// Corromper magic bytes
const magicBytes = this.fileFormats[format].magicBytes[0];
for (let i = 0; i < magicBytes.length; i++) {
view[i] = magicBytes[i] ^ 0xff; // XOR para corromper
}
// Llenar con datos aleatorios
for (let i = magicBytes.length; i < size; i++) {
view[i] = Math.floor(Math.random() * 256);
}
// Insertar patrones problemáticos
const problemPatterns = [
[0x00, 0x00, 0x00, 0x00], // Null bytes
[0xff, 0xff, 0xff, 0xff], // Max values
[0x41, 0x41, 0x41, 0x41], // Repeated 'A'
];
problemPatterns.forEach((pattern, index) => {
const offset = Math.floor((size * (index + 1)) / (problemPatterns.length + 1));
pattern.forEach((byte, i) => {
if (offset + i < size) view[offset + i] = byte;
});
});
return buffer;
}
async testFileUpload(uploadEndpoint, format) {
const malformedFile = this.generateMalformedFile(format);
const blob = new Blob([malformedFile], { type: `${format}/*` });
const formData = new FormData();
formData.append('file', blob, `fuzz_test.${this.fileFormats[format].extensions[0]}`);
try {
const response = await fetch(uploadEndpoint, {
method: 'POST',
body: formData,
});
console.log(`File fuzzing result: ${response.status}`);
if (response.status >= 500) {
console.warn('Server error detected with malformed file');
}
return response;
} catch (error) {
console.error('File upload fuzzing error:', error);
}
}
}
Herramientas de Fuzzing Profesionales
# Instalación de herramientas populares
# OWASP ZAP (Web Application Fuzzer)
# GUI y API para fuzzing automatizado
wget https://github.com/zaproxy/zaproxy/releases/latest/download/ZAP_2.12.0_Linux.tar.gz
# Burp Suite (Intruder para fuzzing)
# Herramienta profesional con funciones avanzadas de fuzzing
# ffuf (Fast web fuzzer)
go install github.com/ffuf/ffuf@latest
# Ejemplo de uso con ffuf
ffuf -w wordlist.txt -u https://ejemplo.com/FUZZ -mc 200,301,302
# wfuzz (Web application fuzzer)
pip install wfuzz
# Ejemplo de uso con wfuzz
wfuzz -c -z file,wordlist.txt --hc 404 https://ejemplo.com/FUZZ
# Radamsa (General-purpose fuzzer)
git clone https://gitlab.com/akihe/radamsa.git
cd radamsa && make && sudo make install
# AFL++ (American Fuzzy Lop)
# Para fuzzing de binarios y aplicaciones
git clone https://github.com/AFLplusplus/AFLplusplus.git
Fuzzing Automatizado con CI/CD
# GitHub Actions para fuzzing continuo
name: Security Fuzzing
on:
schedule:
- cron: '0 2 * * *' # Ejecutar diariamente a las 2 AM
push:
branches: [main]
jobs:
security-fuzzing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Start application
run: |
npm start &
sleep 30 # Esperar a que la app inicie
- name: Run custom fuzzer
run: node scripts/fuzzer.js
- name: Run OWASP ZAP fuzzing
run: |
docker run -v $(pwd):/zap/wrk/:rw \
-t owasp/zap2docker-stable \
zap-baseline.py -t http://localhost:3000 \
-r zap-report.html
- name: Upload fuzzing results
uses: actions/upload-artifact@v3
with:
name: fuzzing-reports
path: |
zap-report.html
fuzzing-log.json
- name: Create security issue if vulnerabilities found
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Security vulnerabilities found by fuzzing',
body: 'Automated fuzzing detected potential security issues. Check the artifacts for details.',
labels: ['security', 'bug']
})
Mejores Prácticas para Fuzzing
// Framework de fuzzing escalable y responsable
class ResponsibleFuzzer {
constructor(config) {
this.config = {
maxRequestsPerSecond: 10,
maxConcurrentRequests: 5,
timeoutMs: 5000,
retryAttempts: 3,
respectRobotsTxt: true,
userAgent: 'SecurityResearcher-Fuzzer/1.0',
...config,
};
this.requestQueue = [];
this.activeRequests = 0;
this.results = [];
}
async respectfulFuzz(targets) {
// Verificar robots.txt si está habilitado
if (this.config.respectRobotsTxt) {
await this.checkRobotsTxt(targets[0]);
}
// Rate limiting inteligente
const rateLimiter = this.createRateLimiter();
for (const target of targets) {
await rateLimiter.acquire();
try {
const result = await this.fuzzTarget(target);
this.results.push(result);
} catch (error) {
console.error(`Error fuzzing ${target}:`, error);
} finally {
rateLimiter.release();
}
}
return this.results;
}
createRateLimiter() {
let tokens = this.config.maxRequestsPerSecond;
const maxTokens = this.config.maxRequestsPerSecond;
// Refill tokens cada segundo
setInterval(() => {
tokens = Math.min(maxTokens, tokens + 1);
}, 1000 / maxTokens);
return {
async acquire() {
while (tokens <= 0) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
tokens--;
},
release() {
// Token ya fue consumido en acquire
},
};
}
async checkRobotsTxt(baseUrl) {
try {
const robotsUrl = new URL('/robots.txt', baseUrl).href;
const response = await fetch(robotsUrl);
const robotsText = await response.text();
// Parsear robots.txt básico
const disallowedPaths = robotsText
.split('\n')
.filter((line) => line.toLowerCase().startsWith('disallow:'))
.map((line) => line.split(':')[1].trim());
console.log('Respecting robots.txt disallowed paths:', disallowedPaths);
return disallowedPaths;
} catch (error) {
console.log('No robots.txt found, proceeding with fuzzing');
return [];
}
}
generateEthicalPayloads() {
// Payloads que no causan daño pero detectan vulnerabilidades
return [
// Test de validación sin ejecutar código
"'; SELECT 1; --", // SQL injection test (no destructivo)
"<img src=x onerror=console.log('xss')>", // XSS test (no malicioso)
'../etc/passwd', // Path traversal test
// Tests de límites
'A'.repeat(1000), // Test de buffer
'0'.repeat(100), // Test numérico
// Tests de encoding
'%2e%2e%2f', // URL encoded path traversal
'<script>', // HTML encoded script
];
}
}
// Uso ético y responsable
const ethicalFuzzer = new ResponsibleFuzzer({
maxRequestsPerSecond: 5, // No sobrecargar el servidor
respectRobotsTxt: true, // Respetar robots.txt
userAgent: 'SecurityResearch-ContactUs@company.com',
});
// Solo fuzzear tus propias aplicaciones o con permiso explícito
const myApplications = ['https://my-staging-app.com', 'https://my-test-environment.com'];
ethicalFuzzer.respectfulFuzz(myApplications);
Análisis de Resultados de Fuzzing
// Analizador de resultados de fuzzing
class FuzzingAnalyzer {
constructor(results) {
this.results = results;
this.vulnerabilities = [];
this.falsePositives = [];
}
analyze() {
this.categorizeFindings();
this.prioritizeVulnerabilities();
this.generateActionItems();
return {
summary: this.generateSummary(),
vulnerabilities: this.vulnerabilities,
recommendations: this.generateRecommendations(),
};
}
categorizeFindings() {
this.results.forEach((result) => {
if (this.isLikelyVulnerability(result)) {
this.vulnerabilities.push({
...result,
severity: this.calculateSeverity(result),
confidence: this.calculateConfidence(result),
});
} else if (this.isPotentialFalsePositive(result)) {
this.falsePositives.push(result);
}
});
}
isLikelyVulnerability(result) {
const indicators = [
result.response?.body?.includes('error'),
result.response?.body?.includes('exception'),
result.response?.body?.includes('mysql_'),
result.response?.body?.includes('ORA-'),
result.response?.status >= 500,
result.payload && result.response?.body?.includes(result.payload),
];
return indicators.filter(Boolean).length >= 2;
}
calculateSeverity(result) {
let score = 0;
// Criterios de severidad
if (result.type === 'SQL_INJECTION') score += 9;
if (result.type === 'XSS_REFLECTED') score += 7;
if (result.type === 'INFO_DISCLOSURE') score += 5;
if (result.response?.status >= 500) score += 3;
if (score >= 9) return 'CRITICAL';
if (score >= 7) return 'HIGH';
if (score >= 5) return 'MEDIUM';
return 'LOW';
}
generateRecommendations() {
const recommendations = new Set();
this.vulnerabilities.forEach((vuln) => {
switch (vuln.type) {
case 'SQL_INJECTION':
recommendations.add('Implement parameterized queries');
recommendations.add('Add input validation and sanitization');
break;
case 'XSS_REFLECTED':
recommendations.add('Implement output encoding');
recommendations.add('Add Content Security Policy headers');
break;
case 'INFO_DISCLOSURE':
recommendations.add('Configure custom error pages');
recommendations.add('Implement proper logging without exposing sensitive data');
break;
}
});
return Array.from(recommendations);
}
}
Conclusión
Fuzzing es una técnica esencial en el arsenal de cualquier desarrollador o security researcher serio. Te permite descubrir vulnerabilidades que testing manual nunca encontraría, automatizando la búsqueda de edge cases y comportamientos inesperados.
Principios clave para fuzzing efectivo:
- Automatización inteligente - Deja que las máquinas hagan el trabajo pesado
- Cobertura comprehensiva - Prueba todos los vectores de entrada posibles
- Análisis contextual - No solo generes datos random, entiende qué estás probando
- Responsabilidad ética - Solo fuzzea aplicaciones que tengas permiso para probar
- Integración continua - Haz fuzzing parte de tu pipeline de desarrollo
Recuerda: Fuzzing es como tener un ejército de testers virtuales trabajando 24/7 para encontrar problemas antes que los atacantes. La inversión en fuzzing se paga exponencialmente - mejor encontrar y arreglar vulnerabilidades durante desarrollo que lidiar con brechas de seguridad en producción.
Fuzzing no reemplaza otras técnicas de seguridad, pero es una herramienta indispensable para construir aplicaciones verdaderamente robustas y seguras.