
Problème avec tough, versions antérieures à 0.20.0 (CVEs multiples)
En mars 2025, plusieurs vulnérabilités de sécurité ont été divulguées dans la bibliothèque Tough d’AWS Labs (une implémentation cliente en Rust de TUF – The Update Framework). Elles sont référencées sous les identifiants CVE-2025-2885, CVE-2025-2886, CVE-2025-2887 et CVE-2025-2888, et ont été corrigées dans Tough version 0.20.0. Les ingénieurs sécurité peuvent utiliser ce document pour comprendre les détails techniques de chaque vulnérabilité, la cause racine dans le code, ainsi que les correctifs qui les résolvent. Tous les utilisateurs de Tough < 0.20.0 sont vivement invités à passer à la v0.20.0 ou à une version ultérieure.
Tough ne parvenait pas à valider en séquence la version des Root metadata lors de la mise à jour. Un attaquant contrôlant un dépôt (ou disposant d’une capacité d’homme du milieu) pourrait fournir un fichier de métadonnées racine avec un numéro de version inattendu, amenant le client à récupérer une mauvaise version et à lui faire confiance. En substance, Tough ne garantissait pas que la version d’une nouvelle métadonnée racine soit exactement supérieure de un à la version précédemment approuvée, ce qui viole l’exigence de TUF d’une chaîne de confiance continue. Cela pourrait conduire à des attaques par rollback ou par mix-and-match, où une ancienne racine (mais correctement signée) est acceptée comme s’il s’agissait de la dernière, ce qui pourrait réintroduire des clés de signature retirées ou une confiance expirée.
tough/src/lib.rs. L’implémentation vulnérable vérifiait uniquement que la version de la nouvelle racine n’était pas inférieure à l’ancienne, au lieu d’exiger qu’elle soit la version suivante dans la séquence. Cette omission enfreint la règle de la spécification TUF selon laquelle les clients doivent télécharger les versions racine intermédiaires dans l’ordre (sans sauter de version).Implémentation vulnérable : Dans les versions 0.19.x et antérieures, après avoir téléchargé un nouveau root.json, Tough vérifiait les signatures mais n’appliquait pas strictement la continuité des versions. Il permettait que la nouvelle version de la racine soit toute valeur >= la version approuvée. Par exemple, si la racine approuvée actuelle était la version N, Tough acceptait une nouvelle racine prétendant être la version N+2 ou supérieure (tant que les signatures étaient valides), sans tenir compte de N+1. L’extrait de code ci-dessous illustre la vérification effectuée avant le correctif :```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 attaquant pourrait exploiter cela en présentant <b>une métadonnée root de version supérieure qui est en réalité un ancien ensemble de clés</b>. Tough l’accepterait, pensant qu’il s’agit d’une mise à jour, et [ferait confiance à un contenu signé avec des clés obsolètes](https://github.com/advisories/GHSA-5vmp-m5v2-hx47#:~:text=The%20tough%20client%20will%20trust,with%20a%20previous%20root%20role).
<b>Implémentation corrigée :</b> Le code corrigé (dans Tough 0.20.0) exige explicitement que la version de la nouvelle métadonnée root soit exactement supérieure d’un à l’ancienne version, voir [commit 0eeb60a](https://github.com/awslabs/tough/commit/0eeb60aefe27f00b65730634b788a1aafb8bf3c6). Il garantit également que la nouvelle version est supérieure (empêche l’égalité ou la diminution) et empêche tout saut supérieur à +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
}
);
Ce changement garantit que le client télécharge et applique les métadonnées racine dans l’ordre (N, N+1, N+2, ...) sans en sauter. Si un fichier de métadonnées racine est rencontré avec une version qui n’est pas exactement old_version+1, Tough le traite désormais comme une attaque potentielle et interrompt la mise à jour.
Tough a mal géré les rôles de cibles délégués « terminants » tels que définis par TUF. Dans un dépôt TUF, une délégation peut être marquée terminante, ce qui signifie que si une recherche de cible atteint cette délégation et que la cible n’y est pas trouvée, la recherche doit s’arrêter (et ne pas continuer vers d’autres délégations de priorité inférieure). (voir ici) En raison d’une erreur logique, Tough ne parvenait pas à terminer la recherche dans ce cas – il continuait à chercher dans les délégations suivantes alors qu’il aurait dû s’arrêter. Cela pourrait permettre à un attaquant contrôlant une délégation de priorité inférieure de fournir du contenu pour des cibles qu’il ne devrait pas contrôler, contournant les frontières de confiance prévues.
tough/src/editor/targets.rs (et le code de résolution des délégations associé). Le code vulnérable ne gérait pas correctement le drapeau terminating sur les délégations. Lors de l’itération sur les délégations pour rechercher une cible, Tough passait à la délégation suivante même si la délégation courante était marquée comme terminante et ne contenait aucune correspondance, contrairement à la spécification TUF.