Intermedioregexioclog-parsingherramientassoc

Regex para Ciberseguridad: Patrones de IOCs y Log Parsing

Referencia practica de expresiones regulares para ciberseguridad. Patrones para extraer IPs, hashes, dominios, URLs y emails de logs y reports. Tecnicas de log parsing con regex para analistas SOC y threat hunters.

MalwareIntel Research··6 min lectura
Serie: Handbooks y Guías Gratuitas — Parte 7

Regex como herramienta de seguridad

Las expresiones regulares son una habilidad transversal en ciberseguridad. Aparecen en reglas YARA, filtros de SIEM, parseo de logs, extraccion de IOCs de reports en PDF, y en cualquier tarea que implique buscar patrones en texto.

Este articulo es una referencia practica: patrones probados para los casos de uso mas comunes en ciberseguridad, con explicaciones de por que funcionan y sus limitaciones.

Patrones para IOCs

Direcciones IPv4

\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b

Este patron valida que cada octeto este entre 0 y 255. Es mas largo que el basico (cuatro grupos de 1 a 3 digitos separados por puntos) pero evita falsos positivos como 999.999.999.999.

Para excluir IPs privadas y reservadas:

import re
import ipaddress

ipv4_pattern = re.compile(
    r'\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}'
    r'(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b'
)

def extract_public_ips(text):
    """Extrae IPs publicas de un texto."""
    results = []
    for match in ipv4_pattern.finditer(text):
        ip_str = match.group()
        try:
            ip = ipaddress.ip_address(ip_str)
            if ip.is_global:  # excluye privadas, loopback, link-local
                results.append(ip_str)
        except ValueError:
            continue
    return results

Direcciones IPv6

\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b

IPv6 tiene muchas formas validas (abreviaciones con ::). Un patron robusto:

\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\b|::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}\b

En la practica, la mayoria de IOCs IPv6 en reports CTI usan la forma completa. Para produccion, usa la libreria ipaddress de Python para validar.

Hashes criptograficos

# MD5: exactamente 32 hex chars
\b[a-fA-F0-9]{32}\b

# SHA-1: exactamente 40 hex chars
\b[a-fA-F0-9]{40}\b

# SHA-256: exactamente 64 hex chars
\b[a-fA-F0-9]{64}\b

# SHA-512: exactamente 128 hex chars
\b[a-fA-F0-9]{128}\b

Cuidado con colisiones: un UUID sin guiones (32 hex chars) coincide con el patron de MD5. Contexto importa.

hash_patterns = dict(
    md5=re.compile(r'\b[a-fA-F0-9]{32}\b'),
    sha1=re.compile(r'\b[a-fA-F0-9]{40}\b'),
    sha256=re.compile(r'\b[a-fA-F0-9]{64}\b'),
)

def extract_hashes(text):
    """Extrae hashes por tipo."""
    results = dict(md5=[], sha1=[], sha256=[])
    # Procesar de mayor a menor longitud para evitar substrings
    for match in hash_patterns["sha256"].finditer(text):
        results["sha256"].append(match.group().lower())
    # Eliminar los sha256 del texto antes de buscar sha1
    cleaned = hash_patterns["sha256"].sub("", text)
    for match in hash_patterns["sha1"].finditer(cleaned):
        results["sha1"].append(match.group().lower())
    cleaned = hash_patterns["sha1"].sub("", cleaned)
    for match in hash_patterns["md5"].finditer(cleaned):
        results["md5"].append(match.group().lower())
    return results

Dominios (FQDN)

\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b

Este patron captura dominios con subdominios (mail.evil.com, c2.malware.example.org). Para reducir falsos positivos en texto tecnico, puedes filtrar por TLDs conocidos o excluir extensiones de archivo (.exe, .dll).

Dominios defanged

Los reports CTI frecuentemente "defangean" dominios para evitar clics accidentales:

evil[.]com       →  evil.com
hxxps://evil.com →  https://evil.com
evil[.]com[:]443 →  evil.com:443

Patron para dominios defanged:

\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\[?\.\]?)+[a-zA-Z]{2,}\b

Funcion de refanging:

def refang(text):
    """Convierte IOCs defanged a formato real."""
    text = text.replace("[.]", ".")
    text = text.replace("hxxp", "http")
    text = text.replace("hxxps", "https")
    text = text.replace("[:]", ":")
    text = text.replace("[://]", "://")
    text = text.replace("[@]", "@")
    return text

URLs

https?://[^\s<>"']+

Patron simple que funciona para la mayoria de URLs en logs y reports. Para URLs con caracteres especiales o parametros complejos:

https?://(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?::\d{1,5})?(?:/[^\s<>"']*)?

Emails

\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b

En contexto de ciberseguridad, los emails aparecen como IOCs de phishing (remitente), como datos exfiltrados, o como identificadores de actores en foros.

CVEs

CVE-\d{4}-\d{4,7}

Captura identificadores CVE en formato estandar. El ano tiene 4 digitos y el ID tiene entre 4 y 7 digitos.

Mutex names

No hay un patron universal para mutex, pero los que usa el malware frecuentemente siguen patrones:

(?:Global\\|Local\\)?[a-zA-Z0-9_\-]{8,64}

MITRE ATT&CK IDs

T\d{4}(?:\.\d{3})?

Captura tecnicas (T1055) y subtecnicas (T1055.001).

Log parsing

Apache/Nginx access logs

Formato combined log:

192.168.1.100 - user [10/Jun/2026:13:55:36 +0200] "GET /admin HTTP/1.1" 200 1234 "http://evil.com" "Mozilla/5.0"
apache_pattern = re.compile(
    r'(?P<ip>\S+) \S+ (?P<user>\S+) '
    r'\[(?P<timestamp>[^\]]+)\] '
    r'"(?P<method>\S+) (?P<path>\S+) (?P<protocol>\S+)" '
    r'(?P<status>\d{3}) (?P<bytes>\d+|-) '
    r'"(?P<referer>[^"]*)" '
    r'"(?P<useragent>[^"]*)"'
)

def parse_apache_log(line):
    match = apache_pattern.match(line)
    if match:
        return match.groupdict()
    return None

Windows Event Logs (texto exportado)

Los eventos de seguridad de Windows exportados como texto tienen campos clave:

# Extraer SIDs de eventos
sid_pattern = re.compile(r'S-\d-\d+-(?:\d+-){1,14}\d+')

# Extraer logon types
logon_pattern = re.compile(r'Logon Type:\s+(\d+)')

# Extraer nombres de proceso
process_pattern = re.compile(r'Process Name:\s+(.+?)(?:\r?\n|\s{2,})')

Syslog

syslog_pattern = re.compile(
    r'(?P<timestamp>\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+'
    r'(?P<hostname>\S+)\s+'
    r'(?P<process>\S+?)(?:\[(?P<pid>\d+)\])?\s*:\s*'
    r'(?P<message>.*)'
)

Firewall logs (iptables)

iptables_pattern = re.compile(
    r'SRC=(?P<src_ip>\d+\.\d+\.\d+\.\d+)\s+'
    r'DST=(?P<dst_ip>\d+\.\d+\.\d+\.\d+)\s+'
    r'.*?PROTO=(?P<proto>\w+)\s+'
    r'.*?(?:SPT=(?P<src_port>\d+))?\s*'
    r'(?:DPT=(?P<dst_port>\d+))?'
)

Regex en herramientas de seguridad

YARA

YARA usa regex Perl-compatible en sus condiciones:

rule detect_c2_url
{
    strings:
        $url = /https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\/\-_]+\.php/ nocase

    condition:
        $url
}

Sigma

Las reglas Sigma usan regex en filtros:

detection:
    selection:
        CommandLine|re: '.*\\\\(cmd|powershell)\.exe.*-enc.*[A-Za-z0-9+/=]{50,}'
    condition: selection

Splunk SPL

index=firewall sourcetype=iptables
| rex field=_raw "SRC=(?P<src_ip>\d+\.\d+\.\d+\.\d+)"
| rex field=_raw "DST=(?P<dst_ip>\d+\.\d+\.\d+\.\d+)"
| rex field=_raw "DPT=(?P<dst_port>\d+)"
| where dst_port IN ("4444", "5555", "8080", "9090")

Elastic ECS / KQL

process.command_line : /.*powershell.*-enc.*[A-Za-z0-9+\/=]{100,}/

Extraccion masiva de IOCs

Script completo de extraccion

import re
from collections import defaultdict

class IOCExtractor:
    """Extrae IOCs de texto libre (reports, emails, logs)."""

    PATTERNS = dict(
        ipv4=re.compile(
            r'\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}'
            r'(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b'
        ),
        md5=re.compile(r'\b[a-fA-F0-9]{32}\b'),
        sha1=re.compile(r'\b[a-fA-F0-9]{40}\b'),
        sha256=re.compile(r'\b[a-fA-F0-9]{64}\b'),
        domain=re.compile(
            r'\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b'
        ),
        url=re.compile(r'https?://[^\s<>"\']+'),
        email=re.compile(r'\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b'),
        cve=re.compile(r'CVE-\d{4}-\d{4,7}'),
        mitre=re.compile(r'\bT\d{4}(?:\.\d{3})?\b'),
    )

    def extract(self, text):
        """Extrae todos los IOCs del texto."""
        text = self._refang(text)
        results = defaultdict(set)
        for ioc_type, pattern in self.PATTERNS.items():
            for match in pattern.finditer(text):
                value = match.group()
                if ioc_type in ("md5", "sha1", "sha256"):
                    value = value.lower()
                results[ioc_type].add(value)
        return dict(sorted=list, **results)

    def _refang(self, text):
        """Reemplaza IOCs defanged."""
        text = text.replace("[.]", ".")
        text = text.replace("hxxp", "http")
        text = text.replace("[://]", "://")
        return text

Rendimiento y trampas

Catastrophic backtracking

Regex con cuantificadores anidados pueden causar tiempo de ejecucion exponencial:

# PELIGROSO: backtracking catastrofico
(a+)+b

# SEGURO: equivalente sin ambiguedad
a+b

En produccion, usa re.compile() con timeout o la libreria regex de Python que soporta limites de backtracking.

Unicode y encodings

Los logs pueden contener caracteres Unicode (especialmente en user-agents o payloads codificados). Usa el flag re.UNICODE y asegurate de que la lectura de archivos especifica encoding:

with open("log.txt", encoding="utf-8", errors="replace") as f:
    text = f.read()

Falsos positivos comunes

  • Hashes MD5 vs UUIDs sin guiones (ambos 32 hex chars)
  • IPs que son versiones de software (1.2.3.4)
  • Dominios que son nombres de archivo (malware.exe matchea como dominio)
  • CVE-like patterns en texto no relacionado

Siempre valida los resultados con contexto o cruzando con bases de datos de CTI.

Conclusiones

Regex no reemplaza herramientas especializadas de extraccion de IOCs, pero es la habilidad que te permite adaptarte a cualquier formato de datos. Los patrones de este articulo cubren los casos mas frecuentes en trabajo SOC y CTI. Adaptalos a tus necesidades y mantenlos en un repositorio propio como referencia rapida.

Preguntas frecuentes

Artículos relacionados

Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.