• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Удаленное выполнение кода в cdnjs Cloudflare

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Удаленное выполнение кода в 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.

7.png


Причина расследования

За несколько недель до моего последнего расследования “Удаленное выполнение кода в 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, имеет для них права на запись, поэтому я сосредоточился на перезаписи файлов с помощью обхода пути.

8.png


Обход пути

Чтобы найти обход пути, я начал читать основную функцию команды автообновления.

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.

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)
[...]
}
Наконец, передайте файл .tgz, полученный с помощью http. Get, в функцию Untar.

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.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх