Удаленное выполнение кода в cdnjs Cloudflare
Предисловие
Cloudflare, на котором работает cdnjs, запускает “Программу раскрытия уязвимостей “ на HackerOne, которая позволяет хакерам проводить оценку уязвимостей.
В этой статье описываются уязвимости, обнаруженные с помощью этой программы и опубликованные с разрешения группы безопасности Cloudflare.
Таким образом, эта статья не предназначена для того, чтобы рекомендовать вам выполнить несанкционированную оценку уязвимости.
Если вы обнаружили какие-либо уязвимости в продукте Cloudflare, сообщите об этом в программу обнаружения уязвимостей Cloudflare.
TL;DR
В сервере обновления библиотеки cdnjs обнаружена уязвимость, которая может выполнять произвольные команды, в результате чего cdnjs может быть полностью скомпрометирован.
Это позволяет злоумышленнику взломать 12,7% всех веб-сайтов в Интернете после истечения срока действия кешей.
О cdnjs
cdnjs - это CDN-библиотека JavaScript/CSS, принадлежащая Cloudflare, которая используется 12,7% всех веб-сайтов в Интернете по состоянию на 15 июля 2021 года.
Это вторая наиболее широко используемая библиотека CDN после 12,8% размещенных библиотек Google, и, учитывая текущую интенсивность использования, в ближайшем будущем она станет наиболее часто используемой библиотекой CDN для библиотек JavaScript.
Причина расследования
За несколько недель до моего последнего расследования “Удаленное выполнение кода в Homebrew путем компрометации официального репозитория Cask ” я расследовал атаки на цепочку поставок.
При поиске службы, от которой зависит много программного обеспечения, и позволяющей пользователям выполнять оценку уязвимости, я обнаружил cdnjs. оэтому я решил исследовать это.
Первоначальное расследование
Просматривая сайт cdnjs, я обнаружил следующее описание.
Не можете найти нужную библиотеку?
Вы можете сделать запрос на добавление его в наш репозиторий GitHub.
Я обнаружил, что информация о библиотеке управляется в репозитории GitHub, поэтому я проверил репозитории организации GitHub, которая используется cdnjs.
В результате выяснилось, что репозиторий используется следующими способами.
- cdnjs/packages: хранит информацию о библиотеке, которая поддерживается в cdnjs.
- cdnjs/cdnjs: хранит файлы библиотек
- cdnjs/logs: хранит журналы обновлений библиотек.
- cdnjs/SRIs: хранит SRI (целостность подресурсов) библиотек.
- cdnjs/static-website: Исходный код cdnjs.com
- cdnjs/origin-worker: Cloudflare Worker для происхождения cdnjs.cloudflare.com
- cdnjs/tools: инструменты управления cdnjs
- cdnjs/bot-ansible: Репозиторий Ansible сервера обновлений библиотеки cdnjs
Как видно из этих репозиториев, большая часть инфраструктуры cdnjs централизована в этой организации GitHub.
Меня интересовали cdnjs/bot-ansible и cdnjs/tools, потому что они автоматизируют обновления библиотек.
После чтения кодов этих двух репозиториев выяснилось, что cdnjs/bot-ansible периодически выполняет команду autoupdate cdnjs/tools на сервере обновления библиотеки cdnjs, чтобы проверить обновления библиотеки из cdnjs/packages, загрузив npm package/репозиторий Git.
Исследование автоматического обновления
Функция автоматического обновления обновляет библиотеку, загружая управляемый пользователем пакет Git repository/npm и копируя из него целевой файл.
И реестр npm сжимает библиотеки в .tgz, чтобы сделать их загружаемыми.
Поскольку инструмент для этого автоматического обновления написан на Go, я предположил, что он может использовать сжатие/gzip и archive/tar в Go для извлечения файла архива.
Архив/tar Go возвращает имя файла, содержащееся в архиве, без очистки, поэтому, если архив извлекается на диск на основе имени файла, возвращенного из archive/tar, архивы, содержащие имя файла, например ../../../../ ../../../tmp/test может перезаписывать произвольные файлы в системе.
Из информации в cdnjs/bot-ansible я знал, что некоторые скрипты выполняются регулярно и пользователь, запускающий команду autoupdate, имеет для них права на запись, поэтому я сосредоточился на перезаписи файлов с помощью обхода пути.
Обход пути
Чтобы найти обход пути, я начал читать основную функцию команды автообновления.
Как видно из фрагмента кода выше, если в качестве источника автообновления указан npm, он передает информацию о пакете в функцию updateNpm.
Затем updateNpm передает информацию о новой версии библиотеки в функцию doUpdateNpm.
И doUpdateNpm передает URL-адрес файла .tgz в npm.DownloadTar.
Как я догадывался, в функции Untar для извлечения файла .tgz использовались compress/gzip и archive/tar.
Сначала я подумал, что он очищает путь в функции removePackageDir, но когда я проверил содержимое функции, я заметил, что она просто удаляет package/ из пути.
Из этих фрагментов кода я подтвердил, что произвольный код может быть выполнен после выполнения обхода пути из файла .tgz, опубликованного в npm, и перезаписи сценария, который регулярно выполняется на сервере.
Демонстрация уязвимости
Поскольку Cloudflare запускает программу раскрытия уязвимостей на HackerOne, вероятно, что группа сортировки HackerOne не отправит отчет в Cloudflare, если он не укажет, что уязвимость действительно может быть использована.
Поэтому я решил провести демонстрацию, чтобы показать, что уязвимость действительно может быть использована.
Процедура атаки следующая:
1. Опубликуйте файл .tgz, содержащий созданное имя файла, в реестр npm.
2. Подождите, пока сервер обновлений библиотеки cdnjs обработает созданный файл .tgz.
3. Содержимое файла, опубликованного на шаге 1, записывается в регулярно выполняемый файл сценария, и выполняется произвольная команда.
… И после того, как я записал процедуру атаки в свой блокнот, мне почему-то стало интересно, как работают автоматические обновления на основе репозитория Git.
Итак, я немного прочитал коды, прежде чем продемонстрировать уязвимость, и казалось, что символические ссылки не учитываются при копировании файлов из репозитория Git.
Поскольку Git по умолчанию поддерживает символические ссылки, можно прочитать произвольные файлы с сервера обновлений библиотеки cdnjs, добавив символическую ссылку в репозиторий Git.
Если регулярно выполняемый файл сценария перезаписывается для выполнения произвольных команд, функция автоматического обновления может быть нарушена, поэтому я решил сначала проверить чтение произвольного файла.
Вместе с тем, процедура атаки была изменена следующим образом.
1. Добавьте символическую ссылку, указывающую на безобидный файл (здесь предполагается /proc/self/maps) в репозиторий Git.
2. Опубликуйте новую версию в репозитории.
3. Подождите, пока сервер обновлений библиотеки cdnjs обработает созданный репозиторий.
4. Указанный файл публикуется на cdnjs.
Было около 20:00, но мне нужно было создать символическую ссылку, поэтому я решил пообедать после создания символической ссылки и ее публикации.
ln -s /proc/self/maps test.js
Инцидент
Когда я закончил ужин и вернулся к своему рабочему столу, я смог подтвердить, что cdnjs выпустил версию, содержащую символические ссылки.
Проверив содержимое файла для отправки отчета, я был удивлен.
Удивительно, но была отображена явно конфиденциальная информация, такая как GITHUB_REPO_API_KEY и WORKERS_KV_API_TOKEN.
Я не мог понять, что произошло на мгновение, и когда я проверил журнал команд, я обнаружил, что случайно поместил ссылку на /proc/self/ окружающая среда вместо /proc/self/maps.
Как упоминалось ранее, если организация cdnjs GitHub будет скомпрометирована, возможно скомпрометировать большую часть инфраструктуры cdnjs.
Мне нужно было принять немедленные меры, поэтому я отправил отчет, содержащий только ссылку, показывающую текущую ситуацию, и попросил их отозвать все учетные данные.
В этот момент я был очень сбит с толку и не подтвердил это, но на самом деле эти токены были признаны недействительными до того, как я отправил отчет.
Похоже, что GitHub немедленно уведомил Cloudflare, потому что GITHUB_REPO_API_KEY (API-ключ GitHub) был включен в репозиторий, и Cloudflare начал реагировать на инциденты сразу после уведомления.
Я чувствовал, что это отличная команда безопасности, потому что они аннулировали все учетные данные в течение нескольких минут после того, как cdnjs обработал специально созданный репозиторий.
Определенное воздействие
После инцидента я исследовал, на что можно повлиять.
GITHUB_REPO_API_KEY был ключом API для robocdnjs, который принадлежит организации cdnjs и имел разрешение на запись для каждого репозитория.
Это означает, что можно было подделать произвольные библиотеки на cdnjs или подделать сам cdnjs.com.
Кроме того, WORKERS_KV_API_TOKEN имеет разрешение на KV Cloudflare Workers, которое используется в cdnjs, его можно использовать для подделки библиотек в кеше KV.
Комбинируя эти разрешения, можно полностью изменить основную часть cdnjs, такую как исходные данные cdnjs, кеш KV и даже веб-сайт cdnjs.
Заключение
В этой статье я описал уязвимость, которая существовала в cdnjs.
Хотя эту уязвимость можно использовать без каких-либо специальных навыков, она может повлиять на многие веб-сайты.
Учитывая, что в цепочке поставок существует множество уязвимостей, которыми легко воспользоваться, но которые имеют большое влияние, я считаю, что это очень страшно.
Если у вас есть какие-либо вопросы/ комментарии по поводу этой статьи, отправьте сообщение на @ryotkak в Twitter.
Предисловие
Cloudflare, на котором работает cdnjs, запускает “Программу раскрытия уязвимостей “ на HackerOne, которая позволяет хакерам проводить оценку уязвимостей.
В этой статье описываются уязвимости, обнаруженные с помощью этой программы и опубликованные с разрешения группы безопасности Cloudflare.
Таким образом, эта статья не предназначена для того, чтобы рекомендовать вам выполнить несанкционированную оценку уязвимости.
Если вы обнаружили какие-либо уязвимости в продукте Cloudflare, сообщите об этом в программу обнаружения уязвимостей Cloudflare.
TL;DR
В сервере обновления библиотеки cdnjs обнаружена уязвимость, которая может выполнять произвольные команды, в результате чего cdnjs может быть полностью скомпрометирован.
Это позволяет злоумышленнику взломать 12,7% всех веб-сайтов в Интернете после истечения срока действия кешей.
О cdnjs
cdnjs - это CDN-библиотека JavaScript/CSS, принадлежащая Cloudflare, которая используется 12,7% всех веб-сайтов в Интернете по состоянию на 15 июля 2021 года.
Это вторая наиболее широко используемая библиотека CDN после 12,8% размещенных библиотек Google, и, учитывая текущую интенсивность использования, в ближайшем будущем она станет наиболее часто используемой библиотекой CDN для библиотек JavaScript.
Причина расследования
За несколько недель до моего последнего расследования “Удаленное выполнение кода в Homebrew путем компрометации официального репозитория Cask ” я расследовал атаки на цепочку поставок.
При поиске службы, от которой зависит много программного обеспечения, и позволяющей пользователям выполнять оценку уязвимости, я обнаружил cdnjs. оэтому я решил исследовать это.
Первоначальное расследование
Просматривая сайт cdnjs, я обнаружил следующее описание.
Не можете найти нужную библиотеку?
Вы можете сделать запрос на добавление его в наш репозиторий GitHub.
Я обнаружил, что информация о библиотеке управляется в репозитории GitHub, поэтому я проверил репозитории организации GitHub, которая используется cdnjs.
В результате выяснилось, что репозиторий используется следующими способами.
- cdnjs/packages: хранит информацию о библиотеке, которая поддерживается в cdnjs.
- cdnjs/cdnjs: хранит файлы библиотек
- cdnjs/logs: хранит журналы обновлений библиотек.
- cdnjs/SRIs: хранит SRI (целостность подресурсов) библиотек.
- cdnjs/static-website: Исходный код cdnjs.com
- cdnjs/origin-worker: Cloudflare Worker для происхождения cdnjs.cloudflare.com
- cdnjs/tools: инструменты управления cdnjs
- cdnjs/bot-ansible: Репозиторий Ansible сервера обновлений библиотеки cdnjs
Как видно из этих репозиториев, большая часть инфраструктуры cdnjs централизована в этой организации GitHub.
Меня интересовали cdnjs/bot-ansible и cdnjs/tools, потому что они автоматизируют обновления библиотек.
После чтения кодов этих двух репозиториев выяснилось, что cdnjs/bot-ansible периодически выполняет команду autoupdate cdnjs/tools на сервере обновления библиотеки cdnjs, чтобы проверить обновления библиотеки из cdnjs/packages, загрузив npm package/репозиторий Git.
Исследование автоматического обновления
Функция автоматического обновления обновляет библиотеку, загружая управляемый пользователем пакет Git repository/npm и копируя из него целевой файл.
И реестр npm сжимает библиотеки в .tgz, чтобы сделать их загружаемыми.
Поскольку инструмент для этого автоматического обновления написан на Go, я предположил, что он может использовать сжатие/gzip и archive/tar в Go для извлечения файла архива.
Архив/tar Go возвращает имя файла, содержащееся в архиве, без очистки, поэтому, если архив извлекается на диск на основе имени файла, возвращенного из archive/tar, архивы, содержащие имя файла, например ../../../../ ../../../tmp/test может перезаписывать произвольные файлы в системе.
Из информации в cdnjs/bot-ansible я знал, что некоторые скрипты выполняются регулярно и пользователь, запускающий команду autoupdate, имеет для них права на запись, поэтому я сосредоточился на перезаписи файлов с помощью обхода пути.
Обход пути
Чтобы найти обход пути, я начал читать основную функцию команды автообновления.
func main() {
[...]
switch *pckg.Autoupdate.Source {
case "npm":
{
util.Debugf(ctx, "running npm update")
newVersionsToCommit, allVersions = updateNpm(ctx, pckg)
}
case "git":
{
util.Debugf(ctx, "running git update")
newVersionsToCommit, allVersions = updateGit(ctx, pckg)
}
[...]
}
Как видно из фрагмента кода выше, если в качестве источника автообновления указан npm, он передает информацию о пакете в функцию updateNpm.
func main() {
[...]
switch *pckg.Autoupdate.Source {
case "npm":
{
util.Debugf(ctx, "running npm update")
newVersionsToCommit, allVersions = updateNpm(ctx, pckg)
}
case "git":
{
util.Debugf(ctx, "running git update")
newVersionsToCommit, allVersions = updateGit(ctx, pckg)
}
[...]
}
Затем updateNpm передает информацию о новой версии библиотеки в функцию doUpdateNpm.
func updateNpm(ctx context.Context, pckg *packages.Package) ([]newVersionToCommit, []version) {
[...]
newVersionsToCommit = doUpdateNpm(ctx, pckg, newNpmVersions)
[...]
}
И doUpdateNpm передает URL-адрес файла .tgz в npm.DownloadTar.
Наконец, передайте файл .tgz, полученный с помощью http. Get, в функцию Untar.func doUpdateNpm(ctx context.Context, pckg *packages.Package, versions []npm.Version) []newVersionToCommit {
[...]
for _, version := range versions {
[...]
tarballDir := npm.DownloadTar(ctx, version.Tarball)
filesToCopy := pckg.NpmFilesFrom(tarballDir)
[...]
}
func Untar(dst string, r io.Reader) error {
gzr, err := gzip.NewReader(r)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
[...]
// the target location where the dir/file should be created
target := filepath.Join(dst, removePackageDir(header.Name))
[...]
// check the file type
switch header.Typeflag {
[...]
// if it's a file create it
case tar.TypeReg:
{
[...]
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
[...]
// copy over contents
if _, err := io.Copy(f, tr); err != nil {
return err
}
}
}
}
}
Как я догадывался, в функции Untar для извлечения файла .tgz использовались compress/gzip и archive/tar.
Сначала я подумал, что он очищает путь в функции removePackageDir, но когда я проверил содержимое функции, я заметил, что она просто удаляет package/ из пути.
Из этих фрагментов кода я подтвердил, что произвольный код может быть выполнен после выполнения обхода пути из файла .tgz, опубликованного в npm, и перезаписи сценария, который регулярно выполняется на сервере.
Демонстрация уязвимости
Поскольку Cloudflare запускает программу раскрытия уязвимостей на HackerOne, вероятно, что группа сортировки HackerOne не отправит отчет в Cloudflare, если он не укажет, что уязвимость действительно может быть использована.
Поэтому я решил провести демонстрацию, чтобы показать, что уязвимость действительно может быть использована.
Процедура атаки следующая:
1. Опубликуйте файл .tgz, содержащий созданное имя файла, в реестр npm.
2. Подождите, пока сервер обновлений библиотеки cdnjs обработает созданный файл .tgz.
3. Содержимое файла, опубликованного на шаге 1, записывается в регулярно выполняемый файл сценария, и выполняется произвольная команда.
… И после того, как я записал процедуру атаки в свой блокнот, мне почему-то стало интересно, как работают автоматические обновления на основе репозитория Git.
Итак, я немного прочитал коды, прежде чем продемонстрировать уязвимость, и казалось, что символические ссылки не учитываются при копировании файлов из репозитория Git.
func MoveFile(sourcePath, destPath string) error {
inputFile, err := os.Open(sourcePath)
if err != nil {
return fmt.Errorf("Couldn't open source file: %s", err)
}
outputFile, err := os.Create(destPath)
if err != nil {
inputFile.Close()
return fmt.Errorf("Couldn't open dest file: %s", err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
inputFile.Close()
if err != nil {
return fmt.Errorf("Writing to output file failed: %s", err)
}
// The copy was successful, so now delete the original file
err = os.Remove(sourcePath)
if err != nil {
return fmt.Errorf("Failed removing original file: %s", err)
}
return nil
}
Поскольку Git по умолчанию поддерживает символические ссылки, можно прочитать произвольные файлы с сервера обновлений библиотеки cdnjs, добавив символическую ссылку в репозиторий Git.
Если регулярно выполняемый файл сценария перезаписывается для выполнения произвольных команд, функция автоматического обновления может быть нарушена, поэтому я решил сначала проверить чтение произвольного файла.
Вместе с тем, процедура атаки была изменена следующим образом.
1. Добавьте символическую ссылку, указывающую на безобидный файл (здесь предполагается /proc/self/maps) в репозиторий Git.
2. Опубликуйте новую версию в репозитории.
3. Подождите, пока сервер обновлений библиотеки cdnjs обработает созданный репозиторий.
4. Указанный файл публикуется на cdnjs.
Было около 20:00, но мне нужно было создать символическую ссылку, поэтому я решил пообедать после создания символической ссылки и ее публикации.
ln -s /proc/self/maps test.js
Инцидент
Когда я закончил ужин и вернулся к своему рабочему столу, я смог подтвердить, что cdnjs выпустил версию, содержащую символические ссылки.
Проверив содержимое файла для отправки отчета, я был удивлен.
Удивительно, но была отображена явно конфиденциальная информация, такая как GITHUB_REPO_API_KEY и WORKERS_KV_API_TOKEN.
Я не мог понять, что произошло на мгновение, и когда я проверил журнал команд, я обнаружил, что случайно поместил ссылку на /proc/self/ окружающая среда вместо /proc/self/maps.
Как упоминалось ранее, если организация cdnjs GitHub будет скомпрометирована, возможно скомпрометировать большую часть инфраструктуры cdnjs.
Мне нужно было принять немедленные меры, поэтому я отправил отчет, содержащий только ссылку, показывающую текущую ситуацию, и попросил их отозвать все учетные данные.
В этот момент я был очень сбит с толку и не подтвердил это, но на самом деле эти токены были признаны недействительными до того, как я отправил отчет.
Похоже, что GitHub немедленно уведомил Cloudflare, потому что GITHUB_REPO_API_KEY (API-ключ GitHub) был включен в репозиторий, и Cloudflare начал реагировать на инциденты сразу после уведомления.
Я чувствовал, что это отличная команда безопасности, потому что они аннулировали все учетные данные в течение нескольких минут после того, как cdnjs обработал специально созданный репозиторий.
Определенное воздействие
После инцидента я исследовал, на что можно повлиять.
GITHUB_REPO_API_KEY был ключом API для robocdnjs, который принадлежит организации cdnjs и имел разрешение на запись для каждого репозитория.
Это означает, что можно было подделать произвольные библиотеки на cdnjs или подделать сам cdnjs.com.
Кроме того, WORKERS_KV_API_TOKEN имеет разрешение на KV Cloudflare Workers, которое используется в cdnjs, его можно использовать для подделки библиотек в кеше KV.
Комбинируя эти разрешения, можно полностью изменить основную часть cdnjs, такую как исходные данные cdnjs, кеш KV и даже веб-сайт cdnjs.
Заключение
В этой статье я описал уязвимость, которая существовала в cdnjs.
Хотя эту уязвимость можно использовать без каких-либо специальных навыков, она может повлиять на многие веб-сайты.
Учитывая, что в цепочке поставок существует множество уязвимостей, которыми легко воспользоваться, но которые имеют большое влияние, я считаю, что это очень страшно.
Если у вас есть какие-либо вопросы/ комментарии по поводу этой статьи, отправьте сообщение на @ryotkak в Twitter.