
Problema con tough, versiones anteriores a 0.20.0 (Múltiples CVEs)
En marzo de 2025 se revelaron múltiples vulnerabilidades de seguridad en la librería Tough de AWS Labs (una implementación cliente en Rust de TUF – The Update Framework). Estos problemas están registrados como CVE-2025-2885, CVE-2025-2886, CVE-2025-2887 y CVE-2025-2888, y se corrigieron en Tough versión 0.20.0. Los ingenieros de seguridad pueden usar este documento para comprender los detalles técnicos de cada vulnerabilidad, la causa raíz en el código y los parches que las resuelven. Se recomienda encarecidamente a todos los usuarios de Tough < 0.20.0 que actualicen a v0.20.0 o superior.
Tough no validaba la versión de Root metadata de forma secuencial durante la actualización. Un atacante que controlara un repositorio (o con capacidad de man-in-the-middle) podría suministrar un archivo de metadatos root con un número de versión inesperado, lo que haría que el cliente obtuviera y confiara en una versión incorrecta. En esencia, Tough no garantizaba que la versión de un nuevo root metadata fuera exactamente una unidad mayor que la versión previamente confiable, violando el requisito de TUF de una cadena de confianza continua. Esto podría dar lugar a ataques de rollback o mix-and-match, en los que una root antigua (pero correctamente firmada) se acepta como si fuera la más reciente, lo que podría reintroducir claves de firma retiradas o confianza caducada.
tough/src/lib.rs. La implementación vulnerable solo comprobaba que la versión de la nueva root no fuera inferior a la anterior, en lugar de exigir que fuera la siguiente versión secuencial. Esta omisión rompe la regla de la especificación TUF según la cual los clientes deben descargar las versiones intermedias de root en orden (sin saltarse versiones).Implementación vulnerable: En la versión 0.19.x y anteriores, tras descargar un nuevo root.json, Tough verificaba las firmas pero no aplicaba estrictamente la continuidad de versiones. Permitía que la nueva versión de root fuera cualquier valor >= la versión confiable. Por ejemplo, si la root confiable actual era la versión N, Tough aceptaría una nueva root que afirmara ser la versión N+2 o superior (siempre que las firmas fueran válidas), omitiendo la N+1. El siguiente fragmento de código ilustra la comprobación anterior a la corrección:```rust
// (Prior to fix) Allow new root version to be >= old version – too permissive
ensure!(root.signed.version <= new_root.signed.version, error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
});
Un atacante podría explotar esto presentando <b>metadatos raíz de una versión superior que en realidad son un conjunto de claves antiguo</b>. Tough los aceptaría, pensando que es una actualización, y [confiaría en contenido firmado con claves obsoletas](https://github.com/advisories/GHSA-5vmp-m5v2-hx47#:~:text=The%20tough%20client%20will%20trust,with%20a%20previous%20root%20role).
<b>Implementación corregida:</b> El código parcheado (en Tough 0.20.0) exige explícitamente que la versión de la nueva raíz sea exactamente una mayor que la versión anterior, consulte [commit 0eeb60a](https://github.com/awslabs/tough/commit/0eeb60aefe27f00b65730634b788a1aafb8bf3c6). También garantiza que la nueva versión sea mayor (evita la igualdad o la disminución) y previene cualquier salto mayor que +1:```rust
// (Fixed in v0.20.0) Enforce sequential root version update (new_version == old_version + 1)
ensure!(
root.signed.version < new_root.signed.version &&
root.signed.version.get() + 1 == new_root.signed.version.get(),
error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
}
);
Este cambio garantiza que el cliente descargue y aplique los metadatos raíz en orden (N, N+1, N+2, ...) sin omitir ninguno. Si se encuentra un archivo de metadatos raíz con una versión que no sea exactamente old_version+1, Tough ahora lo trata como un posible ataque y aborta la actualización.
Tough gestionó incorrectamente los roles de objetivos delegados “terminantes” según lo definido por TUF. En un repositorio TUF, una delegación puede marcarse como terminante, lo que significa que si una búsqueda de objetivo llega a esa delegación y el objetivo no se encuentra allí, la búsqueda debe detenerse (y no continuar con otras delegaciones de menor prioridad). (ver aquí) Debido a un error de lógica, Tough no logró terminar la búsqueda en este caso: continuaba buscando en delegaciones posteriores incluso cuando debería haberse detenido. Esto podría permitir que un atacante que controle una delegación de menor prioridad sirva contenido para objetivos que no debería controlar, evadiendo los límites de confianza previstos.
tough/src/editor/targets.rs (y el código relacionado de resolución de delegaciones). El código vulnerable no manejaba correctamente el indicador terminating en las delegaciones. Al iterar sobre las delegaciones en busca de un objetivo, Tough pasaba a la siguiente delegación incluso si la actual estaba marcada como terminante y no había coincidencia, en contra de la especificación TUF.Implementación vulnerable: En Tough <0.20.0, la lógica de find_target() simplemente recurría o recorría todos los roles delegados posibles hasta encontrar un objetivo o agotarlos todos. No establecía ningún indicador ni salía del bucle al encontrar un rol terminante. Pseudocódigo del comportamiento anterior:```rust
for role in delegation_chain {
if role.has_target(target) {
return target_metadata;
}
// Missing: if role is terminating and target not found, should break.
// Tough erroneously continues to next delegation.
}
Esto significa que un delegado de menor prioridad (que debería ignorarse después de una delegación terminante por encima de él) aún podría ser consultado y suministrar un archivo de destino malicioso