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.
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.