Когда я работал в PKC, моя команда вела около тридцати аудитов кода. Многие из них предназначались для стартапов, которые вышли на серию А или B – именно на этом этапе основатели обычно обзаводились деньгами, отвлекались от тотальной сосредоточенности на выходе на рынок и осознавали, что неплохо бы поплотнее заняться вопросами безопасности.

Работа была очень интересной: мы глубоко закапывались в проекты с самыми разнообразными стэками и архитектурами и из разных областей программирования. Мы находили проблемы безопасности всевозможных видов, от катастрофических до просто занятных. Кроме того, у нас была возможность побеседовать с разработчиками на высоких должностях и техническими директорами на более общие темы – например, с какими техническими и прочими сложностями они столкнулись в самый первый период роста.

Было и еще кое-что занимательное: со времен первых аудитов успело пройти семь-восемь лет, и у нас появилась возможность увидеть, какие из проектов добились успеха, а какие канули. Я бы хотел поделиться некоторыми неожиданными выводами, которые сделал из этих своих наблюдений. Начну с самых общих вещей, а затем буду переходить к тому, что касается конкретно безопасности.

1. Для создания отличного продукта сотни разработчиков не требуется. Я об этом уже писал подробнее, но, если повторить самую суть: хотя стартапы, которые мы анализировали, были примерно на одной ступени развития, размеры команд варьировали очень сильно. На удивление, случалось, что самые впечатляющие продукты с широким набором возможностей оказывались детищем небольших коллективов. И те же «малые да удалые» команды годы спустя держали рынок в кулаке.

2. Простота дает лучший результат, чем заумь. Как самопровозглашенному снобу, мне нелегко это говорить, но факт остается фактом: из всех рассмотренных нами стартапов лучше всего дела идут у тех, кто решительно придерживался принципа KISS в разработке. Умничанье ради умничанья в этих командах отвергалось. С другой стороны, проекты, о которых мы говорили «ух ты, как хитро сделано-то», по большей части ушли в небытие. Если обобщать, самыми распространенными способами выстрелить себе в ногу (об этих способах я рассказывал в другой статье) оказались слишком ранний переход на микросервисы, архитектуры, опирающиеся на распределенные вычисления, и дизайны с упором на сообщения.

3. Самые значимые результаты приносили первые и последние часы аудита. Если подумать, то в этом есть логика. В первые часы аудита выбираешь всё, что лежит на поверхности. То, что прямо-таки мозолит глаза, обнаруживается просто при помощи утилиты grep и базового тестирования функциональности. Ну а к последним часам ты уже полностью погружен в контекст и начинают приходить озарения.

4. За последние десять лет защитить своё ПО стало намного проще. У меня нет надежных статистических выкладок, которые могли бы это подтвердить, но мне кажется, что в коде, написанном до 2012 года, число уязвимостей на строку было гораздо выше, чем в коде, написанном позже (аудиты мы начали проводить в 2014 году). Возможно, дело во фреймворках Web 2.0, или же программисты просто стали серьезнее относиться к вопросам безопасности. Как бы то ни было, на мой взгляд, это означает, что с безопасностью в самом деле стало лучше в том, что касается инструментов и исходного уровня, которые сейчас доступны разработчикам.

5. Самые опасные уязвимости всегда бросаются в глаза. Примерно в пятой части проведенных аудитов нам попадался Крупный Просчет – уязвимость настолько серьезная, что мы сразу же звонили клиенту и говорили устранить ее как можно быстрее. Не помню ни одного случая, чтобы эта уязвимость оказалась чем-то хитроумным. По сути, их банальность отчасти и делала угрозу серьезной – нас беспокоило как раз то, что их очень легко обнаружить и использовать в своих целях. «Вероятность обнаружения» уже давно входит в метрики анализа воздействия, так что мысль сама по себе не новая. Но всё-таки мне кажется, что этому фактору уделяется недостаточно внимания. Когда речь идет о реальных атаках, вероятность обнаружения решает очень многое. Хакеры – народ ленивый, они ищут проторенные дорожки. Они не станут морочиться с распылением кучи, пусть даже в работе с памятью допущены очень грубые ошибки, если есть возможность задать новый пользовательский пароль, потому что соответствующий токен был в ответе на запрос (компания Uber имела возможность в этом убедиться в 2016 году). Тут можно возразить, что, если упирать на вероятность обнаружения, выйдешь на принцип «безопасность через неясность», который ставит во главу угла догадки о том, что может или должно быть известно хакеру. Однако повторюсь: у меня лично практика показывает, что на деле вероятность обнаружения напрямую соотносится с вероятностью эксплойта.

6. Безопасность по умолчанию во фреймворках и инфраструктуре существенно улучшила защиту ПО. Об этом я тоже уже писал, но если вкратце: такие вещи, как автоматический escaping для любого HTML в React во избежание межсайтового скриптинга или бессерверные стэки, задающие конфигурацию операционной системы и веб-сервера вместо разработчика, радикальным образом укрепили кибербезопасность в компаниях, которые пользуются этими возможностями. Если сравнивать с нашими аудитами проектов на PHP, то в последних XSS просто кишмя кишит. Нельзя сказать, что новые стэки и фреймворки – непроходимая крепость, но доступных для атак точек у них меньше как раз в тех областях, которые имеют значение на практике.

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

