
مشكلة في tough، الإصدارات قبل 0.20.0 (عدة ثغرات CVE)
في مارس 2025، تم الكشف عن عدة ثغرات أمنية في مكتبة Tough التابعة لـ AWS Labs (وهي عميل Rust لتطبيق TUF – The Update Framework). يتم تتبع هذه المشكلات تحت CVE-2025-2885 وCVE-2025-2886 وCVE-2025-2887 وCVE-2025-2888، وتم إصلاحها في الإصدار 0.20.0 من Tough. يمكن لمهندسي الأمن استخدام هذا المستند لفهم التفاصيل الفنية لكل ثغرة، والسبب الجذري في قاعدة البيانات البرمجية، والتصحيحات التي تحل هذه المشكلات. يُنصح بشدة جميع مستخدمي Tough < 0.20.0 بالترقية إلى v0.20.0 أو أحدث.
فشل Tough في التحقق من إصدار Root metadata بشكل تسلسلي أثناء التحديث. يمكن للمهاجم الذي يتحكم في المستودع (أو يمتلك القدرة على تنفيذ هجوم الوسيط man-in-the-middle) توفير ملف بيانات وصفية جذرية برقم إصدار غير متوقع، مما يجعل العميل يجلب ويثق في إصدار خاطئ. في جوهر الأمر، لم يضمن Tough أن يكون إصدار بيانات Root metadata الجديدة أكبر بمقدار واحد بالضبط من الإصدار الموثوق به سابقًا، وهو ما يخالف متطلب TUF الخاص بسلسلة ثقة متصلة. قد يؤدي هذا إلى هجمات التراجع أو الخلط والمطابقة (rollback or mix-and-match attacks)، حيث يتم قبول جذر قديم (لكنه مُوقَّع بشكل صحيح) كما لو كان الأحدث، مما قد يؤدي إلى إعادة إدخال مفاتيح توقيع متقاعدة أو ثقة منتهية الصلاحية.
tough/src/lib.rs. كان التنفيذ الضعيف يتحقق فقط من أن إصدار الجذر الجديد ليس أقل من الإصدار القديم، بدلاً من اشتراط أن يكون الإصدار التالي في التسلسل. هذا الإغفال يخالف قاعدة مواصفات TUF التي تتطلب من العملاء تنزيل إصدارات الجذر الوسيطة بالترتيب (بدون تخطي الإصدارات).التنفيذ الضعيف: في الإصدار 0.19.x والإصدارات السابقة له، بعد تنزيل root.json جديد، كان Tough يتحقق من التوقيعات لكنه لم يفرض استمرارية الإصدار بشكل صارم. وقد سمح لإصدار الجذر الجديد بأن يكون أي قيمة >= الإصدار الموثوق به. على سبيل المثال، إذا كان الجذر الموثوق به حاليًا هو الإصدار N، فسيقبل Tough جذرًا جديدًا يدّعي أنه الإصدار N+2 أو أعلى (طالما كانت التوقيعات صالحة)، متجاوزًا الإصدار N+1. يوضح مقتطف الكود أدناه الفحص قبل الإصلاح:```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
});
يمكن للمهاجم استغلال ذلك من خلال تقديم <b>بيانات وصفية لجذر بإصدار أعلى وهي في الواقع مجموعة مفاتيح أقدم</b>. ستقبلها Tough معتقدةً أنها تحديث، وتثق [بمحتوى موقَّع بمفاتيح قديمة](https://github.com/advisories/GHSA-5vmp-m5v2-hx47#:~:text=The%20tough%20client%20will%20trust,with%20a%20previous%20root%20role).
<b>التنفيذ المُصحَّح:</b> يتطلب الكود المُصحَّح (في Tough 0.20.0) صراحةً أن يكون إصدار الجذر الجديد أكبر من الإصدار القديم بواحد بالضبط، انظر [commit 0eeb60a](https://github.com/awslabs/tough/commit/0eeb60aefe27f00b65730634b788a1aafb8bf3c6). كما يضمن أن يكون الإصدار الجديد أعلى (يمنع المساواة أو الانخفاض) ويمنع أي قفزة أكبر من +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
}
);
يضمن هذا التغيير قيام العميل بتنزيل بيانات التعريف الجذرية (root metadata) وتطبيقها بالترتيب (N، N+1، N+2، ...) دون تخطي. إذا تمت مواجهة ملف بيانات تعريف جذرية بإصدار لا يساوي تمامًا old_version+1، فإن Tough يعامله الآن كهجوم محتمل ويُجهض عملية التحديث.
تعامل Tough بشكل غير صحيح مع أدوار الأهداف المفوضة “المُنهية” كما هو معرّف في TUF. في مستودع TUF، يمكن وضع علامة مُنهٍ على تفويض، بمعنى أنه إذا وصل بحث هدف إلى ذلك التفويض ولم يتم العثور على الهدف هناك، يجب أن يتوقف البحث (ولا يستمر إلى تفويضات أخرى أقل أولوية). (انظر هنا) بسبب خطأ منطقي، فشل Tough في إنهاء البحث في هذه الحالة – حيث كان يستمر في البحث في التفويضات اللاحقة حتى عندما كان ينبغي أن يتوقف. قد يسمح هذا لمهاجم يتحكم في تفويض أقل أولوية بتقديم محتوى لأهداف لا ينبغي له التحكم بها، متجاوزًا حدود الثقة المقصودة.
tough/src/editor/targets.rs (وكود حل التفويض المرتبط به). لم يعالج الكود الهش بشكل صحيح علامة terminating على التفويضات. عند التكرار عبر التفويضات بحثًا عن هدف، كان Tough ينتقل إلى التفويض التالي حتى لو كان التفويض الحالي مُعلّمًا بأنه مُنهٍ ولم يكن هناك تطابق، بما يخالف مواصفات TUF.التنفيذ الهش: في Tough <0.20.0، كان منطق find_target() ببساطة يستدعي نفسه بشكل متكرر أو يكرر عبر جميع الأدوار المفوضة الممكنة حتى يتم العثور على هدف أو استنفاد جميع الأدوار. لم يكن يعيّن أي علامة أو يخرج من الحلقة عند مواجهة دور مُنهٍ. الكود الزائف للسلوك القديم:```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.
}
هذا يعني أن المفوَّض ذو الأولوية الأدنى (الذي ينبغي تجاهله بعد تفويض مُنهٍ فوقه) قد يظل قيد الاستشارة ويوفّر ملفًا مستهدفًا خبيثًا.