Capítulo IV: Sistema de Plugins de Escaneo
Uno de los pilares del diseño arquitectónico de TVAPyMM es su motor de escaneo desacoplado y basado en un sistema de plugins extensibles.
Este documento describe detalladamente cómo opera este sistema modular, la orquestación concurrente implementada mediante Kotlin Coroutines en el backend y los pasos detallados para crear e integrar un nuevo escáner de seguridad en el proyecto.
🧩 La Arquitectura de Plugins
En lugar de definir secuencias de auditoría secuenciales o acumular un sinfín de validaciones en clases gigantescas, TVAPyMM encapsula las pruebas de seguridad en componentes independientes.
Cualquier escáner que se incorpore a la plataforma debe implementar la interfaz común VulnerabilityScanner:
interface VulnerabilityScanner {
/**
* Lista de categorías de OWASP soportadas por este escáner.
* Un único escáner puede realizar validaciones y reportar hallazgos de múltiples categorías.
*/
val categories: List<OwaspCategory>
/**
* Ejecuta el análisis de forma asíncrona y retorna la lista de vulnerabilidades encontradas.
*/
suspend fun scan(targetUrl: String, project: Project): List<Vulnerability>
}
Inyección y Detección Dinámica
Gracias a la anotación @Component de Spring Framework, el contenedor detecta en tiempo de ejecución todas las clases que implementan dicha interfaz dentro del classpath, inyectándolas de forma automática en una colección administrada por el servicio ScanService:
@Service
class ScanService(
private val scanners: List<VulnerabilityScanner>, // ¡Inyectados dinámicamente al arrancar la app!
private val vulnerabilityRepository: VulnerabilityRepository
) { ... }
⚡ Concurrencia mediante Kotlin Coroutines
Las validaciones de seguridad consumen mucho tiempo de I/O de red (envío de cabeceras HTTP, negociaciones de SSL/TLS, escaneo de puertos, análisis sintáctico de código HTML y payloads). Ejecutar estas pruebas una por una crearía un cuello de botella inaceptable.
TVAPyMM saca partido de Kotlin Coroutines para iniciar y ejecutar todos los escáneres solicitados simultáneamente en diferentes hilos de ejecución ligeros.
Rutina de Ejecución Paralela
Dentro del servicio ScanService, el flujo filtra los escáneres configurados de acuerdo con la selección realizada por el usuario y los orquesta concurrently:
suspend fun scanProject(targetUrl: String, project: Project, selectedCategories: List<OwaspCategory>): List<Vulnerability> {
return coroutineScope {
scanners
// 1. Filtrar los escáneres que soporten al menos una categoría seleccionada
.filter { scanner ->
scanner.categories.any { it in selectedCategories }
}
// 2. Lanzar de forma asíncrona y paralela la ejecución de cada escáner
.map { scanner ->
async {
try {
logger.info("Iniciando escaneo con ${scanner::class.simpleName} sobre el objetivo: $targetUrl")
scanner.scan(targetUrl, project)
} catch (e: Exception) {
logger.error("El escáner ${scanner::class.simpleName} falló: ${e.message}", e)
emptyList<Vulnerability>() // Salvaguarda: un fallo no detiene los demás escáneres
}
}
}
// 3. Esperar los resultados de todos y agruparlos en una lista única de hallazgos
.awaitAll()
.flatten()
}
}
Si su escáner utiliza alguna librería cliente que no soporte de forma nativa llamadas no bloqueantes, asegúrese de encapsular ese bloque utilizando withContext(Dispatchers.IO) { ... } para no restarle recursos al despachador principal.
🔍 Escáneres Incorporados y Clases de Apoyo
El core del backend provee un conjunto inicial de herramientas de análisis bastante maduro. Para mantener las clases de los escáneres sumamente enfocadas y limpias, las pruebas repetitivas se modularizan en Checkers dentro del paquete scanner/tls/:
TlsConfigChecker: Examina los protocolos criptográficos soportados (TLS 1.2, TLS 1.3) y reporta el uso de suites de cifrado obsoletas o inseguras.CertificateChecker: Valida la vigencia y correspondencia del nombre de dominio dentro de la cadena de confianza de los certificados SSL.HstsChecker: Verifica la correcta configuración de la cabeceraStrict-Transport-Security.CookieChecker: Comprueba la presencia de banderas esenciales comoSecure,HttpOnlyy directivas modernasSameSite.SecurityHeadersChecker: Proba si se están transmitiendo cabeceras defensivas cruciales comoX-Frame-Options,Content-Security-PolicyoX-Content-Type-Options.MixedContentChecker: Inspecciona el código de respuesta en busca de assets estáticos (JavaScript, CSS, imágenes) referenciados mediante protocolo no cifrado HTTP normal.
🛠️ Paso a Paso: Crear un Nuevo Escáner de Seguridad
Añadir una regla de negocio nueva o la comprobación de una nueva categoría de vulnerabilidades es un proceso sumamente sencillo que cumple al pie de la letra con el Principio Abierto/Cerrado.
Paso 1: Registrar el enum en OwaspCategory.kt
Si desea soportar un nuevo vector de ataque o categoría, edite el archivo model/entity/OwaspCategory.kt para darle de alta:
enum class OwaspCategory(val code: String, val displayName: String, val description: String) {
// ... categorías existentes ...
WEB_A05_SECURITY_MISCONFIGURATION(
"A5",
"Web A5 - Configuración de Seguridad Incorrecta",
"Busca banners informativos expuestos, carpetas abiertas o volcados de error visibles."
)
}
Paso 2: Programar la clase del Escáner
Cree un archivo Kotlin en la carpeta scanner/category/ que implemente la interfaz VulnerabilityScanner y configure la anotación de Spring @Component:
package com.tvapymm.backend.scanner.category
import com.tvapymm.backend.model.entity.AnalysisType
import com.tvapymm.backend.model.entity.OwaspCategory
import com.tvapymm.backend.model.entity.Project
import com.tvapymm.backend.model.entity.Vulnerability
import com.tvapymm.backend.scanner.VulnerabilityScanner
import org.springframework.stereotype.Component
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
@Component
class SecurityMisconfigurationScanner : VulnerabilityScanner {
override val categories = listOf(OwaspCategory.WEB_A05_SECURITY_MISCONFIGURATION)
override suspend fun scan(targetUrl: String, project: Project): List<Vulnerability> {
val vulnerabilities = mutableListOf<Vulnerability>()
try {
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI.create(targetUrl))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
val serverHeader = response.headers().firstValue("Server").orElse("")
// Verificar si la cabecera expone de forma detallada la tecnología subyacente
if (serverHeader.contains("/") || serverHeader.lowercase().contains("apache") || serverHeader.lowercase().contains("nginx")) {
vulnerabilities.add(
Vulnerability(
project = project,
analysisType = AnalysisType.DAST,
owaspCategory = OwaspCategory.WEB_A05_SECURITY_MISCONFIGURATION,
severity = "MEDIUM",
description = "Fuga de información en la cabecera 'Server'. El servidor web responde indicando marca y versión exacta: '$serverHeader'.",
evidence = "Cabecera Server recibida en HTTP GET: $serverHeader",
recommendation = "Ajuste el servidor para ocultar o simplificar la cabecera 'Server' (por ejemplo, directiva 'ServerSignature Off' en Apache, o 'server_tokens off' en Nginx)."
)
)
}
} catch (e: Exception) {
// Manejar excepciones de manera local para salvaguardar el resto de análisis concurrentes
}
return vulnerabilities
}
}
Paso 3: Escribir sus Pruebas Unitarias o de Integración
Incorpore pruebas de validación dentro del directorio de testeo src/test/kotlin/:
class SecurityMisconfigurationScannerTest {
@Test
fun `verificar que el escaneo reporta la firma expuesta`() {
val scanner = SecurityMisconfigurationScanner()
// Ejecutar aserciones utilizando servicios mockeados o servidores web locales...
}
}
Paso 4: ¡Todo listo! 🎉
Spring Boot incorporará de forma automática el componente y estará disponible inmediatamente para iniciar análisis desde el frontend de forma transparente, sin configurar mapeos extras ni cableados de lógica.