8. На погружение в лабиринт уязвимостей в библиотеках зависимостей можно хоть весь аудит потратить. Невероятно трудно установить, создает ли некая уязвимость в зависимости угрозу атаки. Можно с уверенностью сказать, что наша индустрия слишком мало вкладывает в обеспечение безопасности фундаментальных библиотек, именно поэтому Log4j и подобное имели такое большое значение. Node и npm в этом отношении просто ужас какой-то – в их цепочках зависимостей черт ногу сломит. Когда GitHub выпустил dependabot, это стало прямо-таки подарком судьбы. Теперь мы по большей части можем просто рекомендовать клиентам проводить апгрейд в порядке приоритетности.

9. Никогда не проводите десериализацию ненадежных данных. Чаще всего такое случается в PHP – по какой-то причине разработчики на PHP обожают проводить сериализацию и десериализацию вместо того, чтобы использовать JSON. Я бы сказал, что практически каждый случай на нашей памяти, когда сервер проводил десериализацию и парсинг клиентского объекта, приводил к жуткому эксплойту. Для тех, кто еще не видел, у Portswigger был хороший разбор, что может пойти не так в этих сценариях (кстати, на примере PHP – совпадение?). Если вкратце, общий мотив всех уязвимостей, связанных с десериализацией, состоит в следующем: когда пользователь получает возможность управлять объектом, который впоследствии будет использоваться сервером, это создает очень мощные возможности с обширной площадью. Концептуально это близко к загрязнению прототипа и генерации шаблонов HTML пользователем. Как это исправить? Куда лучше позволять пользователю отправлять объекты JSON (они очень ограничены в возможных типах данных) и вручную конструировать объект исходя из того, какие в нем представлены поля. Работы чуть больше, но оно того однозначно стоит.

10. Недочеты на уровне бизнес-логики встречались редко, но метко. Если так подумать, недочеты в бизнес-логике определенно будут влиять на бизнес. Здесь есть интересное следствие: даже если протокол у вас составлялся с расчетом на обеспечение доказуемой безопасности, человеческий фактор в виде ущербной бизнес-логики дает о себе знать на удивление часто (достаточно вспомнить серию эксплойтов с катастрофическими последствиями, случившихся по причине плохо написанных смарт-контрактов).

11. Кастомный фаззинг показал себя неожиданно эффективным. Позанимавшись пару лет аудитами кода, я стал настаивать на том, чтобы все аудиты включали в себя изготовление особых фаззеров для тестирования API продукта, аутентификации и так далее. Это довольно распространенная практика, лично я украл идею у Томаса Птацека – он мельком упоминает о ней в своей статье о наборе персонала. Пока мы не стали сами этим заниматься, я был склонен считать фаззинг пустой тратой времени. Мне представлялось, что это пример того, как ресурс разработки пускают не туда, и лучше бы эти часы тратились на чтение кода и проверку различных гипотез. Но вопреки моим ожиданиям фаззинг оказался эффективным и выгодным вложением рабочего времени, особенно когда работа велась над крупными кодовыми базами.

12. Слияния компаний существенно усложняли работу с безопасностью. Больше паттернов программирования для изучения, больше аккаунтов на AWS для просмотра, больше разнообразия в инструментах SDLC. Ну и само собой, слияния часто предполагали появление совершенно нового языка и/или фреймворка со своими собственными действующими паттернами.

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

14. Общий профессиональный уровень компании, как правило, коррелировал с оперативностью устранения уязвимостей. Самые лучшие наши клиенты обычно просили просто сообщать им обо всём, что удалось найти, в режиме реального времени, чтобы они могли решать проблемы сразу же.

15. С токенами JWT и вебхуками почти никто не справлялся с первой попытки. Если говорить о вебхуках, люди в большинстве случаев забывали проводить аутентификацию входящих запросов (либо сервис, которым они пользовались, этого не позволял… довольно странная ситуация, если так подумать). Этот тип проблем привел к тому, что Джош, один из наших сотрудников, стал задавать клиентам серию вопросов и в итоге подготовил выступление на DefCON/Blackhat. То, что с JWT сложно работать, даже если используешь библиотеку, ни для кого не секрет. Нам попадалось много имплементаций, где при выходе не происходило корректного истечения срока действия токена, аутентификация JWT проводилась неверно или просто не проводилась по умолчанию.

16. MD5 до сих пор активно используется, но по большей части, это ложные положительные результаты. Как выясняется, MD5 применяется во многих других целях помимо (недостаточно) устойчивого к коллизиям хэширования паролей. Например, благодаря высокой скорости, его часто используют в автоматическом тестировании, чтобы по-быстрому сгенерировать целую кучу псевдо-рандомных GUID-ов. В таких случаях ненадежные свойства MD5 не играют никакой роли, что бы там не кричал вам инструмент статистического анализа кода.

Любопытно, отмечал ли кто-то из вас те же закономерности? Или может быть, какие-то другие? Если с чем-то не согласны, тоже пишите в комментарии!