Aprende › Vulnerabilidades web

Side-channel attacks: timing, power analysis, fault injection y glitching

· 15 min de lectura

Los ataques de canal lateral extraen secretos observando propiedades físicas: tiempo de ejecución, consumo eléctrico, radiación electromagnética, o inyectando fallos controlados. Aprende las categorías de side-channels, ataques prácticos (DPA, SPA, glitching), defensas, y el impacto de Spectre/Meltdown.

Los ataques de canal lateral (side-channel attacks) explotan la información que se escapa por la implementación física de un sistema, no por su diseño lógico. Un algoritmo criptográfico puede ser matemáticamente perfecto, pero si al ejecutarse en hardware real consume distinta energía, tarda distinto tiempo o emite distinta radiación electromagnética según la clave, quien pueda medir esas magnitudes puede recuperar la clave sin romper la matemática.

Este campo une criptografía, electrónica y física. Desde que Paul Kocher demostró en 1996 ataques de tiempo contra implementaciones de RSA y Diffie-Hellman, hasta Spectre y Meltdown en 2018, los canales laterales han cambiado lo que entendemos por "seguro". Esta lección cubre la taxonomía de side-channels, la causa raíz en el código, las técnicas prácticas (SPA, DPA, fault injection), cómo se detectan las fugas, cómo se corrigen y el impacto moderno en CPUs. Es la decimotercera lección de la Tanda 7.

Practica solo sobre hardware y software propios o con autorización expresa. La investigación de canales laterales en productos comerciales se publica siguiendo divulgación coordinada con el fabricante.

Qué es un canal lateral#

Un canal lateral es cualquier magnitud observable que depende de un dato secreto aunque el programa nunca la haya expuesto a propósito. El programa devuelve "acceso denegado" igual para todas las contraseñas incorrectas, pero si tarda 3 microsegundos más cuando el primer carácter es correcto, el tiempo de respuesta se ha convertido en un canal de información.

Taxonomía de side-channels: timing, power, EM, acústico, inyección de fallos y caché
Taxonomía de side-channels: timing, power, EM, acústico, inyección de fallos y caché

Hay dos grandes familias: la observación pasiva, en la que el atacante solo mide, y la inyección activa de fallos, en la que perturba el dispositivo para que se equivoque de una forma aprovechable.

Por qué ocurre: la causa raíz#

La causa raíz casi siempre es la misma: el flujo de ejecución o los accesos a memoria dependen del secreto. El ejemplo de manual es la comparación de cadenas que sale en cuanto encuentra el primer byte distinto:

# VULNERABLE: tiempo proporcional a los bytes correctos del prefijo
def check_token(recibido: bytes, esperado: bytes) -> bool:
    if len(recibido) != len(esperado):
        return False
    for a, b in zip(recibido, esperado):
        if a != b:
            return False   # salida temprana: filtra la posición del fallo
    return True

Cada byte acertado añade una iteración más al bucle. Con suficientes mediciones, un atacante prueba los 256 valores del primer byte, se queda con el que tarda más, y avanza byte a byte. Un secreto de 32 bytes deja de costar 256^32 intentos y pasa a costar como mucho 32 × 256.

El mismo patrón aparece en criptografía. La exponenciación modular "square-and-multiply" de RSA hace una multiplicación extra solo cuando el bit de la clave vale 1:

// VULNERABLE: la operación depende del bit secreto
for (int i = nbits - 1; i >= 0; i--) {
    r = mod_square(r, n);
    if (bit(d, i))          // rama sobre el secreto
        r = mod_mul(r, m, n);
}

Esa rama se ve en el tiempo total, en la traza de consumo eléctrico y en los accesos a caché. Y las tablas de búsqueda indexadas por el secreto (las T-tables clásicas de AES) filtran el índice a través de qué líneas de caché se cargan.

Tipos de canales laterales#

CanalQué se mideAcceso necesarioEjemplo histórico
TimingDuración de la operaciónRemoto o localKocher 1996, RSA/DH
SPAUna traza de consumoFísicoSquare-and-multiply visible
DPA / CPAMiles de trazas + estadísticaFísicoKocher, Jaffe y Jun 1999
ElectromagnéticoRadiación cercana al chipFísico, sin contactoSondas near-field
AcústicoRuido de componentesProximidadGenkin, Shamir y Tromer 2013
CachéHits y misses de cachéCódigo en la misma máquinaFlush+Reload, Prime+Probe
Fault injectionRespuesta ante fallos inducidosFísicoGlitching de bootloaders

Observación pasiva#

Timing. Se mide cuánto tarda una operación. Funciona incluso a través de la red si se promedian suficientes mediciones, porque el ruido de red es aleatorio y la diferencia del secreto es sistemática.

Power analysis. Se mide el consumo eléctrico durante operaciones criptográficas:

  • SPA (Simple Power Analysis): una sola traza revela operaciones. En RSA con square-and-multiply, el patrón en el osciloscopio distingue bits 0 y 1 de la clave.
  • DPA (Differential Power Analysis): análisis estadístico sobre miles de trazas. Se separan las trazas según una hipótesis sobre un byte de la clave y se busca la diferencia de medias.
  • CPA (Correlation Power Analysis): refina DPA con un modelo de consumo (peso de Hamming o distancia de Hamming) y correlación de Pearson. Es la técnica estándar contra AES por software.

El equipo típico: osciloscopio con frecuencia de muestreo suficiente para el reloj del objetivo, una resistencia shunt en la alimentación del chip y software de análisis.

Electromagnético (EM). Igual que power analysis, pero midiendo con una sonda de campo cercano sobre el chip. No requiere contacto eléctrico y permite localizar zonas concretas del die, a cambio de una señal más ruidosa.

Acústico. Genkin, Shamir y Tromer demostraron en 2013 la extracción de claves RSA de GnuPG escuchando el ruido de alta frecuencia que emiten los componentes de regulación de voltaje del portátil. Es exótico, pero está documentado.

Caché. Es el canal lateral puramente software: no hace falta osciloscopio, solo ejecutar código en la misma máquina física.

  • Flush+Reload: se expulsa de la caché una línea compartida (por ejemplo, de una librería compartida), se espera y se mide si la víctima la volvió a cargar.
  • Prime+Probe: se llena un conjunto de caché con datos propios, se espera y se mide cuántas líneas desplazó la víctima.

Inyección activa de fallos#

Voltage glitching. Se baja el voltaje durante nanosegundos justo en una instrucción crítica (la comprobación de una firma o de un PIN). La CPU ejecuta mal: se salta una rama, lee un valor corrupto o invierte un flag.

Clock glitching. Se inyecta un flanco de reloj extra o se acorta un ciclo para que una instrucción no termine a tiempo. Efecto parecido al glitch de voltaje, a veces más preciso.

Electromagnetic Fault Injection (EMFI). Un pulso EM fuerte induce corrientes en las líneas internas del chip y corrompe datos de forma transitoria.

Laser Fault Injection. Un láser enfocado sobre transistores concretos de un die desencapsulado. Es lo más preciso y lo más caro; se usa en evaluación de tarjetas inteligentes.

Temperatura y cold boot. Halderman y colegas mostraron en 2008 que la DRAM conserva datos segundos o minutos tras cortar la alimentación, más tiempo si se enfría. Reiniciando con un arranque propio se pueden volcar claves de cifrado de disco que estaban en memoria.

Cuándo es explotable#

No todo canal lateral es un riesgo real. Hay que preguntarse tres cosas:

  1. ¿Puede el atacante medir? Timing remoto: cualquiera en la red. Caché: alguien que ejecute código en la misma CPU (VM vecina en la nube, JavaScript en el navegador, otro usuario del servidor). Consumo y fallos: alguien con el dispositivo en la mano.
  2. ¿Puede repetir? DPA necesita miles de ejecuciones con el mismo secreto. Un token de un solo uso es mucho más difícil de atacar que una clave fija de firmware.
  3. ¿Qué gana? Romper la clave raíz de un dispositivo IoT desplegado en millones de unidades justifica un laboratorio caro; un PIN de cuatro dígitos protegido por contador de intentos, no.

Por eso los dispositivos que el usuario final tiene físicamente en su poder (tarjetas, carteras de criptomonedas, consolas, llaves de coche, microcontroladores con bootloader protegido) son los objetivos clásicos de power analysis y glitching, mientras que servidores y nube lo son de timing y caché.

Cómo se detecta#

Detectar una fuga es medir si la magnitud observable cambia cuando cambia el secreto.

PruebaQué respondeHerramienta
Test estadístico de tiempos¿El tiempo depende de la entrada?dudect
Análisis con secretos "no inicializados"¿Hay ramas o accesos indexados por el secreto?ctgrind (Valgrind memcheck)
Análisis estático de tiempo constante¿El binario compilado mantiene tiempo constante?timecop, herramientas de verificación formal
Test TVLA sobre trazas¿El consumo filtra información?ChipWhisperer, SCAlib
Barrido de parámetros de glitch¿Existe una ventana de fallo explotable?ChipWhisperer glitch module

TVLA (Test Vector Leakage Assessment) compara dos grupos de trazas, uno con entrada fija y otro con entrada aleatoria, mediante un test t de Welch. Si el estadístico supera el umbral habitual de 4,5, hay fuga. No dice cómo explotarla, pero sí que existe, y es la prueba estándar en certificaciones.

La idea de ctgrind es ingeniosa: se marca la memoria del secreto como "no inicializada" para Valgrind. Si el programa hace una rama o un acceso a memoria que depende de ese valor, memcheck lo reporta como uso de memoria no inicializada.

Explotación paso a paso en un laboratorio#

Este laboratorio reproduce un ataque de tiempo contra un servicio propio que corre en tu máquina. No hace falta hardware.

Paso 1: levantar el servicio vulnerable#

# servidor_vulnerable.py (solo en tu equipo)
from http.server import BaseHTTPRequestHandler, HTTPServer
import time

SECRETO = b"k3y7"

def check(guess: bytes) -> bool:
    if len(guess) != len(SECRETO):
        return False
    for a, b in zip(guess, SECRETO):
        if a != b:
            return False
        time.sleep(0.002)  # exagera la fuga para que se vea en el lab
    return True

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        guess = self.path.lstrip("/").encode()
        ok = check(guess)
        self.send_response(200 if ok else 403)
        self.end_headers()

HTTPServer(("127.0.0.1", 8088), H).serve_forever()

El sleep amplifica la diferencia para que el ejercicio sea claro. En un caso real la diferencia es de nanosegundos y hay que compensarla con muchas más mediciones y estadística.

Paso 2: medir el tiempo por candidato#

# ataque_timing.py
import string, statistics, time, urllib.request, urllib.error

ALFABETO = string.ascii_lowercase + string.digits
LONGITUD = 4
URL = "http://127.0.0.1:8088/"

def medir(guess: str, n: int = 15) -> float:
    tiempos = []
    for _ in range(n):
        t0 = time.perf_counter()
        try:
            urllib.request.urlopen(URL + guess)
        except urllib.error.HTTPError:
            pass
        tiempos.append(time.perf_counter() - t0)
    return statistics.median(tiempos)

conocido = ""
for pos in range(LONGITUD):
    relleno = "a" * (LONGITUD - pos - 1)
    mejor = max(ALFABETO, key=lambda c: medir(conocido + c + relleno))
    conocido += mejor
    print(f"posición {pos}: {conocido}")

Se usa la mediana y no la media porque el ruido de red y del sistema operativo produce valores atípicos grandes que desplazan la media.

Paso 3: interpretar#

Cada iteración debería revelar un carácter. Si no ocurre, aumenta n. Después repite el experimento quitando el sleep: verás que el ataque necesita muchísimas más muestras, que es justo lo que pasa en la realidad. Por último, cambia check por la versión segura de la sección de corrección y comprueba que la diferencia desaparece.

Paso 4: el salto a hardware (opcional)#

Con un ChipWhisperer Nano o Lite y su objetivo incluido, los notebooks oficiales guían el mismo proceso sobre un microcontrolador real: capturar trazas de consumo mientras cifra con AES, aplicar CPA para recuperar la clave byte a byte y, en otro tutorial, hacer glitching de reloj para saltarse la comprobación de una contraseña.

Qué puede lograr un atacante#

EscenarioCanalImpacto
API compara tokens con salida tempranaTiming remotoRecuperar tokens o firmas HMAC
Microcontrolador cifra con AES por softwareCPAExtraer la clave de firmware o de licencias
Bootloader verifica firma con una sola ramaGlitchingArrancar firmware no firmado
VM vecina en la misma CPU físicaCaché / SpectreLeer claves de otro inquilino
Portátil suspendido con disco cifradoCold bootVolcar la clave de cifrado de la RAM

Spectre y Meltdown#

Spectre y Meltdown: la ejecución especulativa como canal lateral
Spectre y Meltdown: la ejecución especulativa como canal lateral

En 2018 se publicó que CPUs modernas de Intel, AMD y ARM filtran datos a través de la ejecución especulativa: el procesador adelanta instrucciones antes de saber si debe ejecutarlas, y aunque descarte el resultado, los datos dejan huella en la caché, que luego se lee con Flush+Reload.

Meltdown (CVE-2017-5754). Un proceso de usuario lee memoria del kernel porque la CPU ejecuta especulativamente la carga antes de comprobar permisos. Mitigación: KPTI (Kernel Page Table Isolation), que quita el mapeo del kernel del espacio de usuario, con coste de rendimiento variable según la carga de trabajo.

Spectre v1 (CVE-2017-5753, bounds check bypass). La CPU especula que una comprobación de límites pasará y accede fuera del array. Mitigación: barreras como lfence y enmascarado de índices en el compilador.

Spectre v2 (CVE-2017-5715, branch target injection). El atacante entrena el predictor de saltos para desviar la especulación a un gadget que filtra datos. Mitigación: retpolines, IBRS, STIBP.

Variantes posteriores: L1TF (Foreshadow), MDS (RIDL, Fallout, ZombieLoad), TAA, LVI, Spectre-BHB, Retbleed, Inception y Downfall (GDS), entre otras. Cada una explota otra estructura de la microarquitectura.

El impacto de fondo: el aislamiento entre procesos, contenedores y máquinas virtuales ya no puede asumir que el hardware compartido es opaco. La industria respondió con microcódigo, cambios en compiladores y kernels, y nuevas generaciones de CPU, pero la clase de problema sigue abierta.

Cómo se corrige#

La regla de oro: nada que dependa del secreto puede decidir una rama, un índice de memoria o el número de iteraciones.

Comparación en tiempo constante. En vez de salir al primer fallo, se acumulan todas las diferencias:

# SEGURO: usa la primitiva de la librería estándar
import hmac

def check_token(recibido: bytes, esperado: bytes) -> bool:
    return hmac.compare_digest(recibido, esperado)
// SEGURO: recorre siempre todos los bytes y no ramifica sobre el secreto
int ct_equal(const unsigned char *a, const unsigned char *b, size_t n) {
    unsigned char diff = 0;
    for (size_t i = 0; i < n; i++)
        diff |= a[i] ^ b[i];
    return diff == 0;
}

En PHP la función equivalente es hash_equals, en Node.js crypto.timingSafeEqual y en Go subtle.ConstantTimeCompare. Usa siempre la de tu plataforma antes que escribir la tuya.

Exponenciación en tiempo constante. La escalera de Montgomery ejecuta la misma secuencia de operaciones para bits 0 y 1, y elige operandos con selección condicional sin ramas.

Masking. El valor secreto se divide en partes aleatorias que se procesan por separado; el consumo de cada parte es aleatorio y no se correlaciona con el secreto.

Blinding. En RSA se multiplica el mensaje por un valor aleatorio antes de la operación y se deshace después, de modo que cada ejecución procesa datos distintos y DPA no puede acumular estadística.

Contra glitching. Comprobaciones redundantes (verificar dos veces la firma con lógica invertida), contadores de flujo de control, valores de "éxito" con mucha distancia de Hamming en vez de 0 y 1, retardos aleatorios y sensores de voltaje y reloj que reinician el chip.

Hardware dedicado. Secure elements, TPM y HSM se diseñan y certifican específicamente contra estos ataques. Si el producto maneja claves valiosas, guárdalas ahí y no en el microcontrolador general.

Contra ataques de caché y especulativos. Mantener al día microcódigo y kernel, no desactivar mitigaciones por rendimiento sin análisis, aislar cargas sensibles en núcleos o hosts dedicados y usar implementaciones criptográficas sin tablas indexadas por la clave (AES-NI en vez de T-tables).

Errores comunes al defenderse#

  • Escribir una comparación "constante" a mano y dejar que el compilador la optimice de vuelta a una salida temprana. Verifica el binario, no solo el código fuente.
  • Comparar la longitud primero y salir: filtra la longitud del secreto. Compara hashes de longitud fija si la longitud es sensible.
  • Creer que el ruido de red protege. El ruido solo obliga a medir más veces.
  • Añadir un sleep aleatorio como única defensa: se elimina promediando.
  • Proteger el cifrado y olvidar la comprobación de PIN o de firma del bootloader, que es justo donde apunta el glitching.
  • Desactivar mitigaciones de Spectre en servidores multiinquilino para ganar rendimiento.

Preguntas frecuentes#

¿Necesito hardware caro para empezar?#

No. Los ataques de tiempo y de caché se practican solo con software. Para power analysis y glitching, las placas de entrada de ChipWhisperer traen un objetivo incluido y tutoriales completos.

¿Los canales laterales afectan a la nube?#

Sí. La familia Spectre y los ataques de caché funcionan entre máquinas virtuales que comparten CPU física. Los proveedores aplican mitigaciones y aislamiento, pero el riesgo residual existe y por eso ofrecen hosts dedicados.

¿Es difícil programar en tiempo constante?#

Sí. El compilador puede reintroducir ramas, y la CPU puede tener instrucciones con latencia variable. Usa primitivas de librerías criptográficas auditadas y verifica con herramientas como dudect o ctgrind.

¿Un ataque de tiempo remoto es realista?#

Lo es cuando la diferencia es suficientemente grande o el atacante puede hacer muchas peticiones. Por eso todas las comparaciones de tokens, firmas HMAC y contraseñas deben ser en tiempo constante aunque parezca exagerado.

¿Dónde practico?#

Empieza con el laboratorio de esta lección en tu equipo. El lab orion-recon de whoami-labs entrena el reconocimiento previo. Para hardware real, sigue los tutoriales oficiales de ChipWhisperer.

Resumen#

  • Canal lateral: información que se escapa por la implementación física, no por el diseño lógico.
  • Causa raíz: ramas, índices o número de iteraciones que dependen del secreto.
  • Pasivos: timing, consumo (SPA, DPA, CPA), EM, acústico y caché. Activos: glitching de voltaje y reloj, EMFI, láser y cold boot.
  • Detección: dudect, ctgrind y TVLA antes de que lo haga un atacante.
  • Corrección: comparaciones y aritmética en tiempo constante, masking, blinding, redundancia contra glitching y hardware seguro.
  • Spectre y Meltdown: la ejecución especulativa rompió el supuesto de aislamiento entre procesos y VMs.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría