ماذا علّمتنا ثغرة حاويات Cloudflare حول أمان الواجهة الخلفية للألعاب متعددة المستأجرين
باختصار
اكتشف كيف كشفت ثغرة حاويات Cloudflare عن مخاطر أمان الواجهة الخلفية للألعاب متعددة المستأجرين وتعلم خطوات حماية بيانات لاعبيك الحساسة من التسرب.
الواجهة الخلفية للعبة المعتمدة على الحاويات تُشغّل جهازًا افتراضيًا جديدًا لكل مباراة، وكل صالة انتظار، وكل دفعة تحليلات. وعند إيقاف تشغيل الحاوية، تختفي البيانات — أليس كذلك؟
ليس بالضرورة. في سبتمبر 2026، اكتشف باحث الأمن أورين يومتوف من Accomplish أن حاويات Cloudflare كانت تعاني من ثغرة كشف بيانات عبر المستأجرين. كانت كتل القرص المتبقية من الحاويات المُدمَّرة قابلة للقراءة من قبل أعباء عمل غير ذات صلة على نفس المضيف. وشملت البيانات المستردة هياكل الدلائل، وصفحات قواعد البيانات، وقواعد بيانات SQLite مكتملة البنية.
إذا كنت تشغّل واجهات خلفية للألعاب على أي منصة حاويات متعددة المستأجرين، فإن السبب الجذري لهذه الثغرة — وهو إعداد تخزين في Linux يُسمى skip_block_zeroing في نظام dm-thin الفرعي — هو أمر تحتاج إلى فهمه. يشرح هذا المنشور ما الذي حدث بشكل خاطئ، ويوفّر دليل اكتشاف ومعالجة يمكنك تطبيقه اليوم، ويغطي المبادئ المعمارية التي تمنع وصول هذا الصنف من الثغرات إلى بيانات لاعبيك.
كيف يؤدي Thin Provisioning إلى كشف البيانات عبر المستأجرين
تشغّل حاويات Cloudflare أعباء العمل داخل أجهزة افتراضية دقيقة من Firecracker على أجهزة متعددة المستأجرين. تحصل كل حاوية على قرص جذر قابل للكتابة مدعوم بالتزويد الرقيق لـ Linux device-mapper (dm-thin). يعرض Firecracker هذا القرص على الجهاز الافتراضي الضيف كـ /dev/vdc.
يعمل التزويد الرقيق عن طريق تأجيل تخصيص التخزين الفعلي. عند إنشاء قرص افتراضي بحجم 10 GiB، فإنه يستهلك تقريبًا لا مساحة فعلية. تُخصَّص الكتل عند الطلب من مجمع مشترك عندما يكتب الضيف إلى منطقة غير معيّنة مسبقًا. استخدمت مجمعات Cloudflare المتأثرة حجم كتلة رقيقة 64 KiB.
هنا تكمن الثغرة. عندما يتم تدمير حاوية، تُحذف تعيينات الحجم الرقيق، وتعود الكتل الفعلية إلى المجمع المشترك لإعادة الاستخدام. تم تكوين المجمع المتأثر بما يلي:
skip_block_zeroing
مع هذا الخيار، يتخطى dm-thin تصفير الكتل المخصصة حديثًا قبل جعلها متاحة للمستأجر التالي. كتابة كاملة بحجم 64 KiB تستبدل الكتلة المعاد تدويرها بالكامل. لكن الكتابة الجزئية — لنقل 4 KiB — تستبدل تلك المنطقة فقط. يمكن أن تحتفظ الـ 60 KiB المتبقية ببيانات من المالك السابق للكتلة.
هذه ليست حالة حافة نظرية. استعاد الباحث هياكل دلائل، وصفحات قواعد بيانات، وقواعد بيانات SQLite كاملة من الكتل المتبقية عبر 18 من 24 موضع إنتاج و20 من 22 عقدة أساسية تمتد عبر أربع قارات.
لماذا تُعد الكتابات الجزئية المتجه الرئيسي
قراءة منطقة غير معيّنة من قرص رقيق جديد تُرجع أصفارًا — يوفّر dm-thin أصفارًا دون تخصيص كتلة فعلية. هذا آمن. يعمل الاستغلال لأن كتابة صغيرة تُطلق تخصيص كتلة 64 KiB معاد تدويرها دون تصفيرها أولاً.
نمط الهجوم:
- أنشئ حاوية على حساب Workers Paid.
- افتح
/dev/vdc(قرص الجذر القابل للكتابة). - حدد المناطق المحاذية لـ 64 KiB المقابلة للمساحة الحرة في ext4.
- اكتب كتلة واحدة محاذية بحجم 4 KiB في كل منطقة مستهدفة.
- اقرأ كتل 64 KiB كاملة.
- افحص فقط الـ 60 KiB التي لم يستبدلها المهاجم.
الخطوة 4 هي اللحظة الحاسمة. تتسبب كتابة 4 KiB في قيام dm-thin بتخصيص كتلة فعلية من المجمع المشترك. ولأن التصفير معطّل، فقد تحتوي الـ 60 KiB المتبقية على بيانات متبقية من أي شخص كان يملك تلك الكتلة سابقًا.
ما الذي تحقق منه الباحث فعليًا
استخدم الباحثون مجاميع اختبارية لكتل دلائل ext4 (ميزة metadata_csum) لتمييز كتل نظام الملفات الاختباري الخاص بهم عن الكتل الأجنبية. عبر ستة مواضع إنتاج:
- 5,614 كتلة دليل قابلة للاختبار تم فحصها
- 0 كتل تُنسب إلى نظام الملفات الخاص بالباحثين
- 2,700 عقدة دليل أجنبية مميزة تم تحديدها عبر تحليل المجموع الاختباري
لم تكن أنواع الملفات المستردة مجرد بيانات تالفة. لقد تضمنت قواعد بيانات SQLite ذات بنية ذات معنى — النوع من البيانات الذي، في سياق الواجهة الخلفية للألعاب، يمكن أن يحتوي على حالات حفظ اللاعبين، أو رموز الجلسات، أو سجلات المخزون.
دليل الاكتشاف: العثور على حصاد البيانات المتبقية في بنيتك التحتية
إذا كنت تشغّل واجهات خلفية للألعاب المعتمدة على الحاويات على بنية تحتية متعددة المستأجرين، فأنت بحاجة إلى طبقتين من الاكتشاف: تدقيق التكوين وتحليل شذوذ الإدخال/الإخراج في وقت التشغيل.
الخطوة 1: تدقيق تكوين dm-thin لديك
شغّل هذا السكربت على كل مضيف يشغّل حاوياتك:
#!/bin/bash
set -euo pipefail
echo "=== dm-thin Pool Configuration Audit ==="
echo ""
# Find all thin pool devices on this host
for pool in $(dmsetup status --target thin-pool 2>/dev/null | awk '{print $1}'); do
echo "Pool: $pool"
TABLE=$(dmsetup table "$pool")
echo " Full table: $TABLE"
if echo "$TABLE" | grep -q "skip_block_zeroing"; then
echo " ⚠️ WARNING: skip_block_zeroing is ENABLED"
echo " Recycled blocks may retain previous tenant data."
echo " Action: Remove skip_block_zeroing from pool table."
else
echo " ✅ Block zeroing is active (default dm-thin behavior)"
fi
echo ""
done
echo "=== Thin-block size check ==="
for pool in $(dmsetup status --target thin-pool 2>/dev/null | awk '{print $1}'); do
BLOCK_SECTORS=$(dmsetup table "$pool" | grep -oP '\d+ \d+ thin-pool' | awk '{print $1}')
BLOCK_KB=$((BLOCK_SECTORS * 512 / 1024))
echo " Pool $pool: block size = ${BLOCK_KB} KiB"
if [ "$BLOCK_KB" -le 64 ]; then
echo " ⚠️ Small block size amplifies residual data exposure."
echo " Larger block sizes reduce the ratio of leftover bytes per partial write."
fi
done
إذا ظهر skip_block_zeroing في أي مكان في تكوين المجمع لديك، فأنت تمتلك نفس صنف الثغرة التي صححتها Cloudflare. أصلحها فورًا — لا تنتظر نافذة الصيانة المجدولة.
الخطوة 2: راقب التوقيع المميز للإدخال/الإخراج
يُنتج الاستغلال توقيعًا قابلًا للاكتشاف: كتابات صغيرة تليها قراءات كبيرة بشكل غير متناسب. كتابة 4 KiB تخصص كتلة 64 KiB؛ قراءة لاحقة تسترجع كامل 64 KiB. تُعد بيانات القياس عن بُعد للحاويات التي يتجاوز فيها حجم القراءة حجم الكتابة بأكثر من 10 أضعاف أمرًا مريبًا.
#!/usr/bin/env python3
"""
Detect anomalous write-then-read patterns characteristic of
cross-tenant residual data harvesting in thin-provisioned containers.
Usage: python detect_residual_harvesting.py io_events.jsonl
Input: JSONL file with one event per line:
{"container_id": "abc123", "operation": "write", "size_bytes": 4096}
"""
import json
import sys
from collections import defaultdict
ANOMALY_THRESHOLD = 10.0 # read_bytes : write_bytes ratio
def analyze_io_events(events_file):
with open(events_file) as f:
events = [json.loads(line) for line in f]
containers = defaultdict(
lambda: {"writes": 0, "reads": 0, "write_bytes": 0, "read_bytes": 0}
)
for event in events:
cid = event.get("container_id", "unknown")
op = event.get("operation")
size = event.get("size_bytes", 0)
if op == "write":
containers[cid]["writes"] += 1
containers[cid]["write_bytes"] += size
elif op == "read":
containers[cid]["reads"] += 1
containers[cid]["read_bytes"] += size
print("=" * 60)
print("CONTAINERS WITH ANOMALOUS READ/WRITE RATIOS")
print("=" * 60)
flagged = 0
for cid, stats in sorted(containers.items()):
if stats["write_bytes"] == 0:
continue
ratio = stats["read_bytes"] / stats["write_bytes"]
if ratio > ANOMALY_THRESHOLD:
flagged += 1
print(f"\n🚩 Container: {cid}")
print(f" Writes: {stats['writes']:,} ops | "
f"{stats['write_bytes']:,} bytes")
print(f" Reads: {stats['reads']:,} ops | "
f"{stats['read_bytes']:,} bytes")
print(f" Ratio: {ratio:.1f}x — investigate")
if flagged == 0:
print("\n✅ No anomalous containers detected.")
print(f"\nScanned {len(containers)} containers. Flagged: {flagged}")
return flagged
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python detect_residual_harvesting.py <io_events.jsonl>")
sys.exit(1)
sys.exit(1 if analyze_io_events(sys.argv[1]) > 0 else 0)
في حادثة Cloudflare، أنتج إثبات المفهوم للباحثين هذا التوقيع بالضبط. بنى فريق أمان Cloudflare توقيعات اكتشاف من PoC، وطبّقها على بيانات قياس الإدخال/الإخراج التاريخية للقرص، وأكد أن النشاط المطابق الوحيد جاء من الباحثين ومهندسي Cloudflare أنفسهم أثناء التحقق المصرح به. لم يتم العثور على أي استغلال من طرف ثالث.
الدرس: إذا كنت تجمع بيانات قياس الإدخال/الإخراج للحاويات، يمكنك التدقيق بأثر رجعي. إذا كنت لا تجمعها، فأنت تطير عمياء.
دليل المعالجة
إذا كشف تدقيقك عن skip_block_zeroing أو تعرّض مشابه، فهذا هو تسلسل المعالجة الذي نفذته Cloudflare — والترتيب مهم.
الخطوة 1: إعادة تفعيل تصفير الكتل على جميع المجمعات
أزل skip_block_zeroing من تكوين مجمع dm-thin لديك. هذا تغيير في وقت التشغيل على جهاز thin-pool، لكنه يحمي التخصيصات الجديدة فقط. تظل الكتل المعيّنة بالفعل في الحاويات الجارية ولقطات الصور المخزنة مؤقتًا عرضة للخطر.
# This is a simplified representation — actual dm-thin
# pool recreation commands vary by your orchestration layer.
# The core principle: restart the pool WITHOUT skip_block_zeroing.
dmsetup reload <pool-name> --table "0 <size> thin-pool <meta_dev> <data_dev> <data_block_size> 0"
dmsetup suspend <pool-name>
dmsetup resume <pool-name>
دمجت Cloudflare هذا الإصلاح خلال حوالي 6 ساعات من التقرير الأولي. أكد الباحثون بشكل مستقل أن PoC توقف عن العمل بعد هذا التغيير.
الخطوة 2: إحالة جميع أقراص الحاويات الجارية إلى التقاعد
تحتفظ الكتل المعيّنة بالفعل في الأجهزة الرقيقة النشطة بمحتواها (غير المصفّر). الطريقة الوحيدة للقضاء على البيانات المتبقية هي تدمير وإعادة إنشاء كل قرص حاوية.
فرّغت Cloudflare المضيفين خلال ساعات الذروة المنخفضة وأعادت تشغيل الأجهزة الافتراضية على كل مضيف. هذه عملية متدحرجة — بالنسبة للواجهة الخلفية للعبة، جدولها خلال نافذة أقل حركة مرور لديك. توقع انقطاعًا قصيرًا لكل مضيف. إذا كانت طبقة المطابقة (Matchmaking) لديك تدعم التسليم السلس، فسيتم ترحيل اللاعبين على المضيفات التي يتم تفريغها بدلاً من فصلهم.
الخطوة 3: مسح لقطات الصور المخزنة مؤقتًا
من السهل تفويت هذه الخطوة. عادةً ما تخزّن طبقات تنسيق الحاويات طبقات صور OCI مؤقتًا كلقطات dm-thin لبدء تشغيل سريع للحاويات. تم إنشاء هذه اللقطات المخزنة مؤقتًا قبل الإصلاح ويمكن أن تحتوي على بيانات متبقية في مناطق غير مستخدمة (بما في ذلك المساحة الحرة في ext4). يمكن لحاوية جديدة ترث طبقة مخزنة مؤقتًا أن تقرأ بايتات متبقية من /dev/vdc دون تخصيص كتلة جديدة على الإطلاق.
أزالت Cloudflare جميع اللقطات المخزنة مؤقتًا قبل المعالجة عبر الأسطول المتأثر، وأكملت التنظيف بعد 15 يومًا من التقرير الأولي. تمت إعادة إنشاء الطبقات المخزنة مؤقتًا باستخدام تخصيصات مصفّرة.
ترتيب إعادة الإنشاء مهم. إذا قمت بمسح المخزونات المؤقتة قبل تفعيل التصفير، فستكون قد دفعت الكتل غير المصفّرة مباشرة إلى لقطات جديدة.
الخطوة 4: تحقق من صمود الإصلاح
بعد المعالجة، شغّل سكربت الاكتشاف على بيانات قياس الإدخال/الإخراج الجديدة لمدة 48 ساعة على الأقل. قارن نسب الكتابة/القراءة بخط الأساس قبل التصحيح. يجب أن يختفي النمط المميز عالي النسبة تمامًا.
يجب عليك أيضًا التحقق من جدول dm-thin على كل مضيف بعد إعادة التشغيل:
# Confirm no host still has skip_block_zeroing
dmsetup table 2>/dev/null | grep -q "skip_block_zeroing" && \
echo "CRITICAL: Host $(hostname) still has skip_block_zeroing" || \
echo "OK: $(hostname) — block zeroing active"
مبادئ معمارية لمنع هذا الصنف من الثغرات
حادثة Cloudflare هي حالة واحدة من صنف أوسع من إخفاقات عزل التخزين متعدد المستأجرين. إليك المبادئ التي تحمي الواجهة الخلفية للعبتك — سواء كنت تدير حاوياتك الخاصة أو تعتمد على منصة.
المبدأ 1: لا تعطّل تصفير الكتل أبدًا في المجمعات متعددة المستأجرين
يوجد خيار skip_block_zeroing من أجل الأداء — فتصفير كتل 64 KiB عند كل تخصيص يضيف عبئًا على الإدخال/الإخراج. على الأجهزة أحادية المستأجر حيث يعمل كودك فقط، قد تكون المفاضلة مقبولة. على البنية التحتية المشتركة حيث تُعاد تدوير الحاويات عبر المستأجرين، فهو عيب أمني.
قاعدة: أي مجمع dm-thin يخصص كتلًا لأكثر من هوية مستأجر واحدة يجب أن يكون تصفير الكتل مفعّلًا. فرض ذلك في طبقة البنية التحتية كرمز (infrastructure-as-code) بحيث لا يمكن توفير أي مضيف بدونه.
المبدأ 2: تعامل مع أقراص الحاويات باعتبارها عابرة وخطيرة
يجب ألا تفترض الواجهة الخلفية للعبتك أبدًا أن قرص الحاوية نظيف عند بدء التشغيل. حتى مع تفعيل تصفير الكتل، توجد حالات حافة مع طبقات التخزين المؤقت، ووراثة اللقطات، وأخطاء النواة.
اكتب نقطة دخول الحاوية لتهيئة أو مسح وحدة التخزين القابلة للكتابة عند الإقلاع:
#!/bin/bash
# Ensure a clean writable disk on container start
if [ -b /dev/vdc ]; then
# Overwrite with zeros (slow but thorough)
# For a game backend, the writable disk is typically small (1-4 GiB)
dd if=/dev/zero of=/dev/vdc bs=1M count=4096 status=progress
# Create a fresh filesystem
mkfs.ext4 -F /dev/vdc
mount /dev/vdc /workspace
fi
يضيف هذا بضع ثوانٍ إلى بدء تشغيل الحاوية. لمباراة لعبة تستمر من 5 إلى 45 دقيقة، هذا مقبول. لمتطلبات البدء البارد دون الثانية، استخدم النهج الأسرع: blkdiscard (الذي يُطلق TRIM وقد يصفّر الكتل اعتمادًا على الواجهة الخلفية للتخزين) مع mkfs.ext4 -F.
المبدأ 3: راقب نسب الإدخال/الإخراج كإشارة أمنية
تركز معظم مراقبة الحاويات على وحدة المعالجة المركزية والذاكرة والشبكة. أضف إدخال/إخراج القرص إلى بيانات القياس الأمنية لديك. نسبة البايتات المقروءة إلى البايتات المكتوبة هي إشارة قوية لهجمات حصاد البيانات المتبقية — أعباء العمل المشروعة للألعاب تكتب وتقرأ بأنماط متوازنة. الحاوية التي تكتب قطعًا بحجم 4 KiB ثم تقرأ 60+ KiB لكل منطقة هي حالة شاذة.
إذا كانت الواجهة الخلفية لديك تسجّل بالفعل أحداث دورة حياة الحاوية، يمكنك ربط شذوذ الإدخال/الإخراج بهوية المستأجر والطوابع الزمنية للإنشاء لتضييق نطاق الانفجار لحادث محتمل. لمطوري الألعاب الذين يريدون تقارير أعطال مدمجة وسجلات مستخدمين دون إدارة هذه البنية التحتية للقياس بأنفسهم، توفر منصات مثل horizOn هذه الإمكانات خارج الصندوق — مما يعني أنك تقضي وقتًا أقل في بناء السباكة والمزيد من الوقت في منطق اللعبة.
المبدأ 4: طبّق الدفاع في العمق لبيانات اللاعبين
حتى إذا تسرب قرص الحاوية لديك، يكون الضرر محدودًا إذا كانت البيانات الموجودة عليه مشفرة أو بلا معنى بدون مفتاح. خزّن حالات حفظ اللاعبين ورموز الجلسات وبيانات المخزون مشفرة أثناء التخزين. يجب أن يعيش مفتاح التشفير في مدير أسرار، وليس على قرص الحاوية.
هذا هو نفس المبدأ الذي غطيناه في تحليلنا لـ اختراق بيانات Star Citizen وكيفية تصميم واجهات خلفية للألعاب تنجو من الاختراقات — الدفاع في العمق يعني افتراض أن أي طبقة واحدة ستفشل في النهاية.
قائمة أفضل الممارسات
دقق في تكوين كل مجمع dm-thin قبل النشر. أضف فحصًا قبل التشغيل إلى خط أنابيب تنسيق الحاويات لديك يفشل إذا كان
skip_block_zeroingموجودًا. هذا فحص CI من خمسة أسطر يمنع صنفًا كاملًا من الثغرات.اجمع بيانات قياس إدخال/إخراج القرص للحاويات مع إسناد إلى المستأجر. لا يمكنك اكتشاف استغلال لا تلاحظه. سجّل أحجام الكتابة/القراءة لكل حاوية، مرتبطة بمعرّف المستأجر ومعرّف المضيف. احتفظ بها لمدة 30 يومًا على الأقل للتحقيق بأثر رجعي.
صفّر أو امسح وحدات التخزين القابلة للكتابة للحاويات عند الإقلاع. لا تعتمد على طبقة التخزين وحدها. إضافة
mkfs.ext4جديد عند بدء التشغيل يضيف 2–5 GiB من عبء الكتابة ولكنه يضمن عدم بقاء أي بيانات متبقية بعد إعادة تدوير الحاوية.فرّغ وأعد تدوير الحاويات أثناء التصحيحات الأمنية، وليس بعدها فقط. تفعيل تصفير الكتل يحمي التخصيصات الجديدة فقط. يجب مسح التعيينات الموجودة في الحاويات الجارية واللقطات المخزنة مؤقتًا صراحةً. اتبع دائمًا الإصلاح بإعادة تدوير على مستوى الأسطول.
شفر بيانات اللاعبين الحساسة أثناء التخزين مع إدارة مفاتيح خارجية. إذا تسربت كتلة متبقية، فإن النص المشفر بدون المفتاح عديم الفائدة. حالات حفظ اللاعبين المُدارة عبر horizOn للحفظ السحابي المرتبط بالحساب تستخدم كتابات واعية بالمراجعات وحل تعارض من جانب العميل — لكن عزل القرص الأساسي لا يزال مسؤوليتك إذا كنت تستضيف الحاويات بنفسك.
الجدول الزمني: كيف استجابت Cloudflare
للرجوع، إليك مدى السرعة التي يمكن لفريق جيد الموارد أن يتحرك بها بشأن ثغرة بنية تحتية حرجة:
| الوقت (UTC) | الإجراء |
|---|---|
| Sep 4, 15:26 | يبلغ الباحث عبر HackerOne |
| Sep 4, 18:45 | فتح حادث أمني، تأكيد إعداد الإنتاج |
| Sep 4, 21:27 | دمج الإصلاح في وقت التشغيل مع اختبار إعادة الاستخدام |
| Sep 4, 23:15 | بدء التوزيع |
| Sep 7, 06:13 | اكتمال التوزيع، بدء التنظيف |
| Sep 14, 10:50 | يؤكد الباحث أن PoC لم يعد يعمل |
| Sep 19, 15:03 | مسح جميع اللقطات المخزنة مؤقتًا قبل المعالجة |
هذا أقل من 3 ساعات من التقرير إلى تأكيد السبب الجذري، وأقل من 8 ساعات إلى إصلاح مدمج. استغرق التنظيف الكامل للأسطول 15 يومًا — وهو أمر طبيعي للعمليات المتدحرجة عبر بنية تحتية عالمية.
السرعة مهمة. كل ساعة بين تقرير الثغرة والإصلاح هي ساعة يمكن فيها الاستغلال. إذا كنت تشغّل بنية الحاويات التحتية الخاصة بك، فابنِ دليل التشغيل قبل أن تحتاج إليه.
الخلاصة
لم تكن ثغرة حاويات Cloudflare ثغرة يوم صفر بالمعنى التشفيري. لقد كانت مفاضلة تكوين تخزين معروفة — الأداء على حساب الأمان — غير مناسبة لأعباء العمل متعددة المستأجرين. كان الإصلاح تغيير تكوين واحد بالإضافة إلى إعادة تدوير الأسطول.
إذا كنت تشغّل واجهات خلفية للألعاب على بنية حاويات مشتركة، فدقق في مجمعات dm-thin لديك اليوم. شغّل سكربت الاكتشاف على بيانات قياس الإدخال/الإخراج لديك. وإذا كنت تفضل عدم إدارة عزل قرص الحاوية، وإعادة تدوير الأسطول، وخطوط أنابيب قياس الإدخال/الإخراج بنفسك، فإن horizOn يتولى المصادقة، وتقارير الأعطال، والحفظ السحابي، ولوحات المتصدرين، وخدمات الواجهة الخلفية الأخرى التي تحتاجها لعبتك — بحيث يمكنك التركيز على إطلاق لعبتك، وليس تصحيح أخطاء أمان طبقة التخزين في الإنتاج في الساعة 3 صباحًا.
المصدر: كيف عالجت Cloudflare ثغرة كشف البيانات عبر المستأجرين في الحاويات