[JS] Руководство для начинающих: защита конечной точки веб-перехватчика приложения GitHub
Учебник для начинающих по веб-перехватчикам, подписям HMAC и промежуточному программному обеспечению. Узнайте, как защитить свой сервер, написав промежуточное ПО Express, которое проверяет подлинность полезных данных событий, отправляемых GitHub.
Приложения GitHub позволяют настроить веб-перехватчик для прослушивания всевозможных событий в организациях и репозиториях. Они могут варьироваться от новых или обновленных выпусков до PR-комментариев и развертываний. С использованием API-интерфейс GitHub ваше приложение становится способным реагировать на эти события. Возможности действительно безграничны для тех, кто любит автоматизацию.
Но с большой силой приходит большая ответственность! Вы не хотели бы автоматизировать действия из ненадежных или вредоносных полезных нагрузок.
К счастью, GitHub предоставляет необязательный ( но серьезно рекомендуется ) секретное поле webhook на странице настроек вашего приложения. Используя этот секрет и немного волшебства, мы можем убедиться, что полезная нагрузка является законной и получена непосредственно с GitHub, прежде чем предпринимать какие-либо действия.
Что такое вебхук?
Веб-перехватчик — это конечная точка, которую один сервер использует для передачи информации на другой сервер. Проще говоря, это способ уведомления третьих лиц о событии или действии. Сервисы могут контролировать свои внутренние реакции на события, а также позволяют третьим сторонам получать и обрабатывать те же события по своему усмотрению.
Скажем, у нас есть два сервера — сервер событий и прослушивающий сервер. В контексте GitHub Apps сервер событий — это GitHub, а прослушивающий сервер — это сервер вашего приложения.
Серверы прослушивания могут регистрироваться на серверах событий, чтобы крюк в происходящие события. Они настраивают, о каких событиях они хотят получать уведомления и на какую конечную точку они хотят, чтобы уведомления (запросы) отправлялись. Эта конечная точка называется конечной точкой веб-перехватчика прослушивающего сервера.
Распространенным способом защиты конечных точек веб-перехватчика является использование секрета веб-перехватчика. Этот секрет в сочетании с некоторой криптографией обеспечивает механизм доверия между серверами событий и серверами прослушивания.
Как работает секрет вебхука
Когда вы предоставляете GitHub секрет веб-перехватчика, они используют его для создания HMACподпись. Подписи HMAC — это способ подписи полезной нагрузки с использованием секрета, который известен только серверу событий и серверу прослушивания.
Эта подпись прикрепляется в качестве заголовка к запросам событий, отправляемым на сервер вашего приложения. GitHub, в частности, использует HMAC-SHA1, но алгоритм хеширования не зависит от того, как работают HMAC.
Подпись HMAC не шифрует полезные данные. Это проверяемый хэш подписи сделано из секрета [our webhook secret] и текстовые данные [GitHub event payload]. Вы также можете зашифровать данные, но это необязательный шаг.
Подпись используется для проверки легитимности как источника, так и самих данных. Когда запрос получен прослушивающим сервером, он использует свой сохраненный секрет и полезную нагрузку события для создания своей собственной подписи HMAC-SHA1 для сравнения с подписью заголовка.
Когда секрет и полезная нагрузка одинаковы с обеих сторон, подписи HMAC будут совпадать. Это совпадение подтверждает подлинность запроса и данных.
Как насчет подделки данных?
Поскольку подпись создается GitHub с использованием секрета нашего приложения и его полезной нагрузки события, мы можем быть уверены, что оба являются подлинными, когда они оставляют свой конец в запросе.
Но что делать по проводу, если сообщение перехвачено и изменено? Как здесь нам поможет подпись HMAC?
Помните, что и GitHub, и наше собственное приложение генерируют подписи из одного и того же секрета и полезной нагрузки. Пока секрет никогда не просочится, сгенерированная подпись может измениться только при изменении полезной нагрузки.
Злоумышленник будет иметь только подпись из заголовка и исходную полезную нагрузку. У них не было бы [reasonable] способ деконструировать нашу тайну из двух —пока вы используете сильный секрет.
Без секрета у них не было бы возможности сфальсифицировать новую подпись для сопряжения с измененными данными. Любая попытка фальсификации полезной нагрузки приведет к созданию другой сгенерированной подписи, которая не сможет сравниться с подписью заголовка.
Люди чертовски умны. На сборку!
Настраивать
Перейдите на страницу своего приложения GitHub и прокрутите вниз до раздела веб-перехватчиков.
Вы можете найти страницу своего приложения в разделе [user or org] настройки → Приложения GitHub
URL-адрес вебхука
Если вы все еще находитесь в разработке, вы можете использовать такой инструмент, как ngrok
чтобы открыть локальный порт, на котором работает ваше приложение. Он позволяет внешней сети подключаться к вашему локальному серверу через безопасный туннель. Мы будем использовать это для проверки конечных точек нашего приложения.
npm i ngrok --save-dev
- запустите сервер приложений на выбранном вами порту
ngrok http <Your App's Local Port>
- затем скопируйте
https
ссылка, которую он генерирует
Теперь просто добавьте это ngrok
URL-адрес (или действующий URL-адрес, если вы уже развернуты), за которым следует путь вашего веб-перехватчика к настройкам вашего приложения.
Обязательно выключите
ngrok
когда вы закончите его использовать. Не оставляйте открытый порт, даже если он открыт через случайно сгенерированный URL-адрес.
Тсссс
Далее сгенерируем секрет. Я решил сгенерировать случайный хэш, но не стесняйтесь использовать свой собственный подход. Просто убедитесь, что вы используете что-то действительно безопасное — никаких имен собак в виде простого текста!
Поскольку Документы GitHub на рубине и это все новая информация для меня, я спустился в стандартную черную дыру исследований. К счастью, в отличие от большинства моих 50+ вики-вкладок, и я даже больше результаты, на этот раз я ударил золото.
Пользователь663183 объясняетзачем использовать randomBytes()
является более безопасным, чем использование временной метки или генератора случайных чисел. Наряду с некоторой другой полезной информацией — стоит прочитать.
- откройте свой терминал и
node
оболочка const crypto = require('crypto');
crypto.randomBytes(32).toString(‘hex’);
- скопируйте выходную строку
Теперь вам нужно сохранить эту строку в среде вашего приложения или в другом безопасном месте. Затем снова вставьте его как секрет в настройках приложения GitHub.
ПО промежуточного слоя
Наконец-то к реальному коду! Мы напишем две функции — утилиту для создания сигнатуры сравнения и нашу промежуточную функцию веб-перехватчика.
Что такое промежуточное ПО?
Если вы не знакомы с промежуточным программным обеспечением, не волнуйтесь! На самом деле все очень просто и логично. Промежуточное ПО относится к поведению, которое вы хотите выполнить при входящем запросе. Вы можете применять промежуточное ПО на уровне сервера (влияет на все входящие запросы) или для определенных маршрутов.
Без промежуточного ПО ваш процесс HTTP-запроса-ответа выглядит так:
запрос → обработка обработчика маршрута → ответ
С промежуточным ПО ваш процесс выглядит так:
запрос → обработка промежуточного программного обеспечения → обработка обработчика маршрута → ответ
Так что промежуточное ПО просто попадает в середину запроса до того, как он попадет в ваш обработчик маршрута. И вы можете комбинировать или куча промежуточное программное обеспечение, чтобы иметь несколько промежуточных шагов, прежде чем ваш обработчик маршрута увидит запрос.
Промежуточное ПО можно использовать для перехвата объекта запроса и выполнения проверки, регистрации, преобразования или внедрения в него. Затем промежуточное ПО передает запрос по цепочке, возможно, через другое промежуточное ПО, к обработчику запросов, для которого он изначально предназначался.
Вы можете делать такие вещи, как проверка зарегистрированных пользователей (из файла cookie или заголовка аутентификации), обмен на пользователя и вводить его в объект запроса как req.user
. Или что-то удобное, например body-parser
который просто очищает данные входящего запроса и переводит их в более удобную форму на req.body
.
Возможности безграничны. И наш случай — идеальный кандидат на промежуточное ПО. Мы проверим достоверность всех полезных данных событий до того, как наше приложение выполнит какую-либо обработку. Любые недопустимые полезные нагрузки мы можем отклонить заранее и предотвратить нежелательное или опасное поведение.
Код
Промежуточное ПО для проверки полезной нагрузки
[middleware] проверитьGithubPayload()
Эта функция предполагает, что вы используете body-parser
промежуточное ПО на уровне приложения (влияющее на все входящие запросы). Если нет, то вы можете установить и настроить его используя свои документы.
Наша промежуточная функция будет делать следующее:
- извлечь заголовки и тело [payload] из запроса
- извлечь
x-hub-signature
заголовок, который GitHub использует для отправки своей подписи HMAC с запросом - создадим собственную внутреннюю подпись для сравнения с помощью
createComparisonSignature()
- сравнивает подписи через
compareSignatures()
- вернуть
401
(неавторизованный) код состояния, если подписи не совпадают - ввести
event_type
свойство из заголовка в объект запроса. Доступные типы могут быть нашел здесь - вводить
action
а такжеpayload
свойства в запрос, чтобы упростить дальнейшие действия (используя синтаксис деструктурирования и распространения объекта «разделение желтка») - вызов
next()
который передает запрос следующему в очереди для запроса (наш обработчик маршрута для/events
)
[utility] создатьСигнатуруСравнения()
createComparisonSignature()
функция полезности, которая:
- создает базу подписей SHA1 HMAC, используя наш секрет (хранящийся в
.env
файл) - звонки
update()
добавить полезную нагрузку (body
) в базу подписи для формирования полной подписи - звонки
digest('hex')
на полной подписи, чтобы превратить ее в шестнадцатеричную строку для сравнения - возвращаться
sha1=SIGNATURE
чтобы соответствовать форме, которую GitHub использует в своей подписи заголовка
[utility] сравнитьПодписи()
На первый взгляд эта функция может показаться неуместной. Если две подписи являются просто строками, почему мы не можем сравнить их через signature !== comparison_signature
?
Причина в том, что сравнение строк вернет false
как только происходит несоответствие символов. Это означает, что время, необходимое для возврата ошибочного ответа, зависит от того, сколько символов совпадает, прежде чем несоответствующий вызов приведет к возврату выражения. false
.
Злоумышленники могут использовать эту информацию для выполнения выбор времени атака. Они могут изменить первую букву, затем вторую и так далее, определяя, насколько они близки, исходя из времени отклика.
С использованием crypto.timingSafeEqual()
метод защищает нас от этого вектора атаки. Метод будет выполняться за постоянное время независимо от того, где происходит несоответствие символов. Из-за этого время отклика всегда будет постоянным.
Применение промежуточного программного обеспечения
Вернуться в app.js
мы добавляем промежуточное ПО в /event
маршрут
app.use(‘/events’, verifyGithubPayload, eventsHandler);
Здесь мы говорим: для каждого запроса, который идет к /events
сначала пропустите запрос через verifyGithubPayload
тогда [next()
] отправить его в eventsHandler
обработчик маршрута.
const eventsHandler = (req, res) => {
console.log(req.event_type, req.action, req.payload);
return res.send('got authentic event data through my new middleware!');
};
module.exports = eventsHandler;
Заметки
Типы событий
Я написал скрипт для очистки документов GitHub для .json
а также .txt
файлы типов событий. Вы можете использовать их для создания белого списка принятых событий. В файле JSON есть поле описания и URL-адреса, если оно вам нужно для документации.
Вы можете найти скребок и выходы сюда.
Отладка
Вы можете просмотреть запросы событий и ответы вашего приложения в разделе
настройки → дополнительные → последние доставки.
Эта страница полезна для отладки и получения образцов полезной нагрузки для тестов.
Мониторинг полезной нагрузки приложения GitHub
— Вамп