
معرّف CVE: CVE-2023-36664
المنتج: Ghostscript (< 10.01.2)
نوع الثغرة: تنفيذ التعليمات البرمجية عن بُعد (RCE)
تتمثل هذه الثغرة (CVE-2023-36664) في ثغرة تنفيذ تعليمات برمجية عشوائية (RCE) ناتجة عن معالجة غير دقيقة من Ghostscript للتحقق من صلاحيات المسار الخاصة بجهاز الأنبوب (بادئة %pipe% أو |).
عند قيام النظام بتحليل (Parsing) أو معالجة ملفات مستندات (PS/EPS) تم التلاعب بها بشكل خبيث من قبل المهاجم، يمكن تنفيذ أوامر نظام عشوائية دون إذن المستخدم.
الدور الرئيسي: يقرأ كود الإحداثيات الرسومية المكوَّن أساسًا من نصوص (مثل: «ارسم خطًا عند الموضع 100 200») ويحوّله إلى صورة يمكن رؤيتها على شاشة العرض أو إلى ملف صورة جاهز للطباعة عبر الطابعة.
الأنبوب (Pipe) هو ميزة تربط مخرجات أحد التطبيقات بمدخلات تطبيق آخر، مما يتيح للبرامج التواصل مع بعضها البعض. ويُرمز إليه في الأوامر بالرمز |.
مثال: cat /etc/hosts | grep localhost
عندما يكون الوضع الآمن (-dSAFER) مفعّلًا افتراضيًا، يفحص Ghostscript مسارات الملفات بصرامة لمنع الوصول إلى الملفات الحساسة في النظام أو تنفيذ أوامر خارجية بشكل عشوائي.
المشكلة: عندما يقوم المهاجم بإدخال بادئة جهاز الأنبوب (%pipe% أو |) بشكل متقن في موضع مسار الملف بدلاً من اسم ملف عادي، فإن منطق التحقق من الصلاحيات داخل Ghostscript لا يتعرّف على هذه البادئة بشكل صحيح، بل يعتبرها «مسارًا آمنًا» أو «غير خاضعة للتحقق» ويسمح بمرورها.
النتيجة: الأوامر الخطيرة التي كان يجب حجبها تتجاوز حلقة التحقق دون عناء.
حقن ملف خبيث: يزرع المهاجم تعليمات برمجية داخل ملف PostScript (.ps/.eps) بصيغة مشابهة للشكل التالي: (%pipe%أمر_خبيث) (الوضع) file /DCTDecode filter.
التحليل وسوء التعرّف: أثناء قيام Ghostscript بقراءة هذا الملف ومعالجته، يجتاز مرحلة التحقق من الصلاحيات (الحارس) دون أي أخطاء.
الإرسال إلى شل نظام التشغيل: يتم تمرير تدفق الأوامر الذي اجتاز التحقق مباشرةً عبر وظيفة جهاز الأنبوب إلى شل نظام التشغيل الداخلي مثل sh في لينكس أو cmd في ويندوز، ويُنفَّذ بصلاحيات النظام الخلفية.
بالاطلاع على الكود المصدري المُصحَّح، يمكن ملاحظة أن الملفين التاليين .c تم تعديلهما.
base/gpmisc.cbase/gslibctx.cلفهم الثغرة، يكفي النظر إلى gpmisc.c.

يكمن جوهر هذه الثغرة في عدم التمييز بين المسارات الخاصة مثل %pipe% ومسارات الملفات العادية، حيث يتم التعامل معها كمسار عادي تمامًا.
gp_file_name_reduceللتحقق من ذلك، من الضروري إلقاء نظرة على الدالة المسؤولة عن تنقية مسار الملف. الدالة التي تقوم بتنقية المسار في gpmisc.c هي gp_file_name_reduce(...)، وتقوم داخليًا باستدعاء gp_file_name_combine() وإرجاع نتيجتها.
gp_file_name_reduce(const char *fname, uint flen, char *buffer, uint *blen) {
return gp_file_name_combine(fname, flen, fname + flen, 0, false, buffer, blen);
}
كما يوحي اسمها، تقوم gp_file_name_combine() بإزالة تعبيرات المسار النسبي غير الضرورية مثل ./ أو // من مسار الملف المستلم.
وهنا تكمن المشكلة. إذا تم إدخال سلسلة خاصة تستهدف تنفيذ أوامر، مثل %pipe%، بدلاً من مسار ملف عادي، فإن الدالة لا تتعرف عليها كنمط مسار صالح، بل تعيد السلسلة الأصلية كما هي دون أي معالجة.
gp_validate_path_len
تستدعي gp_validate_path_len(...) الدالة gp_file_name_reduce() داخليًا للتحقق من المسار، ولكن لا يوجد في هذه العملية أي منطق تحقق منفصل يميز بين مسار ملف عادي وصيغة تنفيذ أوامر مثل %pipe%.
ونتيجةً لذلك، تمر السلسلة التي تحتوي على %pipe% دون أي تصفية، وهذا هو السبب الجذري لهذه الثغرة.
لذلك، إذا تم تمرير سلسلة مثل %pipe%touch /tmp/pwned كمسار ملف، فبالرغم من أن المستخدم كان ينوي فقط عرض ملف صورة، سيتم تنفيذ أمر touch /tmp/pwned فعليًا، مما يؤدي إلى إنشاء ملف /tmp/pwned.
┌─────────────────────────────────┐
│ Docker Container │
│ (Ubuntu 22.04 + Ghostscript) │
│ │
│ /home/test/ ← Working directory
│ ├── poc.py ← PoC 생성 스크립트
│ | |
│ └── /var/www/html/config.php ← 민감 정보 파일
│ │
│ User: test (비 root) │
│ Ghostscript: 10.01.1 (취약) │
└─────────────────────────────────┘
سبب اختيار حساب عادي بدلاً من حساب root هنا هو أنه في خوادم الإنتاج الفعلية، من الشائع أمنيًا عدم استخدام حساب root مباشرة. لذلك، ولأجل محاكاة سيناريو تعرض خادم فعلي للهجوم بأكبر قدر ممكن من الدقة، تم بناء البيئة على أساس حساب عادي.
version: '3.8'
services:
gs-lab:
build: .
container_name: cve_lab
network_mode: "host"
environment:
- DISPLAY=${DISPLAY}
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix:ro
- ./poc.py:/home/test/poc.py
stdin_open: true
tty: true
DISPLAY=${DISPLAY}: تمرير متغير البيئة لتمكين تطبيقات الواجهة الرسومية (GUI) داخل الحاوية من الوصول إلى خادم العرض X11 الخاص بالمضيف./tmp/.X11-unix:/tmp/.X11-unix:ro: تركيب مقبس Unix للاتصال بين خادم عرض X11 الخاص بالمضيف والحاوية.
./poc.py:/home/test/poc.py: تركيب برنامج الـ PoC المحلي من المضيف إلى بيئة تشغيل الحاوية.
FROM ubuntu:22.04
RUN apt update && \
apt install -y \
wget \
build-essential \
gedit \
python3 \
sudo && \
rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10011/ghostscript-10.01.1.tar.gz && \
tar -xzf ghostscript-10.01.1.tar.gz
WORKDIR /ghostscript-10.01.1
RUN ./configure && \
make && \
make install
RUN mkdir -p /var/www/html && \
echo "DB_PASSWORD=SuperSecret1234!!!" > /var/www/html/config.php
RUN useradd -m -s /bin/bash test
WORKDIR /home/test
USER test
CMD ["/bin/bash"]
لتكوين بيئة PoC الأساسية، نحتاج إلى wget لجلب نسخة Ghostscript المعيبة، وbuild-essential لتثبيت الكود المصدري بعد فك ضغط ملف tar.gz، وgedit كمفكرة نصية يتم فتحها بشكل خبيث عند تنفيذ ملف .ps.
تم تثبيت النسخة المعيبة 10.01.1 من Ghostscript عن طريق جلب الكود المصدري من git. وقد تم تنفيذ RUN ./configure && \ make && \ make install وفقًا لتعليمات الاستخدام المكتوبة في المجلد الذي تم تنزيل الكود المصدري إليه.
عادةً ما يحتوي /var/www/html/config.php على الإعدادات الأساسية لتطبيق الويب، مثل اسم قاعدة البيانات وكلمة المرور لذلك، وبافتراض أننا نستولي على كلمة مرور قاعدة البيانات، نقوم بإنشاء ملف config.php.
نظرًا لأننا سننفذ الـ PoC باستخدام حساب عادي باسم test وليس حساب root، فإننا نقوم بإنشاء مستخدم جديد.
# Docker 이미지 빌드
docker compose -f docker-compose.yml up -d
# 컨테이너 실행
docker exec -it cve_lab bash
داخل الحاوية:

python3 poc.py -p "gedit /var/www/html/config.php" -m r -f test
نتيجة الإنشاء:

يمكن ملاحظة أن المسار الخبيث الذي تم إدخاله قد وُضع بشكل صحيح داخل ملف .ps.
gs -dNOSAFER test.ps

سيتم فتح gedit تلقائيًا وستظهر محتويات /var/www/html/config.php.
DB_PASSWORD=SuperSecret1234!!!

يجب التحديث إلى الإصدار الأحدث من Ghostscript (10.01.2 أو أحدث) حيث تم إصلاح هذه الثغرة رسميًا.
في التصحيح الرسمي، تمت إضافة منطق تحقق داخل دالة gp_validate_path_len() يتم تنفيذه قبل استدعاء gp_file_name_reduce().
معالجة استثنائية لسلاسل الأنابيب الخاصة وفرض الحظر: تمت إضافة عبارة شرطية (فرع) تكتشف بشكل منفصل وكامل الحالات التي تبدأ فيها بادئة مسار الإدخال بالرمز %pipe% أو الرمز |.
إصدار التصحيح الرسمي: https://github.com/ArtifexSoftware/ghostpdl/commit/5f56c6f6f989816fc9cc671116740acecbed5b6c