🎵 Demanufacture — Fear Factory

de Demanufacture

Me llegó una historia de esas que el gremio cuenta en voz baja, sin nombres, como quien pasa un IOC de mano en mano. Un SOC con turnos rotativos. Volumen alto de alertas, del que ya escribí en otra parte: la cinta no se detiene para que pienses. Un analista, en algún punto del turno, carga un indicador en el MISP de la casa — una IP, una más entre las decenas de ese día — y la IP resulta ser privada. Una dirección RFC 1918, de esas que existen por millones dentro de cualquier red y no identifican nada fuera de ella. El evento se comparte, un cliente la ve, el cliente se queja. Y la organización responde con el control de seguridad más antiguo y más barato que existe: le vacía el escritorio al analista. Fin del incidente, según el acta. La persona que me lo contó preguntaba si estaba mal por pensar que la culpa no era del analista. No solo no está mal: la pregunta correcta es otra, y este ensayo existe para reformularla. Pero antes hay que hacer dos trabajos, porque me pidieron dos mitades — la crítica y la guía — y las dos son el mismo argumento contado en dos lenguajes.

El error humano no es una causa: es un síntoma que llegó a producción. Hay una literatura entera sobre esto — Sidney Dekker la resume en una fórmula que uso como lente desde que la leí: el error humano no es la conclusión de la investigación, es el punto de partida. Cuando el análisis de un incidente termina en «el operador se equivocó», lo que en realidad terminó es la voluntad de seguir investigando. Porque la pregunta siguiente es obvia y nadie quiere hacerla: ¿qué tenía que pasar después del error para que el error llegara al cliente? Y la respuesta, en esta historia, es: nada. No había nada después. Ninguna validación al ingreso, ninguna lista de advertencia activa, ninguna revisión antes de publicar, ningún inventario contra el cual contrastar. El dato viajó del cansancio de una persona a la pantalla de un cliente sin tocar un solo control intermedio, porque el único control intermedio era la persona. Un turno rotativo, decenas de alertas, memoria de trabajo saturada — exactamente las condiciones sobre las que escribí en Tú puedes: el sistema fabrica la falla en serie y después la factura a nombre del individuo. Despedir al analista es despedir al único control que la arquitectura tenía. La postura de seguridad de esa organización no mejoró con el despido; empeoró, porque ahora el control es una persona nueva, con menos contexto, y con una lección aprendida que es exactamente la equivocada: acá los errores se esconden.

El teatro no está en que el error fuera posible: está en la respuesta. La persona que me contó la historia usó la palabra «teatro» — si el modelo de seguridad que venden permite ese error, está mal diseñado, es un teatro — y quiero afinarla, porque la palabra es correcta pero apunta a otra escena. Que a un sistema le falte un control no es teatro: es deuda, y toda arquitectura real la tiene; el MISP recién instalado no valida rangos privados igual que la puerta recién colocada no viene con alarma. El teatro empieza cuando la organización actúa la seguridad en lugar de repararla — y despedir al analista es la función de gala. Tiene todo lo que el teatro necesita: un culpable visible, un acto decisivo, un cliente que ve sangre y se da por satisfecho, y ningún cambio en la probabilidad de que vuelva a pasar. La aviación entendió esto hace medio siglo y construyó lo contrario: el sistema ASRS, donde un piloto que reporta su propio error recibe, bajo condiciones, inmunidad — porque alguien hizo la cuenta y le dio que un error contado vale más que un culpable castigado. Cada informe es un sensor. Un gremio que castiga el reporte se queda ciego a cambio de parecer estricto, que es la definición operativa de teatro: gastar el presupuesto en la percepción del control en lugar del control. La comodidad de cerrar el caso con un despido es la misma comodidad que describí en Cuando no pasa nada — la organización queda tranquila justo en el momento en que debería estar más inquieta, porque ahora sabe que su única barrera era una persona y decidió no construir otra.

Ahora la guía, porque la crítica sin parche es otra forma de teatro. ¿Qué tan difícil es evitar que una IP privada entre a un MISP? Esta es la parte que más me irrita de toda la historia: es un problema resuelto. No hay que investigar nada, no hay que desarrollar nada exótico; hay tres niveles de dureza, los tres documentados, y se implementan en una tarde. Aclaración de rigor antes de los tres: MISP cambia entre versiones — todo lo que sigue hay que probarlo en la instancia propia antes de darlo por hecho, con un evento de prueba, no con producción.

Nivel 0 — advertir: warninglists. MISP trae de fábrica una lista de advertencia con los bloques RFC 1918. Se activa en Warninglists (sincronizar y habilitar la lista de CIDR privados) y desde ahí todo atributo que caiga en esos rangos aparece marcado visualmente en el evento. No bloquea nada — el analista cansado puede ignorar la marca igual que ignora el enésimo banner — pero tiene un segundo uso que casi nadie explota: el parámetro enforceWarninglist en la API. Cualquier export hacia sensores o clientes puede pedirse así:

POST /attributes/restSearch
{
  "returnFormat": "json",
  "type": ["ip-src", "ip-dst"],
  "to_ids": 1,
  "enforceWarninglist": true
}

Con eso, aunque la IP privada entre, no sale: el feed que consume el cliente la filtra. Es el control compensatorio mínimo — si el SOC de la historia hubiera tenido esta línea en sus exports, el cliente jamás habría visto nada y el analista seguiría empleado. Una línea.

Nivel 1 — bloquear en la puerta: filtros de entrada. En Administration → Input filters → Regular expressions se pueden definir regex que se aplican a todo atributo entrante, y una regla cuyo reemplazo queda vacío hace que el atributo se descarte en la validación. Para rangos privados, loopback y link-local:

^(10\.(\d{1,3}\.){2}\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|127\.(\d{1,3}\.){2}\d{1,3}|169\.254\.\d{1,3}\.\d{1,3})$

El atributo muere con un «Attribute dropped due to validation» y no entra ni por la interfaz web ni — según reporta el propio proyecto en sus issues — por la API y PyMISP, aunque esto último es exactamente el tipo de afirmación que se verifica en la versión propia y no se hereda de un ensayo. El costo es de experiencia de usuario: el mensaje de error es seco, no le explica al analista por qué — el sistema bloquea por defecto lo que no puede integrar, como escribí en Bloqueado por defecto, y este filtro es ese principio en miniatura. Para un control de emergencia, alcanza y sobra.

Nivel 2 — validación en el modelo. Para quien quiera el error bonito y en el lugar arquitectónicamente correcto, la validación se agrega en el modelo de atributos del propio MISP (CakePHP, app/Model/Attribute.php), donde PHP ya trae la primitiva exacta:

if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false) {
    return 'Rango privado (RFC 1918): esta IP no identifica nada fuera de tu red.';
}

Mensaje claro, bloqueo total, cero regex. El costo es el que siempre cobra tocar el código de un producto vivo: acabas de crear un fork que hay que rebasar en cada upgrade. La jugada adulta no es mantener el parche en silencio sino proponerlo aguas arriba como opción de configuración — si el problema le dolió a este SOC, le duele a cientos.

Antes de bloquear con entusiasmo: hay escenarios donde una IP privada es un IOC legítimo — movimiento lateral documentado en un informe forense, un C2 interno, infraestructura de un adversario dentro de la propia red. El control correcto no es «las IP privadas no existen» sino «las IP privadas no salen del perímetro de compartición»: bloqueo duro en instancias que comparten con clientes o comunidades, y distribución Your organisation only como valor por defecto en la instancia interna. El filtro se dimensiona al contexto de distribución, no al tipo de dato.

Y hay un cuarto control que no es técnico y vale más que los tres: el principio de cuatro ojos ya viene incluido en el RBAC de MISP. Un rol de analista sin permiso de publicación, y un paso de revisión donde alguien con más contexto publica el evento. Eso convierte «un humano cansado» en «dos humanos en momentos distintos del turno», que es la diferencia entre un punto único de falla y un control de verdad. La organización de la historia tenía esta función disponible, gratis, en el software que ya corría. Publicaba sin revisión de todos modos. Eso también es una decisión de diseño; solo que nadie la firmó.

Pero la regex no resuelve el caso que de verdad duele, y acá entra el inventario. Miren de nuevo la queja del cliente. La versión que me llegó dice «IP privada», y para esa versión alcanzan los niveles de arriba. Pero el caso hermano — el que la regex no ve ni puede ver — es el analista que carga una IP pública del cliente: la VPN corporativa, el mail server, el rango del proveedor de hosting donde el cliente tiene medio negocio. Esa IP es sintácticamente indistinguible de un IOC real. Ningún filtro genérico del planeta la detecta, porque no es un error de formato: es un error de contexto, y el contexto no viene en el paquete — hay que tenerlo en un inventario. Que el SOC de la historia no tuviera un inventario de IP y servicios consultable no es un detalle: es la vulnerabilidad raíz de la que el despido fue el exploit. La buena noticia es que MISP también tiene la pieza para esto: las warninglists son JSON y se pueden crear propias. Una lista con los rangos de cada cliente —

{
  "name": "Rangos de clientes — no compartir",
  "type": "cidr",
  "description": "Espacio IP propio y de clientes. Coincidencia = probable falso IOC.",
  "matching_attributes": ["ip-src", "ip-dst", "domain|ip"],
  "list": ["203.0.113.0/24", "198.51.100.0/24"]
}

— y cada rango de cliente que un analista intente cargar aparece marcado en el momento, y filtrado del export con el mismo enforceWarninglist de arriba. Lo que exige es lo que ninguna regex regala: que el inventario exista. Las prácticas son las de siempre y por eso nadie las hace — una sola fuente de verdad (un IPAM como NetBox, no siete planillas), cada rango con un dueño humano y una fecha de revisión, alta y baja de rangos atada al alta y baja de clientes en el contrato, y un sync automático — un cron que regenera la warninglist desde el IPAM — para que la lista no se convierta en la planilla número ocho. Con eso, el «no tener dónde consultar» del turno rotativo desaparece: la consulta pasa a estar dentro de la herramienta, en el momento del ingreso, que es el único lugar donde un control le sirve a alguien que tiene cuarenta segundos por indicador.

Desagrego, porque me comprometí a auditar también las historias que me dan la razón. Esta me llegó de un solo lado, sin el expediente. Cabe la posibilidad de que hubiera contexto que la vuelve menos limpia: un historial de errores previos, una advertencia documentada, una cláusula contractual que convertía el dato en incidente grave. La cultura justa — la de verdad, no la del póster — no dice «nunca hay consecuencias individuales»: distingue entre el error honesto, la conducta de riesgo repetida y la imprudencia, y trata cada una distinto. Si el analista era reincidente advertido, el despido cambia de género y deja de ser teatro. Pero noten qué no cambia con ninguna de esas hipótesis: la arquitectura. Aun en la versión menos favorable al analista, un dato erróneo viajó de un teclado a un cliente sin cruzar un solo control automático ni un segundo par de ojos, en un sistema que traía ambas cosas de fábrica, apagadas. La culpabilidad individual es una variable de la historia; la ausencia de barreras es una constante. Y las constantes son lo único que el próximo turno hereda.

Así que reformulo la pregunta, que venía mal enunciada de fábrica. «¿Cómo evito que un analista ingrese una IP privada?» ya está contestada — tres niveles, elijan dureza. Pero es la pregunta chica. La grande, la que cualquier gerente puede hacerse sobre cualquier sistema que opere, es: ¿cuántos controles separan el error individual del daño visible al cliente? En esta historia la respuesta era cero, y cuando la respuesta es cero, cada empleado es un single point of failure con piernas y la única política de seguridad real es la esperanza. El número decente es dos — una validación automática en el ingreso y un humano distinto antes de publicar —, y en MISP los dos existen, documentados, sin escribir una línea de código si no se quiere. El día que ese SOC los encienda, el error del analista despedido pasará a ser lo que siempre debió ser: un evento de log, atrapado por la primera capa, invisible para el cliente, olvidado a los diez minutos.

Cierro con la cuenta que nadie hizo en esa oficina. Un despido cuesta un reclutamiento, meses de curva de aprendizaje, y un mensaje al resto del turno: escondan los errores. Una regex cuesta una tarde. Un rol sin permiso de publicación cuesta una reunión. El inventario cuesta lo que siempre costó — voluntad sostenida —, pero se paga una vez y descuenta para siempre. La organización eligió la opción más cara, la única que no arregla nada, porque era la única que parecía una respuesta. El analista ya no está; el sistema que lo usó de fusible sigue exactamente igual, esperando al siguiente. Espero que el siguiente lea la documentación de su MISP antes que este ensayo. Está todo ahí.

commit dr0p1918

Date: 2026-07-14 06:12 PM

attribute dropped due to validation; analyst dropped due to optics