D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Оригинал: https://blog.amberwolf.com/blog/202...authenticated-rce-via-openssl-crlf-injection/
Перевёл: Эрмано
Специально для XSS.IS
The following screenshot shows the
Страница генерации сертификата администратора
При нажатии на опцию "Новый CSR" пользователю предлагается заполнить некоторые данные, которые будут использованы для генерации CSR с помощью CGI-скрипта по адресу
POST-запрос, который генерирует эта форма, выглядит следующим образом:
Код: Скопировать в буфер обмена
Просмотрев исходный код файла
Эти управляемые пользователем значения добавляются в новый объект из команды:
DSCert.so импортирует функцию
После создания объекта CSR в файле
Поскольку мы знаем, что это экспортируется libdsplibs.so, мы можем декомпилировать код в IDA, чтобы увидеть, как генерируется CSR.
Для некоторых параметров, таких как код страны, в этой функции предусмотрена дополнительная проверка:
Однако ряд параметров передается без фильтрации и записывается в файл конфигурации CSR по адресу
Эта конфигурация CSR затем передается в
Как описано в документации OpenSSL, существуют различные поля, которые могут быть переданы в конфигурации. Однако интерес для злоумышленника представляет опция engine, которая позволяет пользователю указать произвольный "движок", который будет использоваться. Как описано здесь, API Engine предоставляет интерфейс для "добавления альтернативных реализаций криптографических примитивов", и с тех пор в OpenSSL 3.0 он был заменен API Provider.
С точки зрения злоумышленника, эта функция позволяет указать путь к внешней библиотеке, из которой OpenSSL будет пытаться загрузить указанный движок. Если злоумышленник сможет внедрить эту опцию в конфигурационный файл, то он сможет получить возможность выполнения произвольного кода при загрузке движка.
Чтобы убедиться в том, что инъекция CRLF возможна, мы можем провести простой тест. Сначала мы сделаем запрос, инжектируя в параметр
Как и предполагалось, все прошло успешно, и локальность показывает, что значение
Далее мы можем попытаться внедрить новую секцию движка, при этом следует ожидать появления ошибки, поскольку мы передадим путь к несуществующей библиотеке движка.
Как показано ниже, это приводит к ошибке, поскольку файл
Это подтверждает, что сервер уязвим к инъекции CRLF.
Устройство Ivanti Connect Secure работает на базе Centos 6.4, которая использует довольно старое ядро и версию glibc. Мы скомпилировали нашу тестовую полезную нагрузку, используя образ
Снова обратившись к нашему PoC-запросу, мы введем последовательность CRLF с фальшивым путем к движку, как показано ниже:
Код: Скопировать в буфер обмена
Однако сначала нам нужно решить одну проблему. Опция dynamic_path должна ссылаться на файл общего объекта на диске. Это означает, что нам нужно найти способ получить файл на целевом сервере, чтобы на него можно было ссылаться в конфигурации OpenSSL.
Интересно, что OpenSSL, похоже, не возражает:
Когда пользователь загружает клиентские журналы, они сохраняются в папке
Этот CGI не проверяет, является ли содержимое клиентских журналов действительным, и является ли сам файл ZIP-файлом.
На следующем снимке экрана показан POST-запрос, используемый для загрузки файлов журнала:
Имя результирующего файла
Поэтому, чтобы доставить полезную нагрузку на сервер по известному локальному пути, мы можем просто загрузить поддельный клиентский журнал через uploadlog.cgi. Затем мы можем посмотреть имя файла и определить путь. Затем на него можно сослаться в нашей инжектируемой конфигурации, например:
Код: Скопировать в буфер обмена
Примечание: сначала необходимо включить клиентские журналы и использовать учетную запись низкопривилегированного пользователя для их загрузки. Однако если у злоумышленника есть административный доступ к веб-приложению, оба этих условия могут быть выполнены непосредственно злоумышленником (т. е. путем включения загрузки клиентских журналов и/или добавления локального низкопривилегированного пользователя, если это необходимо). Клиентские журналы можно включить в
Теперь мы можем сделать POST-запрос, содержащий наш CRLF и полезную нагрузку OpenSSL. Как показано на скриншоте ниже, это приводит к успешной эксплуатации, и создается обратная оболочка, работающая под пользователем root:
Перевёл: Эрмано
Специально для XSS.IS
Детали уязвимости
Генерация CSR
Ivanti Connect Secure предоставляет административным пользователям возможность генерировать новые сертификаты через веб-приложение администратора по адресу URL:/dana-admin/cert/admincert.cgi
The following screenshot shows the
admincert.cgi
page:Страница генерации сертификата администратора
При нажатии на опцию "Новый CSR" пользователю предлагается заполнить некоторые данные, которые будут использованы для генерации CSR с помощью CGI-скрипта по адресу
/dana-admin/cert/admincertnewcsr.cgi
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
проверяется с помощью регекса, но другие значения, похоже, не проверяются:Эти управляемые пользователем значения добавляются в новый объект из команды:
DSCert::NewCSR()
. Это модуль Perl, который можно найти по адресу /home/perl/DSCert.pm
. Этот модуль содержит обертки Swig, которые вызывают нативный код в DSCert.so
:DSCert.so импортирует функцию
DSCert::NewCSR::NewCSR
из libdsplibs.so
:После создания объекта CSR в файле
admincertnewcsr.cgi
вызывается DSCert::Admin::addCSR
Поскольку мы знаем, что это экспортируется libdsplibs.so, мы можем декомпилировать код в IDA, чтобы увидеть, как генерируется CSR.
Для некоторых параметров, таких как код страны, в этой функции предусмотрена дополнительная проверка:
Однако ряд параметров передается без фильтрации и записывается в файл конфигурации CSR по адресу
/home/runtime/tmp/csrconf.<pid>
.Эта конфигурация 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Как и предполагалось, все прошло успешно, и локальность показывает, что значение
[foo]
было проигнорировано. Значение локальности показано в CSR DetailsДалее мы можем попытаться внедрить новую секцию движка, при этом следует ожидать появления ошибки, поскольку мы передадим путь к несуществующей библиотеке движка.
Как показано ниже, это приводит к ошибке, поскольку файл
/tmp/test.so
не существует на диске:Это подтверждает, что сервер уязвим к инъекции 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.
Когда пользователь загружает клиентские журналы, они сохраняются в папке
/home/runtime/uploadlog/
с именем, содержащим временную метку и заканчивающимся .zip, например: log-20240115-035029.zip. Эти файлы загружаются с помощью CGI-скрипта /dana/uploadlog/uploadlog.cgi
Этот CGI не проверяет, является ли содержимое клиентских журналов действительным, и является ли сам файл ZIP-файлом.
На следующем снимке экрана показан POST-запрос, используемый для загрузки файлов журнала:
Имя результирующего файла
.zip
будет показано в ответе:Поэтому, чтобы доставить полезную нагрузку на сервер по известному локальному пути, мы можем просто загрузить поддельный клиентский журнал через 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: