Разработка
Уроки 5 лет аудита кода стартапов
Я хочу поделиться некоторыми из самых удивительных вещей, которые я усвоил в этих наблюдениях.
Когда я работал в PKC, наша команда провела более двадцати аудитов кода, многие для стартапов Серии A или B (обычно это было, когда у них были деньги, и они понимали, что было бы неплохо погрузиться глубже в их безопасность, уже после того, как они достигли product market fit).
Это была увлекательная работа — мы глубоко погружались в разнообразие стеков и архитектур в самых разных областях. Мы обнаруживали всевозможные проблемы с безопасностью, от катастрофических до просто интересных. У нас также была возможность пообщаться со старшим инженерным руководством и техническими директорами в целом о проблемах проектирования и продуктов, с которыми они столкнулись, когда они только начинали масштабироваться.
Также было интересно увидеть, какие из этих стартапов преуспели, а какие угасли теперь, когда некоторым из этих аудитов исполнилось 7 или 8 лет.
Я хочу поделиться некоторыми из самых удивительных вещей, которые я усвоил в этих наблюдениях, примерно в порядке от наиболее общего к наиболее специфичному для безопасности.
1. Вам не нужны сотни инженеров, чтобы создать отличный продукт
Я написал об этом более длинную статью, но, по сути, несмотря на то, что аудит проходили стартапы примерно на одной стадии развития, размеры инженерных команд сильно различались. Удивительно, но иногда самые впечатляющие продукты с широчайшим набором функций создавались небольшими командами. И именно такие «маленькие, но могущественные» команды спустя годы сокрушали свои рынки.
2. Простой обгоняет умного
Как самопровозглашенной элите мне больно говорить об этом, но это правда: проверенные нами стартапы, которые сейчас добились наилучших результатов, обычно имели почти наглый подход к проектированию — «Не усложняй». Заумство ради заумства было им ненавистно. С другой стороны, компании, в которых мы говорили «Вау, эти ребята чертовски умны», по большей части исчезли. Как правило, основная проблема (о которой я рассказывал подробнее в предыдущем посте) заключалась в преждевременном переходе к микросервисам, архитектурам, основанным на распределенных вычислениях, и проектам с большим объемом обмена сообщениями.
3. Наши наиболее важные выводы всегда приходили в течение первых и последних нескольких часов аудита
Если подумать, в этом есть смысл: в первые несколько часов аудита вы находите самые «низко висящие» плоды. Вещи, которые просто вылезают, как больной палец, в коде и тестировании некоторых базовых функций. За последние несколько часов вы полностью освоились с новой кодовой базой, и вещи начинают складываться.
4. За последние 10 лет писать безопасное программное обеспечение стало значительно проще
У меня нет статистически достоверных доказательств, подтверждающих это, но похоже, что код, написанный примерно до 2012 года, имеет гораздо больше уязвимостей на строчку, чем код, написанный после 2012 года (мы начали проводить аудит в 2014 году). Возможно, это фреймворки Web 2.0 или повышенная осведомленность разработчиков о безопасности. Что бы это ни было, я думаю, это означает, что безопасность действительно улучшилась на фундаментальной основе с точки зрения инструментов и стандартных настроек, которые теперь доступны инженерам-программистам.
5. Все действительно серьезные уязвимости безопасности были очевидны
Вероятно, в пятой части аудитов кода, которые мы проводили, мы находили The Big One — уязвимость настолько серьезную, что мы звонили нашим клиентам и просили их немедленно исправить ее. Я не могу вспомнить ни одного случая, когда эта уязвимость была бы очень умной. На самом деле, это часть того, что делало худшие уязвимости худшими — мы беспокоились в первую очередь потому, что их было бы легко найти и использовать. «Обнаруживаемость» уже некоторое время является компонентом анализа воздействия, так что это не ново. Но я думаю, что она должна иметь гораздо больший вес. Обнаруживаемость — это всё, когда дело доходит до фактического воздействия. Хакеры ленивы и ищут самый простой ход. Они не будут заботиться об обнаружении очень серьезной heap-spray уязвимости, если могут сбросить пароль пользователя, потому что токен сброса присутствует в самом ответе (как Uber выяснил примерно в 2016 году). Контраргументом этого является то, что большое значение обнаруживаемости возводит на пьедестал «безопасность через неясность», поскольку она в значительной степени зависит от предположения, что злоумышленник не может или не должен чего-то знать. Но опять же, личный опыт убедительно свидетельствует о том, что на практике возможность обнаружения является важным предиктором реальной эксплуатации.
6. Функции безопасности по умолчанию в платформах и инфраструктуре значительно повысили безопасность
Я также написал об этом более длинную статью, но, по сути, такие вещи, как React, который по умолчанию экранирует весь HTML, чтобы избежать межсайтового скриптинга, и бессерверные стеки, убирающие конфигурацию операционной системы и веб-сервера из рук разработчиков, значительно улучшили безопасность компаний, которые их использовали. Сравните это с нашими аудитами PHP, которые были пронизаны XSS. Эти новые стеки/фреймворки не являются непроницаемыми, но их доступная для атаки площадь меньше именно в тех местах, которые имеют огромное значение на практике.
7. Монорепозитории легче проверять
Если говорить с точки зрения эргономики исследователя безопасности, то было проще проводить аудит монорепозитория, чем серии сервисов, разделенных на разные кодовые базы. Нет необходимости писать сценарии-оболочки для различных инструментов, которые у нас есть. Легче определить, использовался ли данный фрагмент кода где-либо еще. И самое главное, не нужно беспокоиться о том, что версия общей библиотеки отличается в другом репозитории.
8. Вы легко можете потратить весь аудит на поиск уязвимых зависимостей
Невероятно сложно сказать, можно ли использовать данную уязвимость в зависимости. Мы, как отрасль, определенно недостаточно инвестируем в защиту базовых библиотек, поэтому такие вещи, как Log4j, были настолько эффективными. Node и npm в этом отношении были ужасны — цепочки зависимостей просто невозможно было проверить. Когда GitHub выпустил dependabot, это было огромным благом, потому что по большей части мы могли просто сказать нашим клиентам, чтобы они обновляли вещи в порядке приоритета.
9. Никогда не десериализуйте ненадежные данные
Больше всего это относится к PHP, потому что по какой-то причине PHP-разработчики любят сериализовать/десериализовать объекты вместо использования JSON, но я бы сказал, что почти каждый случай, когда сервер десериализовал клиентский объект и анализировал его, приводил к ужасной эксплуатируемой ошибке. Для тех из вас, кто не знаком, у Portswigger есть хорошее описание того, что может пойти не так (кстати, с акцентом на PHP, совпадение?). Короче говоря, общая черта всех уязвимостей десериализации заключается в том, что предоставление пользователю возможности манипулировать объектом, который впоследствии используется сервером, является чрезвычайно мощной возможностью с широкой областью действия. Концептуально это похоже как на prototype pollution, так и на HTML-шаблоны, созданные пользователями. Исправление? Гораздо лучше разрешить пользователю отправлять объект JSON (у него так мало возможных типов данных) и вручную создавать объект на основе полей в этом объекте. Это немного больше работы, но оно того стоит!
10. Ошибки в бизнес-логике были редкостью, но когда мы их находили, они оказывались эпическими. Подумайте об этом — огрехи в бизнес-логике гарантированно влияют на бизнес. Интересным следствием является то, что даже если ваш протокол создан для предоставления доказуемо безопасных свойств, человеческая ошибка в виде плохой бизнес-логики на удивление распространена (посмотрите на серию абсолютно разрушительных эксплойтов, которые используют в своих интересах плохо написанные смарт-контракты).
11. Кастомный фаззинг оказался на удивление эффективным
Спустя пару лет аудита нашего кода я начал требовать, чтобы все наши аудиты кода включали создание собственных фаззеров для тестирования API продуктов, аутентификации и т.д. Это довольно часто делается, и я украл эту идею у Томаса Птачека, на которую он намекает в его Hiring Post. До того, как мы это сделали, я на самом деле думал, что это пустая трата времени — я просто всегда считал, что это пример неправильного проектирования, и что часы аудита лучше потратить на чтение кода и проверку различных гипотез. Но оказалось, что фаззинг был на удивление эффективным и действенным с точки зрения затраченных часов, особенно на больших кодовых базах.
12. Покупки стартапов немного усложняли безопасность
Нужно было проверить больше шаблонов кода, больше учетных записей AWS, больше разнообразных SDLC-инструментов. И, конечно же, обычно приобретение означало совершенно новый язык и/или фреймворк с собственными используемыми шаблонами.
13. Среди инженеров-программистов всегда был хотя бы один тайный энтузиаст безопасности
Всегда было удивительно, кто это был, ведь он почти никогда не знал, что это он! По мере того, как наборы навыков безопасности становятся все более искаженными, здесь возникает огромный арбитраж, если этих людей можно надежно идентифицировать.
14. Быстрые действия по устранению уязвимостей обычно коррелируют с общим техническим операционным превосходством
В лучшем случае клиенты просили нас просто постоянно давать им ленту всего, что мы находили, и они сразу же это исправляли.
15. Практически никто не работа с JWT-токенами и вебхуками правильно
С вебхуками люди почти всегда забывали аутентифицировать входящие запросы (или сервис, который они использовали, не позволял аутентификацию… что довольно плохо!). Этот класс проблем привел к тому, что Джош, один из наших исследователей, провел серию изыскания, и потом выступил на DefCON/Blackhat. Общеизвестно, что JWT сложно сделать правильно, даже если вы используете библиотеку, и было много реализаций, которые не могли должным образом деактивировать токены при выходе из системы, неправильно проверяли подлинность JWT или просто доверяли им по умолчанию.
16. Все еще используется много MD5, но в основном это ложные срабатывания
Оказывается, MD5 используется для многих других вещей, помимо (не)достаточно устойчивого к коллизиям хэширования паролей. Например, поскольку он такой быстрый, его часто используют в автоматизированном тестировании для быстрого создания множества псевдослучайных идентификаторов GUID. В этих случаях небезопасные свойства MD5 не имеют значения, несмотря на то, что ваш инструмент статического анализа может ругаться на вас.