Ivanti Connect Secure - аутентифицированный RCE через OpenSSL CRLF Injection (CVE-2024-37404)

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Оригинал: https://blog.amberwolf.com/blog/202...authenticated-rce-via-openssl-crlf-injection/
Перевёл: Эрмано
Специально для XSS.IS

Детали уязвимости​

Генерация CSR​

Ivanti Connect Secure предоставляет административным пользователям возможность генерировать новые сертификаты через веб-приложение администратора по адресу URL: /dana-admin/cert/admincert.cgi
The following screenshot shows the admincert.cgi page:
1729688433360.png

Страница генерации сертификата администратора

При нажатии на опцию "Новый CSR" пользователю предлагается заполнить некоторые данные, которые будут использованы для генерации CSR с помощью CGI-скрипта по адресу /dana-admin/cert/admincertnewcsr.cgi

1729688465744.png



POST-запрос, который генерирует эта форма, выглядит следующим образом:

Код: Скопировать в буфер обмена
Код:
POST /dana-admin/cert/admincertnewcsr.cgi HTTP/1.1
Host: 192.168.2.92
Cookie: DSSignInURL=/admin; SUPPORTCHROMEOS=1; PHC_DISABLED=1; DSBrowserID=f5afd4b930af818747c03bc6f2adaf59; id=state_16f05e4e8ed9a95921f9e5561b63b977; DSSIGNIN=url_admin; DSPERSISTMSG=; DSDID=00064f3b9acd709a; DSFirstAccess=1705398262; DSLastAccess=1705398282; DSLaunchURL=2F64616E612D61646D696E2F636572742F61646D696E636572742E636769; DSID=4b2c0f96862dc17c23622ef26a7a6db8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 594
Origin: https://192.168.2.92
Referer: https://192.168.2.92/dana-admin/cert/admincertnewcsr.cgideletecsr=&selectedCSRIds=&xsauth=4e2fea1b8e12585a4744fc72820de0cc
Connection: close

xsauth=4e2fea1b8e12585a4744fc72820de0cc&commonName=vpn.pcs.local&organizationName=Example+Corp&organizationalUnitName=IT+Group&localityName=SomeCity&stateOrProvinceName=California&countryName=US&emailAddress=test%40example.com&keytype=RSA&keylength=1024&eccurve=prime256v1&random=aaaaaaaaaaaaaaaaaaaa&newcsr=yes&certType=device&btnCreateCSR=Create+CSR

Просмотрев исходный код файла admincertnewcsr.cgi, написанного на языке Perl, мы видим, что главная подпрограмма получает эти значения из POST-запроса.
CommonName проверяется с помощью регекса, но другие значения, похоже, не проверяются:

1729688560396.png



Эти управляемые пользователем значения добавляются в новый объект из команды: DSCert::NewCSR(). Это модуль Perl, который можно найти по адресу /home/perl/DSCert.pm. Этот модуль содержит обертки Swig, которые вызывают нативный код в DSCert.so:

1729688584513.png


DSCert.so импортирует функцию DSCert::NewCSR::NewCSR из libdsplibs.so:
1729688631447.png


После создания объекта CSR в файле admincertnewcsr.cgi вызывается DSCert::Admin::addCSR
1729688669164.png


Поскольку мы знаем, что это экспортируется libdsplibs.so, мы можем декомпилировать код в IDA, чтобы увидеть, как генерируется CSR.
Для некоторых параметров, таких как код страны, в этой функции предусмотрена дополнительная проверка:

1729688691231.png



Однако ряд параметров передается без фильтрации и записывается в файл конфигурации CSR по адресу /home/runtime/tmp/csrconf.<pid>.

1729688743066.png



Эта конфигурация CSR затем передается в DSCert::runOpenssl, который выполняет команду:
openssl req config /home/runtime/tmp/csrconf.<pid> -new -utf8 -out /home/runtime/tmp/csr.<pid>

Как описано в документации OpenSSL, существуют различные поля, которые могут быть переданы в конфигурации. Однако интерес для злоумышленника представляет опция engine, которая позволяет пользователю указать произвольный "движок", который будет использоваться. Как описано здесь, API Engine предоставляет интерфейс для "добавления альтернативных реализаций криптографических примитивов", и с тех пор в OpenSSL 3.0 он был заменен API Provider.
С точки зрения злоумышленника, эта функция позволяет указать путь к внешней библиотеке, из которой OpenSSL будет пытаться загрузить указанный движок. Если злоумышленник сможет внедрить эту опцию в конфигурационный файл, то он сможет получить возможность выполнения произвольного кода при загрузке движка.

Инъекция CRLF​

Как уже говорилось, проверка пользовательского ввода перед его передачей в файл конфигурации ограничена. Поэтому злоумышленник может вставить символы CRLF в один из POST-параметров, чтобы добавить собственную секцию в файл конфигурации, указав произвольный путь к движку.
Чтобы убедиться в том, что инъекция CRLF возможна, мы можем провести простой тест. Сначала мы сделаем запрос, инжектируя в параметр localityName последовательность CRLF после значения, а затем значение [foo]. Если все прошло успешно, и сертификат не содержит [foo] в локали, то это хороший знак, что инъекция CRLF работает, и значение [foo] игнорируется, поскольку оно интерпретируется как пустой раздел: CRLF Injection Test

1729688999938.png



Как и предполагалось, все прошло успешно, и локальность показывает, что значение [foo] было проигнорировано. Значение локальности показано в CSR Details

1729689025488.png



Далее мы можем попытаться внедрить новую секцию движка, при этом следует ожидать появления ошибки, поскольку мы передадим путь к несуществующей библиотеке движка.
Как показано ниже, это приводит к ошибке, поскольку файл /tmp/test.so не существует на диске:

1729689168141.png



Это подтверждает, что сервер уязвим к инъекции CRLF.

Proof of Concept​

Чтобы проверить, можно ли использовать эту проблему для удаленного выполнения кода, нам нужно создать полезную нагрузку "движка" OpenSSL (который представляет собой просто файл с общим объектом).
Устройство Ivanti Connect Secure работает на базе Centos 6.4, которая использует довольно старое ядро и версию glibc. Мы скомпилировали нашу тестовую полезную нагрузку, используя образ ubuntu:xenial x86 Docker.
Снова обратившись к нашему PoC-запросу, мы введем последовательность CRLF с фальшивым путем к движку, как показано ниже:

Код: Скопировать в буфер обмена
Код:
[default]openssl_conf = openssl_init
[openssl_init]
engines = engine_section
[engine_section]
foo = foo_section
[foo_section]
engine_id = foo
dynamic_path = /tmp/test.so
init = 0

Однако сначала нам нужно решить одну проблему. Опция dynamic_path должна ссылаться на файл общего объекта на диске. Это означает, что нам нужно найти способ получить файл на целевом сервере, чтобы на него можно было ссылаться в конфигурации OpenSSL.
Интересно, что OpenSSL, похоже, не возражает:
  • Какие разрешения назначены файлу, на который ссылается dynamic_path. Он не обязательно должен быть исполняемым.
  • Расширение файла. Оно не обязательно должно заканчиваться на .so.
Устройство Ivanti Connect Secure включает функцию "Загрузка журнала клиента", которая позволяет пользователям VPN-клиента и/или Java-апплета загружать журналы клиента в целях диагностики.
Когда пользователь загружает клиентские журналы, они сохраняются в папке /home/runtime/uploadlog/ с именем, содержащим временную метку и заканчивающимся .zip, например: log-20240115-035029.zip. Эти файлы загружаются с помощью CGI-скрипта /dana/uploadlog/uploadlog.cgi
Этот CGI не проверяет, является ли содержимое клиентских журналов действительным, и является ли сам файл ZIP-файлом.

1729689592033.png



На следующем снимке экрана показан POST-запрос, используемый для загрузки файлов журнала:

1729689622867.png



Имя результирующего файла .zip будет показано в ответе:
1729689650367.png



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

Код: Скопировать в буфер обмена
Код:
..
[foo_section]
engine_id = foo
dynamic_path = /home/runtime/uploadlog/log-20240115-035029.zip
init = 0

Примечание: сначала необходимо включить клиентские журналы и использовать учетную запись низкопривилегированного пользователя для их загрузки. Однако если у злоумышленника есть административный доступ к веб-приложению, оба этих условия могут быть выполнены непосредственно злоумышленником (т. е. путем включения загрузки клиентских журналов и/или добавления локального низкопривилегированного пользователя, если это необходимо). Клиентские журналы можно включить в Users -> User Roles -> Users (Role) -> General -> Session Options -> Enable Upload
Теперь мы можем сделать POST-запрос, содержащий наш CRLF и полезную нагрузку OpenSSL. Как показано на скриншоте ниже, это приводит к успешной эксплуатации, и создается обратная оболочка, работающая под пользователем root:
1729689721685.png
 
Сверху Снизу