Saltar al contenido

Patrones RASP de autoprotección en tiempo de ejecución

Monitoreo de seguridad en tiempo de ejecución que detecta y bloquea ataques desde dentro de tu app Node.js: inyección SQL, path traversal y anomalías.

5 min de lectura
Arquitectura de protección en tiempo de ejecución que muestra hooks de seguridad a nivel de aplicación interceptando solicitudes maliciosas antes de que lleguen a la lógica de negocio

Las herramientas de seguridad tradicionales —WAF, firewalls de red, análisis estático— operan fuera de tu aplicación. Ven solicitudes HTTP y paquetes de red, pero carecen del contexto de lo que la aplicación realmente hace con los datos. Runtime Application Self-Protection (RASP) integra comprobaciones de seguridad dentro de la propia aplicación, donde tiene visibilidad completa sobre cómo se procesan las entradas, qué consultas a la base de datos se construyen y a qué archivos se accede.

Esto no sustituye la seguridad perimetral: añade una capa interna crítica que detecta los ataques que logran pasar las defensas externas.

Detección de inyección SQL en la capa de consultas

En lugar de intentar sanear las entradas en el límite HTTP, RASP supervisa las consultas que realmente se ejecutan y señala los patrones sospechosos.

tstypescript
// ❌ Relying only on input validation at the HTTP layer
function validateInput(input: string): boolean {
  // Blocklist approach — always incomplete
  const suspicious = /('|--|;|DROP|UNION|SELECT)/i;
  return !suspicious.test(input);
}
// Bypassed by: URL encoding, Unicode tricks, comment injection
tstypescript
// ✅ RASP: Monitor query construction for injection signatures
interface QueryAnalysis {
  query: string;
  isSuspicious: boolean;
  reason?: string;
  severity: "low" | "medium" | "high" | "critical";
}
 
class SQLInjectionDetector {
  private readonly patterns: { pattern: RegExp; severity: string; description: string }[] = [
    {
      pattern: /UNION\s+(?:ALL\s+)?SELECT/i,
      severity: "critical",
      description: "UNION-based injection attempt",
    },
    {
      pattern: /;\s*(?:DROP|ALTER|TRUNCATE|DELETE\s+FROM)\s/i,
      severity: "critical",
      description: "Destructive statement appended",
    },
    {
      pattern: /OR\s+['"]?\d+['"]?\s*=\s*['"]?\d+['"]?/i,
      severity: "high",
      description: "Tautology-based authentication bypass",
    },
    {
      pattern: /SLEEP\s*\(\s*\d+\s*\)/i,
      severity: "high",
      description: "Time-based blind injection",
    },
    {
      pattern: /BENCHMARK\s*\(/i,
      severity: "high",
      description: "Benchmark-based blind injection",
    },
  ];
 
  analyze(query: string): QueryAnalysis {
    for (const { pattern, severity, description } of this.patterns) {
      if (pattern.test(query)) {
        return {
          query,
          isSuspicious: true,
          reason: description,
          severity: severity as QueryAnalysis["severity"],
        };
      }
    }
 
    return { query, isSuspicious: false, severity: "low" };
  }
}
 
// Wrap database client to intercept queries
function createProtectedClient(
  client: DatabaseClient,
  detector: SQLInjectionDetector,
  onThreat: (analysis: QueryAnalysis) => void
): DatabaseClient {
  return {
    ...client,
    async query(queryText: string, params?: unknown[]) {
      const analysis = detector.analyze(queryText);
 
      if (analysis.isSuspicious) {
        onThreat(analysis);
 
        if (
          analysis.severity === "critical" ||
          analysis.severity === "high"
        ) {
          throw new SecurityError(
            `Blocked suspicious query: ${analysis.reason}`
          );
        }
      }
 
      return client.query(queryText, params);
    },
  };
}

La idea clave es supervisar la cadena de consulta final, no la entrada. Las consultas parametrizadas previenen la inyección por diseño, así que cualquier consulta que contenga patrones de inyección significa que la aplicación está construyendo consultas de forma insegura o que un atacante encontró la manera de sortear la parametrización.

Prevención de path traversal

Las operaciones de acceso a archivos son objetivos de alto riesgo. RASP supervisa las llamadas al sistema de archivos para garantizar que se mantengan dentro de los directorios permitidos.

tstypescript
import { resolve, relative } from "path";
 
interface FileAccessPolicy {
  allowedRoots: string[];
  blockedExtensions: string[];
  maxPathLength: number;
}
 
class PathTraversalGuard {
  private readonly policy: FileAccessPolicy;
  private readonly resolvedRoots: string[];
 
  constructor(policy: FileAccessPolicy) {
    this.policy = policy;
    this.resolvedRoots = policy.allowedRoots.map(r => resolve(r));
  }
 
  validatePath(requestedPath: string): {
    allowed: boolean;
    resolvedPath: string;
    reason?: string;
  } {
    // Resolve to absolute path to eliminate ../
    const resolved = resolve(requestedPath);
 
    // Check path length
    if (resolved.length > this.policy.maxPathLength) {
      return {
        allowed: false,
        resolvedPath: resolved,
        reason: "Path exceeds maximum length",
      };
    }
 
    // Check if path is within allowed directories
    const withinAllowed = this.resolvedRoots.some(root => {
      const rel = relative(root, resolved);
      return !rel.startsWith("..") && !resolve(rel).startsWith("..");
    });
 
    if (!withinAllowed) {
      return {
        allowed: false,
        resolvedPath: resolved,
        reason: `Path ${resolved} is outside allowed directories`,
      };
    }
 
    // Check blocked extensions
    const ext = resolved.split(".").pop()?.toLowerCase() ?? "";
    if (this.policy.blockedExtensions.includes(`.${ext}`)) {
      return {
        allowed: false,
        resolvedPath: resolved,
        reason: `File extension .${ext} is blocked`,
      };
    }
 
    return { allowed: true, resolvedPath: resolved };
  }
}
 
const guard = new PathTraversalGuard({
  allowedRoots: ["/app/uploads", "/app/public"],
  blockedExtensions: [".env", ".key", ".pem", ".sh"],
  maxPathLength: 512,
});
 
// Usage in file serving middleware
function secureFileMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const result = guard.validatePath(req.params.filepath);
 
  if (!result.allowed) {
    logSecurityEvent({
      type: "path_traversal_attempt",
      path: req.params.filepath,
      resolvedPath: result.resolvedPath,
      reason: result.reason,
      ip: req.ip,
      timestamp: new Date(),
    });
 
    return res.status(403).json({ error: "Access denied" });
  }
 
  req.resolvedFilePath = result.resolvedPath;
  next();
}

Detección de anomalías en el comportamiento en tiempo de ejecución

RASP puede detectar comportamientos inusuales de la aplicación que podrían indicar un ataque en curso: picos repentinos en las consultas a la base de datos, patrones de acceso a endpoints fuera de lo común o tasas de error inesperadas.

tstypescript
interface BehaviorBaseline {
  endpoint: string;
  avgQueriesPerRequest: number;
  avgResponseTime: number;
  avgErrorRate: number;
  stdDevQueries: number;
  stdDevResponseTime: number;
}
 
class BehaviorMonitor {
  private baselines: Map<string, BehaviorBaseline> = new Map();
  private windowMs: number = 60_000; // 1-minute windows
  private currentWindow: Map<string, RequestMetrics[]> = new Map();
 
  recordRequest(endpoint: string, metrics: RequestMetrics): void {
    const requests = this.currentWindow.get(endpoint) ?? [];
    requests.push(metrics);
    this.currentWindow.set(endpoint, requests);
  }
 
  detectAnomalies(
    endpoint: string,
    metrics: RequestMetrics
  ): AnomalyReport | null {
    const baseline = this.baselines.get(endpoint);
    if (!baseline) return null;
 
    const anomalies: string[] = [];
 
    // Detect unusual number of database queries
    if (
      metrics.queryCount >
      baseline.avgQueriesPerRequest + 3 * baseline.stdDevQueries
    ) {
      anomalies.push(
        `Unusual query count: ${metrics.queryCount} ` +
        `(baseline: ${baseline.avgQueriesPerRequest.toFixed(1)})`
      );
    }
 
    // Detect unusual response time
    if (
      metrics.responseTime >
      baseline.avgResponseTime + 3 * baseline.stdDevResponseTime
    ) {
      anomalies.push(
        `Unusual response time: ${metrics.responseTime}ms ` +
        `(baseline: ${baseline.avgResponseTime.toFixed(0)}ms)`
      );
    }
 
    if (anomalies.length === 0) return null;
 
    return {
      endpoint,
      timestamp: new Date(),
      anomalies,
      metrics,
      baseline,
    };
  }
}
 
interface RequestMetrics {
  queryCount: number;
  responseTime: number;
  statusCode: number;
  timestamp: Date;
}
 
interface AnomalyReport {
  endpoint: string;
  timestamp: Date;
  anomalies: string[];
  metrics: RequestMetrics;
  baseline: BehaviorBaseline;
}

Registro de eventos de seguridad

Los eventos de RASP necesitan un registro estructurado que alimente los sistemas de monitoreo y alertas de seguridad.

tstypescript
interface SecurityEvent {
  type: string;
  severity: "info" | "warning" | "critical";
  timestamp: Date;
  source: {
    ip: string;
    userAgent?: string;
    userId?: string;
  };
  details: Record<string, unknown>;
  action: "logged" | "blocked" | "alerted";
}
 
class SecurityEventLogger {
  private events: SecurityEvent[] = [];
  private alertThresholds: Map<string, number> = new Map([
    ["sql_injection_attempt", 3],
    ["path_traversal_attempt", 5],
    ["brute_force_attempt", 10],
  ]);
 
  private recentCounts: Map<string, number> = new Map();
 
  log(event: SecurityEvent): void {
    this.events.push(event);
 
    // Track event frequency per type+IP
    const key = `${event.type}:${event.source.ip}`;
    const count = (this.recentCounts.get(key) ?? 0) + 1;
    this.recentCounts.set(key, count);
 
    const threshold = this.alertThresholds.get(event.type);
    if (threshold && count >= threshold) {
      this.escalate(event, count);
    }
 
    // Emit structured log
    console.log(JSON.stringify({
      level: "security",
      type: event.type,
      severity: event.severity,
      action: event.action,
      ip: event.source.ip,
      userId: event.source.userId,
      details: event.details,
      timestamp: event.timestamp.toISOString(),
    }));
  }
 
  private escalate(event: SecurityEvent, count: number): void {
    console.log(JSON.stringify({
      level: "security_alert",
      message: `Repeated ${event.type} from ${event.source.ip}: ${count} occurrences`,
      type: event.type,
      ip: event.source.ip,
      count,
      timestamp: new Date().toISOString(),
    }));
  }
}

Puntos clave

Runtime Application Self-Protection añade visibilidad de seguridad justo donde más importa: dentro de la aplicación, donde puedes ver cómo fluyen las entradas hacia las consultas, las operaciones de archivos y las llamadas al sistema. Supervisa las consultas a la base de datos en busca de patrones de inyección en la capa de construcción de la consulta, en lugar de en la capa de entrada HTTP, porque es ahí donde el ataque realmente se manifiesta. Protege las operaciones del sistema de archivos resolviendo las rutas y validando que permanezcan dentro de los directorios permitidos. Da seguimiento a las líneas base de comportamiento para detectar patrones anómalos, como picos repentinos de consultas o tasas de error inusuales, que indiquen una explotación activa. Registra los eventos de seguridad con datos estructurados que alimenten los sistemas de alertas, escalando cuando surjan patrones de ataque repetidos desde el mismo origen. RASP complementa, en lugar de sustituir, las defensas perimetrales: la combinación de WAF, validación de entradas, consultas parametrizadas y monitoreo en tiempo de ejecución crea una defensa en profundidad que ninguna capa por sí sola puede ofrecer.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX