Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Análisis a fondo

Análisis a fondo

bitcoinjs-lib acepta bloques falsificados (CVE-2026-107846)

bitcoinjs-lib hasta 7.0.1 acepta una lista de transacciones falsificada bajo un identificador de bloque real; corregido en 5.2.1, 6.1.8 y 7.0.2.

Resumen

  • bitcoinjs-lib valida un bloque falsificado cuya lista de transacciones difiere del bloque real. Un par duplica la última transacción para rellenar el nivel impar del árbol de Merkle, y checkTxRoots() sigue devolviendo verdadero.
  • Las versiones 2.3.0 a 5.2.0, 6.0.0 a 6.1.7 y 7.0.0-rc.0 a 7.0.1 están afectadas. La falla está en la validación de bloques, no en la firma de transacciones.
  • El registro CVE la califica CVSS v4.0 9.3 Crítica, mientras que el aviso del repositorio la califica alta. Hay una prueba de concepto pública en el aviso; no se conoce explotación y no está en CISA KEV al 2026-11-09.
  • Actualice a 5.2.1, 6.1.8 o 7.0.2. Si valida bloques no confiables, rechace también cualquier bloque que repita un identificador de transacción.

¿Estoy afectado?

Todas estas condiciones deben cumplirse:

  • Usa bitcoinjs-lib en versión 2.3.0 a 5.2.0, 6.0.0 a 6.1.7, o 7.0.0-rc.0 a 7.0.1.
  • Analiza bloques de una fuente no confiable y actúa sobre el resultado de Block.checkTxRoots(), Block.calculateMerkleRoot() o el identificador del bloque.
  • Una respuesta incorrecta tiene una consecuencia en su sistema, como acreditar un depósito, indexar dos veces una transacción o aceptar un bloque que otro nodo rechaza.

El código afectado es el validador de bloques que la biblioteca añadió en 2.3.0 como Block.checkMerkleRoot(), después expuesto como Block.checkTxRoots(). El código que solo construye o firma transacciones no lo llama. Una cartera que nunca analiza un bloque de un par no está expuesta por esta vía.

Rama Versiones afectadas Versión corregida
7.x 7.0.0-rc.0 a 7.0.1 7.0.2
6.x 6.0.0 a 6.1.7, incluida 6.1.0-rc.0 6.1.8
5.x y anteriores 2.3.0 a 5.2.0 5.2.1
Antes de 2.3.0 Sin verificador de raíz de bloque No afectada

El repositorio publicó otros cinco avisos el 2026-10-26: una subestimación de comisiones PSBT, dos problemas de denegación de servicio al analizar, la aceptación de un objetivo compacto inválido y un error de comisiones por UTXO en conflicto. Son fallas distintas con sus propias versiones corregidas, posteriores a las de este CVE: 5.2.3, 6.1.10 y 7.0.5, que npm lista como versiones de mantenimiento y última versión actuales.

Compruebe qué está instalado:

npm ls bitcoinjs-lib
node -p "require('bitcoinjs-lib/package.json').version"

grep -n '"node_modules/bitcoinjs-lib"' -A 4 package-lock.json
grep -n "bitcoinjs-lib" yarn.lock pnpm-lock.yaml

Después busque las llamadas que importan:

grep -rn "checkTxRoots\|calculateMerkleRoot\|checkMerkleRoot" --include="*.js" --include="*.ts" .

¿Cómo lo corrijo?

Actualice a la versión corregida de su rama: 5.2.1, 6.1.8 o 7.0.2. Las tres se publicaron el 2026-09-07, siete semanas antes de la publicación del aviso.

  1. Para la rama 7.x:
npm install bitcoinjs-lib@7.0.2
  1. Para la rama 6.x:
npm install bitcoinjs-lib@6.1.8
  1. Para la rama 5.x:
npm install bitcoinjs-lib@5.2.1
  1. Las versiones anteriores a 5.0.0 no tienen corrección integrada. Pase a una rama corregida, o añada la comprobación de la aplicación de esta sección antes de confiar en un bloque.

Si no puede actualizar de inmediato, rechace cualquier bloque cuya lista de transacciones repita un identificador de transacción antes de llamar a checkTxRoots(). Un identificador repetido es la forma observable de la mutación, y es más fácil de auditar que la condición interna de relleno.

// Application-level guard, adapted from the advisory's remediation note.
function hasRepeatedTxid(block) {
  const ids = block.transactions.map(tx => tx.getId());
  return new Set(ids).size !== ids.length;
}

if (!hasRepeatedTxid(block) && block.checkTxRoots()) {
  // accept the block
}

Esa comprobación cubre la forma de la transacción duplicada. No convierte la biblioteca en un validador completo de bloques y no sustituye la actualización.

¿Cuál es la causa raíz?

La rutina de raíz de Merkle rellena un nivel impar hasheando el último nodo consigo mismo y devuelve la raíz sin informar de ese relleno. La construcción del árbol de Merkle de Bitcoin hace el mismo relleno. Bitcoin Core también recorre cada nivel en busca de dos hashes adyacentes iguales y activa un indicador mutated, que CheckBlock convierte en un rechazo bad-txns-duplicate. bitcoinjs-lib hacía el relleno y descartaba la señal.

En ts_src/merkle.ts en la versión v7.0.1, fastMerkleRoot duplica el último nodo cuando un nivel tiene longitud impar:

let length = values.length;
const results = values.concat();

while (length > 1) {
  let j = 0;

  for (let i = 0; i < length; i += 2, ++j) {
    const left = results[i];
    const right = i + 1 === length ? left : results[i + 1];
    const data = tools.concat([left, right]);

    results[j] = digestFn(data);
  }

  length = j;
}

return results[0];

Cuando i + 1 === length, right toma el valor de left, así que el último nodo se hashea consigo mismo. No hay comparación con el nodo anterior ni parámetro de salida para informar de un duplicado. La función solo devuelve la raíz.

Block.calculateMerkleRoot pasa esa raíz sin cambios, y Block.__checkMerkleRoot la compara solo con el campo de la cabecera:

private __checkMerkleRoot(): boolean {
  if (!this.transactions) throw errorMerkleNoTxes;

  const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
  return tools.compare(this.merkleRoot!, actualMerkleRoot) === 0;
}

checkTxRoots() añade la comprobación del witness commit y después llama a __checkMerkleRoot(). Una cabecera que compromete la raíz real pasa por tanto, incluso cuando la lista de transacciones lleva una copia extra de la última transacción absorbida por el relleno. El atacante no necesita minar: la cabecera, la prueba de trabajo y el identificador del bloque siguen iguales.

Un identificador de bloque solo hashea la cabecera de 80 bytes, y la cabecera autentica la lista de transacciones solo mediante la raíz de Merkle. Cualquier cambio en la lista que preserve la raíz preserva por tanto el identificador y todas las búsquedas que dependen de él.

La misma clase de duplicación es CVE-2012-2459, la mutación que Bitcoin Core corrigió en 2012. El aviso indica que Core rechaza el caso mediante ComputeMerkleRoot y CheckBlock. Es una diferencia de paridad en la biblioteca, no una falla nueva de construcción en Bitcoin.

¿Cómo se explota?

El ataque toma un bloque existente y cambia su lista de transacciones sin cambiar la cabecera:

  1. El atacante toma un bloque real de la cadena, con cabecera válida y prueba de trabajo, cuyo árbol de Merkle tiene un número impar de nodos en algún nivel.
  2. El atacante analiza el bloque, añade una copia de la última transacción a la lista y lo serializa de nuevo. Eso añade 259 bytes en el ejemplo de la red principal del aviso y deja la cabecera intacta.
  3. El atacante retransmite el bloque mutado a un servicio que analiza bloques con bitcoinjs-lib y se apoya en checkTxRoots().
  4. El servicio recalcula la raíz de Merkle. La transacción duplicada rellena el nivel impar, así que la raíz y el identificador del bloque coinciden con la cabecera, y checkTxRoots() devuelve verdadero.
  5. El servicio ve ahora un bloque con una lista de transacciones distinta bajo un identificador que también pertenece al bloque honesto. Un indexador puede acreditar dos veces la transacción duplicada, o un sistema de depósitos puede contarla dos veces.

El aviso señala que la mayoría de los bloques reales alcanzan un número impar de nodos en algún nivel, así que la mayoría de los bloques existentes pueden reescribirse.

Su prueba de concepto se ejecuta contra un bloque real de la red principal, del conjunto de fixtures de Bitcoin Core del repositorio. El bloque honesto tiene nueve transacciones y valida; la copia falsificada tiene diez, 259 bytes más y el mismo identificador de bloque, y también valida. El aviso extiende además el mismo bloque a doce transacciones con el mismo resultado.

// Adapted from the advisory's proof of concept, trimmed to the mutation.
const honest = Block.fromHex(fixture.hex);
const forged = Block.fromHex(fixture.hex);

forged.transactions = forged.transactions.concat([
  forged.transactions[forged.transactions.length - 1],
]);

const parsed = Block.fromHex(forged.toHex());

honest.getId() === parsed.getId();        // true
parsed.checkTxRoots();                    // true on v7.0.1

¿Cómo se desarrolla el ataque?

Un par reescribe un bloque existente duplicando su última transacción. El cálculo de raíz de la biblioteca acepta la lista reescrita porque nunca informa del relleno que hizo invisible el duplicado.

flowchart LR
    A["Un par retransmite un bloque mutado"] --> B["Block.fromHex analiza transacciones duplicadas"]
    B --> C["fastMerkleRoot rellena el nivel impar"]
    C --> D["sin comprobación de pares duplicados"]
    D --> E["checkTxRoots devuelve verdadero"]
    E --> F["El indexador acredita la transacción duplicada"]

Cronología de CVE-2026-107846: la corrección se publicó el 7 de septiembre, el CVE se reservó el 8 de octubre, el aviso se publicó el 26 de octubre y el registro CVE el 4 de noviembre
Figura 1: La corrección se publicó siete semanas antes del aviso y 58 días antes del registro CVE. Fuente: el aviso del repositorio, las fechas de publicación npm y el registro CVE, comprobados el 9 de noviembre de 2026.

¿Qué puede hacer un atacante?

El impacto está en el consumidor, no en la biblioteca. Con un bloque falsificado aceptado, un atacante puede:

  • Hacer que un servicio que acredita transacciones las acredite dos veces, cuando usa checkTxRoots() como control de integridad de bloques.
  • Hacer que un explorador o indexador guarde una lista de transacciones que no coincide con la cadena, bajo un identificador de bloque que sí coincide.
  • Provocar la denegación de servicio descrita en CVE-2012-2459: un consumidor que reacciona bloqueando el identificador del bloque también rechaza el bloque honesto con ese identificador.

El ataque necesita un consumidor que analice bloques no confiables y actúe sobre checkTxRoots(). No recupera claves privadas, no firma transacciones y no permite al atacante gastar directamente monedas ajenas. El aviso es explícito en que la gravedad depende del consumidor, porque la validación de bloques es una parte menor del uso de esta biblioteca que el tratamiento de transacciones.

Un servicio que solo firma o difunde transacciones no es el objetivo. La exposición está en la ruta de lectura: indexadores, casas de cambio, exploradores y cualquier canal de depósitos que acepte un bloque porque la biblioteca dijo que su raíz de transacciones coincidía.

¿Por qué funciona la corrección?

La corrección informa del relleno fuera del cálculo de la raíz y hace fallar la validación del bloque cuando encuentra un par real de duplicados. El commit b79c7714c0687473dc6e25c69fc5fc597302c7a0 añade un parámetro de salida mutated a fastMerkleRoot y lo comprueba en __checkMerkleRoot.

 export function fastMerkleRoot(
   values: Uint8Array[],
   digestFn: (b: Uint8Array) => Uint8Array,
+  mutated?: { value: boolean },
 ): Uint8Array {
   ...
   let length = values.length;
   const results = values.concat();
+  if (mutated) mutated.value = false;

   while (length > 1) {
     let j = 0;

     for (let i = 0; i < length; i += 2, ++j) {
       const left = results[i];
       const right = i + 1 === length ? left : results[i + 1];
+      // Odd-node padding is not a mutation; only compare pairs from this level.
+      if (mutated && i + 1 < length && tools.compare(left, right) === 0)
+        mutated.value = true;
       const data = tools.concat([left, right]);
       results[j] = digestFn(data);
     }

La condición i + 1 < length es la parte importante. Compara dos hashes que son realmente hermanos en este nivel, y excluye el par sintético creado por el relleno del nivel impar. Un solo valor no se marca, y dos hashes iguales en pares distintos tampoco, lo que cubren las nuevas pruebas del commit.

__checkMerkleRoot devuelve después falso cuando el indicador está activo:

   private __checkMerkleRoot(): boolean {
     if (!this.transactions) throw errorMerkleNoTxes;

-    const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
-    return tools.compare(this.merkleRoot!, actualMerkleRoot) === 0;
+    const mutated = { value: false };
+    const actualMerkleRoot = Block.calculateMerkleRoot(
+      this.transactions,
+      false,
+      mutated,
+    );
+    return (
+      !mutated.value && tools.compare(this.merkleRoot!, actualMerkleRoot) === 0
+    );
   }

La corrección existe en las tres ramas corregidas: el commit b79c7714c0687473dc6e25c69fc5fc597302c7a0 para la 7.x, 009f165edfbf925cad8964b2dce478d18dd19449 para la 6.x, y f3434d1e4bb5e0784fe769c4cd08a243576232a1 para la 5.2.1, que además sustituye la dependencia externa merkle-lib por una implementación local. La corrección es completa para la ruta de raíz de bloque de la biblioteca. No añade una comprobación de transacciones repetidas al código de Merkle de cada consumidor, por eso la comprobación de la aplicación sigue siendo útil.

¿Cómo compruebo si estuve comprometido?

Aquí no hay una compromisión de host que buscar: el ataque cambia qué bloques acepta un consumidor, así que las pruebas están en los registros de bloques y depósitos. Busque bloques cuya lista de transacciones guardada no coincida con una segunda fuente, y identificadores de transacción repetidos dentro de un mismo bloque.

  • Compare los bloques guardados con un segundo nodo de Bitcoin o un explorador para la misma altura e identificador de bloque.
  • Busque un bloque cuya lista de transacciones contenga un txid duplicado.
  • Audite los créditos y depósitos basados en un bloque validado por la biblioteca antigua.
  • Vuelva a ejecutar checkTxRoots() con una versión corregida sobre los bloques guardados y anote cualquier bloque que ahora rechace.
# List transaction IDs in a stored block and find duplicates with jq.
jq -r '.tx[] | .txid' stored-block.json | sort | uniq -d

# Compare the block ID your service recorded with a second node.
bitcoin-cli getblockhash <height>
bitcoin-cli getblock <blockhash> 2

Un bloque que falla la comparación con la segunda fuente es un elemento que investigar. Un bloque que coincide no demuestra que su consumidor lo tratara correctamente, porque la falla está en la ruta de validación del consumidor, no en la cadena.

Cronología

  • 2026-09-07: las correcciones se publican en 5.2.1, 6.1.8 y 7.0.2.
  • 2026-10-08: se reserva el CVE.
  • 2026-10-26: el repositorio publica seis avisos, incluido GHSA-8vv3-6qpc-gpw6.
  • 2026-11-04: se publica el registro CVE de CVE-2026-107846.

Preguntas frecuentes

¿Puede esta falla robar bitcoins de una cartera?

No por sí sola. No expone claves ni crea firmas válidas. El riesgo de robo es indirecto: un servicio que acredita depósitos desde un bloque validado con la biblioteca antigua puede ser engañado para acreditar dos veces la misma transacción.

¿Bitcoin Core acepta el bloque falsificado?

No. El aviso indica que Bitcoin Core detecta la mutación en ComputeMerkleRoot y rechaza el bloque con bad-txns-duplicate. La falla es una diferencia entre el validador de bloques de bitcoinjs-lib y el comportamiento de Core.

¿Debo actualizar si solo creo transacciones?

No por esta falla, salvo que su código también analice bloques y llame a checkTxRoots(). La construcción y firma de transacciones no pasan por la ruta de raíz de bloque afectada.

¿Por qué el bloque falsificado conserva el mismo identificador?

El identificador del bloque es el hash de la cabecera, y la cabecera no cambia. La transacción duplicada solo cambia la lista de transacciones y el tamaño serializado del bloque, que quedan fuera del hash de la cabecera.

¿Hay una solución alternativa si no puedo actualizar?

Sí, para la forma de la transacción duplicada. Rechace cualquier bloque que repita un identificador de transacción antes de confiar en checkTxRoots(). La comprobación aparece en "¿Cómo lo corrijo?" y debe tratarse como temporal hasta la actualización.

Encuentre copias obsoletas de bitcoinjs-lib con Ostorlab

El análisis de composición de software y el inventario SBOM de Ostorlab registran las versiones de bibliotecas como bitcoinjs-lib en sus repositorios e imágenes de contenedores. Eso le permite encontrar los servicios que aún resuelven una versión anterior a 5.2.1, 6.1.8 o 7.0.2.

Referencias

  • Aviso del repositorio GHSA-8vv3-6qpc-gpw6, "Undetected merkle node duplication in block root computation validates a forged transaction list" (2026-10-26): https://github.com/bitcoinjs/bitcoinjs-lib/security/advisories/GHSA-8vv3-6qpc-gpw6 (comprobado el 9 de noviembre de 2026)
  • Registro CVE CVE-2026-107846 (2026-11-04): https://www.cve.org/CVERecord?id=CVE-2026-107846 (comprobado el 9 de noviembre de 2026)
  • Commit de corrección b79c7714c0687473dc6e25c69fc5fc597302c7a0 (2026-09-03): https://github.com/bitcoinjs/bitcoinjs-lib/commit/b79c7714c0687473dc6e25c69fc5fc597302c7a0 (comprobado el 9 de noviembre de 2026)
  • Commit de corrección 009f165edfbf925cad8964b2dce478d18dd19449 para la rama 6.x (2026-09-03): https://github.com/bitcoinjs/bitcoinjs-lib/commit/009f165edfbf925cad8964b2dce478d18dd19449 (comprobado el 9 de noviembre de 2026)
  • Commit de corrección f3434d1e4bb5e0784fe769c4cd08a243576232a1 para la 5.2.1 (2026-09-07): https://github.com/bitcoinjs/bitcoinjs-lib/commit/f3434d1e4bb5e0784fe769c4cd08a243576232a1 (comprobado el 9 de noviembre de 2026)
  • CVE-2012-2459, la clase de mutación del árbol de Merkle (comprobado el 9 de noviembre de 2026): https://www.cve.org/CVERecord?id=CVE-2012-2459
  • Recuento de descargas npm de bitcoinjs-lib, del 2 al 8 de noviembre de 2026: https://api.npmjs.org/downloads/point/last-week/bitcoinjs-lib (comprobado el 9 de noviembre de 2026)