Seguridad

41 minutos, 1.082 bitcoins y un condicional mal escrito

Vaciaron 1.196 monederos de bitcoin sin tocar ningún dispositivo: las llaves se podían adivinar. La causa fue una comprobación que preguntaba si una opción existía en vez de si estaba activada, y un fallback silencioso que llevaba cinco años funcionando en un repositorio público.

La semana pasada, en 41 minutos, alguien vació 1.196 monederos de bitcoin.

No hubo phishing, ni malware, ni nadie tocó ningún dispositivo. Los aparatos vaciados eran monederos fríos: cacharros sin conexión, de código abierto, que se recomiendan precisamente porque la llave privada no sale nunca de ahí.

Y no salió. No hacía falta: se podía adivinar.

Voy a contar esto despacio, porque no va de criptomonedas. Va de una línea de código que hacía la pregunta equivocada, y de un fallo que estuvo cinco años a la vista de todo el mundo en un repositorio público.

Varios dados de seis caras sobre una superficie clara

Dados: el generador de aleatoriedad que sí se puede auditar mirándolo. Foto en dominio público, vía Wikimedia Commons.

Qué se supone que hace un monedero frío

Un monedero de bitcoin no guarda monedas: guarda una llave. Y esa llave se deriva de una semilla, un número aleatorio que el dispositivo genera una sola vez, la primera vez que lo enciendes. De ahí salen las doce o veinticuatro palabras que te hacen apuntar en un papel.

El estándar del sector fija esa semilla en 128 bits de entropía: hay 2¹²⁸ semillas posibles, un número de treinta y nueve dígitos. Probarlas todas no es caro ni lento, es físicamente imposible con la energía disponible en el planeta. Ese es el trato completo: toda la seguridad del sistema cuelga de que ese número sea genuinamente impredecible.

Lo demás —que el aparato esté desconectado, que haya un elemento seguro— sirve para que nadie te robe la semilla, y no sirve de nada si se puede recalcular.

El commit de marzo de 2021

En marzo de 2021, un único commit cambió cómo el firmware pedía números aleatorios: de una llamada que iba directa al generador de hardware del microcontrolador —dedicado y físico— a una llamada equivalente de una biblioteca nueva.

Sobre el papel, lo mismo. En la práctica, la llamada nueva nunca llegaba al hardware.

Y aquí está la parte que me interesa de verdad.

El bug, traducido a tu lenguaje

La biblioteca decidía si usar el generador de hardware o un sustituto por software mirando una macro de compilación. La comprobación era, en esencia, «si esta macro no está definida, usa el sustituto».

El firmware del dispositivo sí la definía. La definía con el valor cero, que es la forma canónica de decir «no uses el generador de MicroPython, que ya tenemos el nuestro».

La comprobación preguntaba si la opción existía. No preguntaba si estaba activada.

Si esto te suena raro por venir de C, mira cómo se escribe el mismo error en PHP:

// Lo que se quiso decir
if ($config['usar_hsm']) { ... }

// Lo que se escribió
if (isset($config['usar_hsm'])) { ... }   // cierto aunque valga false

Dos palabras de diferencia. Y más de mil bitcoins.

Lo que acabó ejecutándose fue el generador por software de MicroPython, inicializado con tres cosas que parecen aleatorias y no lo son:

  • El identificador único del chip: un valor fijo, distinto para cada aparato pero constante para toda su vida.
  • El contador de arranque del procesador: entre ochenta mil y ciento veinte mil valores posibles.
  • Unos registros de reloj correlacionados entre sí.

Y, después del arranque, no volvía a recoger entropía nunca más.

Cuántos bits quedaban de verdad

Aparato Entropía esperada Entropía real
Estándar del sector 128 bits
Modelos Mk2 y Mk3 128 bits ~40 bits
Modelos Mk4, Mk5 y Q 128 bits 72 bits según el fabricante; 32 según el análisis independiente

Los 32 bits son el dato que hiela la sangre. Los modelos nuevos sí volvían a sembrar el generador con el elemento seguro, pero de todo ese material solo cuatro bytes llegaban a la función que lo aplicaba: 2³², es decir 4.294 millones de posibilidades.

Cuatro mil millones suena a mucho. Para una máquina moderna es un bucle.

Y el que enumeró ese bucle lo hizo un jueves por la noche, en seis bloques consecutivos, entre la 01:10 y la 01:51 UTC: 1.082,65 bitcoins de 1.196 direcciones. Los análisis posteriores elevan el total, con oleadas adicionales, a unos 1.367 bitcoins en 4.585 direcciones. Las víctimas no eran ballenas: más de ochocientas tenían menos de un bitcoin.

Conviene además separar tres cosas que aquí se dieron seguidas y que en otros casos no van juntas: que exista un fallo aprovechable, que alguien demuestre que se puede explotar, y que alguien lo explote de verdad contra terceros. Esto último es lo que ocurrió aquella noche.

Por qué nadie lo vio en cinco años

Esta es la pregunta que hay que hacerse, y tiene cuatro respuestas. Ninguna es «eran malos programadores».

1. La mala aleatoriedad no se ve. Una semilla de 32 bits de entropía y otra de 256 son, mirándolas, dos cadenas igual de feas: no hay inspección visual ni revisor humano que las distinga.

2. Los tests estadísticos tampoco. Un generador pseudoaleatorio decente pasa las pruebas de distribución con nota, porque lo que falla no es la forma de los números, sino que el conjunto del que salen es pequeño. Hay propiedades que no se pueden comprobar mirando lo que un sistema produce, y la aleatoriedad es la más traicionera.

3. El código abierto no es código auditado. Ese firmware llevaba cinco años en un repositorio público, en un proyecto con reputación y en un sector que se toma la seguridad muy en serio, y el fabricante ha reconocido que nadie lo había revisado a fondo. «Muchos ojos» es una hipótesis, no un mecanismo: los ojos hay que ponerlos y pagarlos. Es lo que ya conté con los mods y los plugins, un piso más abajo.

4. Y el más importante: el fallo fue silencioso. Si la llamada hubiera reventado al no encontrar el generador de hardware, esto se habría detectado en el primer arranque de marzo de 2021, en la mesa de alguien. En vez de eso degradó educadamente a un sustituto y siguió funcionando.

Un fallback que funciona en silencio no es tolerancia a fallos: es un fallo aplazado, con intereses.

Los caminos de degradación tienen que gritar: dejar registro, encender una alarma y, cuando la garantía que sostienen es lo único que justifica el producto, negarse a arrancar. Un aparato cuya razón de ser es generar un número impredecible debería negarse a encenderse si no llega al chip que lo genera.

Lo que un parche no puede arreglar

El fabricante publicó firmware corregido para todos los modelos el mismo día, y hay que reconocérselo: fue rápido. Pero conviene entender qué arregla y qué no.

El parche impide que se generen semillas débiles nuevas. No arregla las que ya existen.

Aquí hay una distinción que se olvida constantemente:

  • Un fallo de disponibilidad se arregla desplegando. Actualizas, se acabó.
  • Un fallo de secreto no se arregla desplegando. Un secreto que se pudo calcular ya está calculado, o lo estará. Un secreto filtrado no se des-filtra.

Por eso a un afectado no se le dice «actualiza», sino: actualiza el firmware, genera una semilla completamente nueva y mueve los fondos a esa, con una transacción de prueba primero. Y por eso, cuando una incidencia toca credenciales, la pregunta no es «¿ya está parcheado?», sino «¿qué secretos hay que rotar y cuánto tardamos?». Sin ese procedimiento cronometrado no tienes un plan de incidentes: tienes un plan de despliegue.

Y otra cosa: no busques la cifra exacta

Según dónde lo leas, el robo fue de sesenta, de setenta, de ochenta y ocho o de ciento diez millones de dólares. Ninguna cifra es falsa: cambian según la cotización del momento en que se hizo la conversión, según si se cuenta solo la primera oleada o todas, y según qué análisis se sigue. Por eso aquí las cifras van en bitcoins, que es la unidad que no se mueve.

En las primeras horas de un incidente, el número redondo es lo menos fiable que vas a leer. Lo fiable es el mecanismo.

Qué hacer esta semana (aunque no tengas un bitcoin)

  1. Busca tus fallbacks silenciosos. Cualquier sitio donde el código diga «si no está disponible X, usa Y». Si la respuesta a «¿y si Y se usa siempre?» es «funciona igual pero peor y no nos enteramos», ya tienes un ticket.
  2. Revisa tus isset(), tus ?? y tus if (defined(...)). Comprobar que una opción existe no es comprobar su valor. Es el bug de esta historia.
  3. Y la incómoda: elige una garantía de seguridad de las que das por hechas —«las contraseñas van cifradas», «los tokens caducan», «los identificadores no son adivinables»— y compruébala hoy contra el código, no contra la documentación. La documentación describe la intención; los 1.082 bitcoins se fueron por la diferencia entre la intención y la línea.

Porque lo que se rompió aquí no fue la criptografía: hizo su trabajo derivando llaves impecables a partir de una semilla que un becario podría haber adivinado. Lo que se rompió fue el pegamento: la línea aburrida que conecta la parte buena con el resto del sistema.

Ahí es donde está siempre.

(Y sí: el chip generador de números aleatorios por hardware funcionaba perfectamente. Estuvo cinco años encendido, esperando una llamada que no llegó nunca. Me lo imagino como el compañero del turno de noche al que nadie avisó de que habían cambiado la extensión del teléfono.)


Los detalles técnicos de este artículo están contrastados con el análisis del propio fabricante y con el análisis independiente de Block, que es el que sitúa la resiembra real en 32 bits.

Preguntas frecuentes

¿Qué falló exactamente en el firmware del monedero?

Un cambio introducido en marzo de 2021 sustituyó la llamada que accedía al generador de números aleatorios por hardware del microcontrolador por otra procedente de una biblioteca distinta. Esa biblioteca decidía si usar el hardware o un sustituto por software comprobando si una macro de compilación estaba definida, en lugar de comprobar su valor. Como el firmware la definía con valor cero para indicar que no debía usarse el sustituto, la comprobación resultó cierta y el sistema quedó vinculado a un generador pseudoaleatorio por software.

¿Cuánta entropía tenían realmente las semillas afectadas?

El estándar del sector fija 128 bits de entropía para una semilla de doce palabras. En los modelos más antiguos la entropía efectiva quedó en torno a 40 bits. En los modelos posteriores, que incorporan una resiembra basada en el elemento seguro, el fabricante estimó unos 72 bits, mientras que un análisis independiente determinó que solo cuatro bytes de ese material llegaban a la función de resiembra, lo que limita el espacio real a 32 bits, es decir, unos 4.294 millones de combinaciones.

¿Por qué no se detectó el problema durante cinco años pese a ser código abierto?

Porque la salida de un generador pseudoaleatorio defectuoso es indistinguible de la de uno correcto mediante inspección visual y supera sin dificultad las pruebas estadísticas habituales de distribución. El defecto no está en la forma de los números sino en el tamaño del conjunto del que proceden, algo que solo se aprecia revisando el proceso de generación. A ello se sumó que el código degradaba de forma silenciosa, sin error ni registro, y que la disponibilidad pública del código no equivale a que haya sido auditado.

¿Una actualización de firmware resuelve el problema para los afectados?

No por sí sola. La actualización impide que se generen semillas débiles a partir de ese momento, pero no modifica las semillas ya creadas, que siguen siendo calculables. El procedimiento recomendado consiste en actualizar el firmware, generar una semilla completamente nueva en el dispositivo actualizado y transferir los fondos a las direcciones derivadas de esa semilla, comprobando previamente el resultado con una transacción de prueba.

¿Qué lección aplica a un proyecto de software convencional?

Que los caminos de degradación deben ser explícitos y ruidosos. Un mecanismo alternativo que se activa sin dejar registro convierte un fallo detectable en un fallo latente que puede permanecer en producción durante años. Asimismo, conviene distinguir entre incidencias de disponibilidad, que se resuelven con un despliegue, e incidencias que comprometen secretos, que exigen un procedimiento de rotación definido y cronometrado, dado que un secreto expuesto no deja de estarlo al aplicar una corrección.

Otras noticias

Destacado Seguridad

El camaleón, el PGSharp y la clave del wifi

Compré el juego viral del momento para jugar con mis hijos. Hoy he leído que dos mapas del Steam Workshop instalaban malware y que a los afectados les recomiendan formatear. Crónica del padre pesado que no da la clave del wifi, dice que no a las APK y hoy, por una vez, tenía razón.

Leer más →

El blog, día a día

32 artículos publicados 7 feb 2026 — 6 feb 2027

¿Te ha resultado útil o quieres comentar algo?

Hablemos →