SDL – Security Development Lifecycle и с чем его едят


Так вот, это был некий Хлюст. Давно
был. Он не слушался старших и теперь
лежит там специально для того, чтобы
показывать умным людям дорогу.
А. и Б. Стругацкие «Пикник на обочине»

Вы знаете, в чем разница для охотника между уткой и слоном? Помимо всего прочего в том, что в слона значительно легче попасть. Он – большой. И если попасть (например, чтобы заразить его каким-нибудь новым вирусом) – это все, что вы хотите, то охота на слонов куда проще чем охота на уток. Со слонами и утками это обычно не так, а вот в программировании подобная цель случается сплошь и рядом. В конце 90-х стало ясно, что Windows – это очень большая цель и с этим надо что-то делать. Так появился SDL – Security Development Lifecycle – методика разработки безопасных программ, применяемая на Microsoft уже восемь лет.

Некоторые думают, что SDL – это изобретение теоретиков. И да, надо признать, что некоторые из разработчиков этой методики имеют ученые звания в Computer Science. Но пришла она отнюдь не из теории.

Началось все с того, что Microsoft создал свою группу быстрого реагирования на проблемы с безопасностью. Своего рода эквивалент CERT – университетского проекта времен больших компьютеров в стиле IBM 360/370. Как только выявлялась проблема, группа должна была разобраться что произошло, почему, как это исправить, в чем глубинная причина и как можно быстрее выпустить исправление. Они и сейчас это делают – именно так появляются "security updates", которые периодически заставляют вас перезагружать ваши компьютеры.

По ходу дела они начали анализировать и собирать статистику, и выяснилось несколько очень интересных вещей. А именно, три.

Уроки истории

Урок Первый: Изолента не работает!

Если вы хотите сделать лодку, то все дырки в ней должны быть заделаны до того, как она отправится в плавание, а не после. А еще лучше дырок просто не делать. Поскольку даже если залепить дырку изолентой, как следует все равно не получается. Отклеивается, протекает, а то еще и заодно что-то полезное закрывает и не дает использовать. А главное, если заранее не подумать, чтобы дырок не было, то их оказывается столько, что никакой изоленты не хватит. То же самое применимо и к программным продуктам.

Причем чтобы дырок не было с самого начала, вам может придется изменить дизайн, процесс, использовать иные технологии, средства, инструменты... И обо всем этом нужно подумать заранее.

Попросту говоря, о безопасности надо думать до создания продукта, а не после. Вы можете сказать, что уж ваша-то фирма или группа думает. Ну, что на это ответить? Во-первых, на дворе 2008-ой, а не конец 90-х, а во-вторых, думать мало, надо еще и уметь. Вот для этого-то SDL и предназначена!

Урок Второй: Все то же самое, господа, все то же самое...

Оказалось, что при всем разнообразии ошибок безопасности, правило 20-80 по-прежнему работает. То есть, подавляющее большинство их все равно вылезает из очень небольшого набора исходных причин, связанных с конкретными языковыми конструкциями или стилем программирования.

К слову, мне кажется, что создатели вирусов, троянов и прочей компьютерной нечисти должны поставить памятник из чистого золота создателям языка С (и С++) за две конструкции этого языка: кончающиеся нулем строки и адресную арифметику.

Строка – это по сути кусок памяти, который кончается там, где он кончается. В общем, как огород без забора, который кончается там, где начинается минное поле. Садись на трактор и паши, пока не найдешь первую мину. А как найдешь – остановись!

Конечно, по правилам хозяин обязан поставить табличку в конце (нулевую литеру), по которой трактора и останавливаются, а что если кто эту табличку утащил горшки в печи прикрывать? Кстати, трактора, останавливающиеся у таблички – небезопасные стандартные функции из библиотеки C времен Unix’а для работы со строками – также внесли значительный вклад в количество неудачиливых огородников-саперов.

Вторая огромная проблема С и С++, адресная арифметика – это тоже великолепный перл. Вот, например, видите вы такое выражение:

pbOne = (BYTE*)plTwo + 1;

и такое выражение:

pbOne = (BYTE*)(plTwo + 1);

Как думаете, результат будет один и тот же или разный? Можно чуть усложнить, предположим plTwo == 0x00A02010, каковы будут значения pbOne?

Ответ: 0x00A02011, 0x00A02014 или 0x00A02018, а может и еще какое.

Дело в том, что адресная арифметика при сложении считает количество элементов, на который ссылается указатель. То есть в первом случае она считает что пропускает один байт, а во втором, если автор программы имел в виду long при использовании венгерской записи, длинное целое, которое на одних платформах занимает 4 байта, а на других – 8. И это при условии, что венгерская запись использовалась правильно, и мы не имеем дело с какой экзотической платформой, скажем каким-нибудь устаревшим процессором, где long – это два байта.

А теперь представьте выражение чуть посложнее да еще и без венгерской записи. Что-нибудь вроде:

One = (MyType1)(Two + (MyType2)((Three - 2) – (MyType3)(Four + 4))) + 3;

Думаете оно будет равно Two+Three-Four-3? Ага, как же... В общем, как любит говорить в своих выступлениях Майкл Говард, «Адресная арифметика – это Зло.» Вот именно, с большой буквы.

Собственно, а чем так плохи строки и адресная арифметика? Во-первых, и правда очень легко наехать на мину – попытаться залезть в кусок защищенной памяти после чего ваша программа сломается по AV – Access Violation. Однако, это еще хороший вариант. А что если память оказалась не защищена? Например, это кусок вашей собственной памяти? Тогда вы перепишете одну из ваших собственных переменных или даже код некиим случайным значением. А теперь представьте себе, что ваш код выполняется с правами системы или администратора, а то, что вы записываете было получено от обычного пользователя, например, как ввод в HTML форме. И получается, что данные от пользователя превратились в код, работающий с правами системы. Это и есть типичный сценарий ошибки безопасности на основе переполнения буфера.

Другие популярные проблемы включают в себя использование неаккуратно созданных ActiveX компонент, кросс-доменные и другие скриптовые атаки в браузерах, слабая изоляция кода, и некоторые другие. Но сухой остаток все тот же – большинство реальных проблем сводится к очень небольшому числу языковых конструкций и технологий.

Урок Третий: Никому нельзя верить!

Любые данные, которые пришли снаружи, опасны. Классический пример: предположим у вас есть веб-приложение, которое позволяет пользователю найти друзей по имени. SQL код для этого дела выглядит примерно так:

SELECT UserName FROM Users WHERE FirstName = "…"

А то, что вместо "…" - это как раз то, что ввел пользователь. Теперь, представим себе, что пользователь ввел «неважно"; DROP TABLE Users; -- » Получившийся результат выглядит так:

SELECT UserName FROM Users WHERE FirstName = "неважно"; DROP TABLE Users; --"

Если кто не в курсе, эта строка удалит таблицу ваших пользователей. И это еще не самое страшное. Представьте себе вариант, когда вместо имени введено «неважно"; SELECT User,Password FROM Users; -- »

Это необязательно сработает, поскольку зависит от кода, который превращает ответ базы данных в HTML, но если тот слепо выбрасывает все на экран...

Чего далеко ходить, на нескольких моих сайтах были так называемые формы feedback, то есть HTML формочка, используя которую посетитель сайта может послать мне email. Мне пришлось практически все эти формы убрать. Почему? Коротко, в мире полно идиотов. Более длинная версия: эти идиоты пытались эти формы сломать. Например, так:

To: test_address
Test

Идея? Если тело сообщения случайно окажется вплотную к заголовку, то это "To:" станет частью заголовка и письмо будет послано на test_address. А если это сработает, то сайт «этого тупого американца» можно использовать для рассылки спама. Разумеется на моих сайтах это не работало, но упорство товарищей из Египта, Румынии и Китая было столь сильным, что я каждый день был вынужден вручную удалять из почтового ящика десятки тестовых попыток. После чего я просто махнул рукой и убрал эти страницы.

Вообще, я опасаюсь, что деятельность хакеров с высокой степенью вероятности приведет просто к исчезновению многих бесплатных сервисов на Интернете. По неофициальным данным (не могу ручаться за эти данные, я услышал эту цифру от представителя одной вполне успешной на Интернете фирмы) более половины «пользователей» FaceBook – роботы. Они мило общаются друг с другом, накапливают репутацию, а когда надо хозяину – дружно ляпают какую-нибудь «новость», которая обрушивает какие-нибудь акции или реноме политика.

Впрочем, это уже не проблема SDL и безопасного ПО. Так что, вернемся к разработке надежного ПО.

Обзор SDL

SDL обычно представляется как дополнение-модификация классической модели «водопада» – «waterfall». Тем не менее, приложив ум, ее достаточно легко модифицировать и для практически любой другой модели, включая Scrum или WhiteWater. Так что я изложу ее в классической форме, а дополнительно прокомментирую, что происходит в других случаях.

Фаза требований – requirements

Классической первой фазой модели «водопада» является фаза определения требований к продукту и планировния. В рамках SDL вы определяете дополнительные требования к продукту, которые должны гарантировать его безопасность.

Примеры таких требований:

  • Каждый член команды должен пройти какой-нибудь курс по безопасности ПО в последний год. Кстати, это реальное требование к выпуску продуктов в Microsoft.
  • Весь код должен проходить проверку автоматическими средствами анализа кода после фильтров правил установленных менеджментом.
  • Тест должен провести проверку на поведение системы со случайным вводом ("fuzzing").
  • Продукт должен пройти тестирование на предмет атак ("penetration testing").
  • Продукт должен пройти ревью отдельной командой (внешней или другой командой фирмы, не связанной с командой, производящей продукт)

Да, в случае Scrum или WhiteWater весьма маловерятно, что вы сможете проводить penetration testing каждый 2-3 месяца, поскольку это очень дорогое удовольствие. Но вы можете запланировать его скажем раз в год или два.

По моему личному опыту, огромная ценность установления этих требований в самом начале состоит в том, что менеджмент может планировать все эти виды деятельности и не финансировать их «по остаточному принципу». Когда время – деньги, и людей не хватает, всегда возникает соблазн «срезать угол». Включение подобных требований в официальный критерий релиза предотвращает экономию на безопасности, даже когда менеджмент испытывает очень большой соблазн так поступить.

С другой стороны, еще больше помогает, когда продукт для релиза должен быть одобрен независимой командой, единственный критерий которой – это безопасность продукта. Кстати, именно так продукты в Microsoft и выпускаются. Есть специальная команда, именуемая Secure Windows Initiative, которая следит за тем, чтобы выпускаемые продукты удовлетворяли требованиям безопасности, и без ее разрешения ни один продукт не может быть выпущен.

Фаза дизайна

Это фаза, когда вы определяете архитектуру безопасности вашего продукта, принципы, которым вы должны следовать (изоляция компонент, «слойная» структура, минимальные права, и т.д.) Не все из этого можно использовать в любом проекте, но наличие такого решения заранее помогает потом аргументировать правильные решения, когда очень хочется «срезать угол».

Опять же, определение подобных принципов работает как в модели водопада, так и в большинстве новых методик разработки ПО. В конце концов, если у вас десять фаз изменений дизайна, вы по-прежнему можете следовать принципам, положенным в начале проекта.

Фаза дизайна, а точнее ее самый конец – это также время для так называемого моделирования угроз ("threat modeling"). Официально SDL требует ее на фазе дизайна, что вызывает естественную критику – как мы можем моделировать угрозы тому, что мы еще даже не придумали? Однако, когда вы это все-таки придумали, полезно подумать также и о том, как это могут сломать. В идеале, об этом следует думать именно тогда, когда вы придумываете, что собираетесь делать, но практически это обычно не выходит. Поэтому очень полезно сделать это хотя бы сразу после и до того как написана первая строчка кода.

Фаза реализации

Наиболее важные элементы в фазе реализации связаны с проверкой создаваемого кода различными способами. Эти способы включают в себя:

  • Стандарты кодирования и тестирования. Например, запрет на использование strcpy() и использование ее безопасного эквивалента взамен, или аннотация всех API используя SAL, который помогает автоматической верификации кода.
  • Использование средств анализа кода, например, FxCop, который вы можете загрузить бесплатно с MSDN. Да, часто такие средства поднимают панику и по-прежнему у них очень много ложных сигналов, но когда вы привыкнете их использовать, вы обнаружите, что они действительно находят и реальные проблемы в коде, вроде разыменований NULL указателя.
  • Использование специальных средств тестирования, в особенности «fuzzing» средств, который скармливают случайные данные в ваши API и сетевые каналы. Поначалу кажется, что они не способны дать ничего интересного, но я лично видел как грамотно использованные fuzzing tools выявили вполне резонные и серьезные баги, которые благодаря этому удалось исправить до выпуска продукта.
  • И, наконец, инспекции кода (code reviews)! Да-да, любая нормальная команда и так это должна делать, но SDL об этом говорит специально.

Фаза верификации

На этой фазе производится так называемый «security push». Он включает в себя бета-тестирование, полезное не только ради безопасности, специальные инспеции кода на безопасность, специальные виды тестирования вроде penetration testing (тестирование на потенциальные атаки) и расширенное fuzz testing (тестирование на плохо сформированные данные).

Говоря об инспекциях кода, повторюсь, любая нормальная команда должна делать их перед тем как отправлять его в систему контроля версий, но тут речь идет о несколько другом виде инспекций кода. Я говорю, об инспекции кода спецально для выявления проблем с безопасностью – в первую очередь потенциальные переполнения буферов (уже упомянутые С строки и адресная арифметика), использование неочищенного пользовательского ввода (в SQL, в запросах в WMI, в именах файлов...), передача прав туда, где они излишни, просто способность внешнего пользователя завесить или сломать систему (например, сможет ваш социальный сайт загрузить фото в 5 гигабайт и преобразовать его во внутренний формат?)

Особенно важны инспекции так называемого «высокоприоритетного кода», кода имеющего дело с «поверхностью атаки». Например, если данные пользователя разбираются специальным парсером, то код парсера требует особого внимания на предмет возможных проблем, поскольку вы не хотите, чтобы магическая комбинация в HTML форме могла отключить электростанцию или там стереть базу данных.

Тут же вылезают и так называемые «гиблеты» – куски кода и библиотеки, сделанные не вашей командой, которые вам нужны для функциональности и которые вам приходится упаковывать вместе со своим продуктом. Типичным «гиблетом» является, например, бесплатная версия XML парсера MSXML, который, впрочем, является достаточно безопасной компонентой. Куда хуже дела обстоят, когда у вас в коде используется библиотека, созданная другой фирмой, которая за это время уже успела выйти из бизнеса. В Microsoft в таком случае дело скорее всего закончилось бы заменой компоненты, но очевидно, что не столь крупная фирма может оказаться просто не в состоянии заменить функциональность.

Главный вопрос насчет «гиблетов» состоит в том, а что делать, если в нем обнаружится ошибка? Представьте себе, что вы выпустили продукт и этот самый «гиблет» у вас на фирменном CD. Через месяц в нем обнаруживается баг безопасности. Что делать? В случае крупных продуктов Microsoft всегда есть Windows, Microsoft и Office Update, через который это можно сделать автоматически. Тот же MSXML в случае чего может быть обновлен на всех компьютерах в мире, установленных на автоматический Windows Update, довольно быстро. А если ваш продукт не имеет подобной системы?

Упоминая MSXML, хочется также рассказать о другой форме чужого кода в вашем продукте. Когда в 2001-м году мы делали «security push» для MSXML 3.0, мы столкнулись с проблемой. Для поддержки серверного XMLHTTP обьекта нам пришлось заимствовать код от команды, отвечавшей за сетевой интерфейс в Windows, и соответственно в команде просто не было людей, знавших в деталях, что же этот код делает и способных провести его грамотную инспекцию. Код этот не был использован как бинарный файл, а был включен на уровне исходников, что облегчало задачу инспекции, но все равно над проблемой пришлось попотеть, поскольку пропускать его без проверки было просто нельзя.

В общем, подумайте и об этом. Microsoft обычно избегает использования чужого кода, даже с полным лицензированием, но у других фирм это случается сплошь и рядом. Чтобы далеко не ходить – веб сервер от IBM используемый в их WebSphere продуктах – это модифицированный Apache. И если такой код откроет возможность атаки, то пострадает ваш продукт и ваши пользователи.

Фаза выпуска

Фаза выпуска описана в SDL в основном через деятельность в ходе FSR – Final Security Review. Это процесс в котором отдельностоящая команда проверяет выполнили ли вы требования по безопасности, обязательные для всех продуктов, выпускаемых фирмой, равно как и ваши собственные требования по безопасности, установленные в фазе требований.

Фаза поддержки

Вы думали с выпуском все закончено? Отнюдь. Что произойдет, если в вашем продукте обнаружится критический баг?

К этому надо быть готовым. «Быть готовым» означает иметь список людей и их телефонов, которых, если понадобится, придется разбудить среди ночи. Это означает план, что делать. Это означает план как доставить пользователям исправленный вариант. В общем, мало выпустить продукт – надо быть готовым его сопровождать.

Немаловажным элементом реакции на проблемы также является способность научиться на ошибках и добавить то, чему научились, в процесс или те же самые инструменты анализа кода, чтобы такое более не повторялось.

Подробнее и в деталях

Некоторые элементы SDL часто оказываются сложными для быстрого освоения. Так что, давайте их рассмотрим хотя бы один из них – очень важный – чуть лучше.

Модель угроз – Threat Modeling

Каждый раз, когда я проводил threat modeling, одной из самых сложных задач было обьяснить, что это такое. Когда человек слышит эту фразу, «модель угроз», он сразу начинает думать о реальных угрозах. Скажем, вы говорите, а может пользователь добраться до файловой системы веб-сервера? И в ответ тут же раздается возмущенный вопль: «Это не угроза! Он не может!!!»

Позвольте обьяснить. «Угроза» – это не то, что злоумышленник может. Если он может, то вы прошляпили баг. Угроза – это то, что злоумышленник хочет. Даже если и не может.

«Модель угроз» делается по принципу, который я называю 3A – Assets, Actors, Actions. Активы, Актеры, Действия.

Активы – это то, что вы защищаете, то, доступ к чему должен быть закрыт или ограничен. Это файловые системы используемых в системе машин, регистрационные файлы, сервисы, ресурсы, и наконец, собственно сервис вашего продукта, который тоже может быть структуирован – одно дело данные базы данных, другое дело – ее настройка.

Актеры – это те, кто могут иметь доступ к системе. Администраторы, пользователи, гости-анонимные пользователи... Опять же, в зависимости от вашего продукта, у вас может быть значительно больше категорий. Например, оператор бекапов, оператор ввода форм...

Действия – это то, что актер может (или хочет) сделать с активом. Создать. Прочитать. Изменить. Удалить. Использовать. Не все действия применимы ко всем активам, но те, что возможны, нужно учесть.

Дальше вы делаете эвклидово произведение этих трех компонент – то есть попросту все возможные сочетания троек актив-актер-действие. Сочетания актив-действия, которые не имеют смысла (скажем, «принтер – прочитать») можно выкинуть сразу.

Следующим шагом у каждой тройки пишется либо «разрешено», либо «предотвращено тем-то и тем-то», либо большой восклицательный знак. Скажем, «пользователь – список товаров – прочитать» - это «разрешено», поскольку для того список товаров и создавался, чтобы показывать пользователю. А на «пользователь – жесткий диск веб сервера – записать» пишется «предотвращено тем, что наш asp.net код не пишет на диск сервера.»

К любой угрозе, кроме тех, что разрешены, следует также приписать, что же это за угроза. Для классификации угроз в Microsoft используют так называемую STRIDE model по первым буквам пяти основных категорий угроз:

  • S – Spoofing of Identity – это возможность притвориться тем, кем ты не являешься. Скажем, Степа не должен иметь возможности притвориться Вовой на социальном сайте и от его имени начать изьясняться в любви Маше. А уж если у Вовы и Степы разные права на сайте, то дело тут же переходит к последнему классу угроз в STRIDE – элевация прав.
  • T – Tampering with Data – возможность изменить какие-то данные, которые вы не имеете права менять, будь то ваш счет в банке или запись в вашей кредитной истории. Или не в вашей.
  • R – Repudiation – «я – не я, лошадь не моя» – это типичный пример repudiation, когда тот, кто сделал действие, отрицает, что он его сделал. Эта угроза очень важна, например, в системах финансовых расчетов, чтобы пользователь отдавший команду отправить деньги не мог потом утверждать, что он никогда этого не делал.
  • I – Information Disclosure – раскрытие информации. Ну, тут и так все ясно. Работник не должен иметь доступ к зарплате своих коллег, новобранец не должен быть в курсе планов Генштаба, а человек, присевший за ваш компьютер, не должен быть способен прочитать состояние вашего счета в банке.
  • D – Denial of Service – отказ в сервисе. Например, относительно недавние распределенные атаки на некоторые сайты являются типичным примером Denial of Service. К слову, это одна из наиболее сложных для противодействия угроз. В конечном счете, если ваш сервис открыт через Интернет, полностью эту угрозу нейтрализовать просто невозможно. Но по крайней мере можно сильно снизить ее шансы путем правильной настройки серверов, брандмауэров и прочих элементов вашей системы.
  • E – Elevation of Privilege – элевация прав. Это очень близко к Spoofing, поскольку понятно, что если пользователь может притвориться админом, то он сможет сделать то, что ему не положено. Но это не только spoofing, а вообще возможность получить права, которые тебе не положены. Бытовой пример, когда elevation of privilege происходит без spoofing, это когда ребенок просит мать на минуту присесть за компьютер, и пользуясь тем, что он под ее логином, присваивает себе права админа.

Такая классификация помогает сразу видеть, чем та или иная угроза реально грозит и насколько она серьезна. Например, любой сервер на Интернете может быть доведен до отказа в сервисе в результате распределенной атаки, но если сам сервер при этом не падает, то ничего страшного в этом в общем-то обычно нет.

А вот те, что с большим восклицательным знаком и надо внимательно просмотреть и либо понять, почему это невозможно, либо что-то сделать, чтобы это стало невозможно.

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

Итоги

Ну, хорошо, скажет пытливый читатель. А толку то? Стоит ради чего огород городить? Стоит. Вот лишь несколько цифр:

Количество security bulletins в течении года после выпуска продукта (данные Microsoft):

  • Windows 2000 (до SDL): 62
  • Windows Server 2003 (с SDL): 24

Количество Secunia advisories (бюллетени независимой фирмы Secunia, специализирующейся на безопасности программных продуктов):

  • IIS 5 (до SDL): 12
  • IIS 6 (первый выпуск с SDL): 5
  • IIS 7 (с SDL): 1

Для сравнения, Apache 2.0 за тот же период (2003-2008) – 37.

Так что SDL работает.

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

Ссылки

Security Development Lifecycle (статья)
MSDN Security Developer Center (сайт)
Threat Modeling (список ресурсов)
Threat Modeling Blog by Microsoft ACE (Application Consulting and Engineering) team
STRIDE Threat Model (статья)
FxCop (загрузка)
Secunia бюллетени: IIS 5, IIS 6, IIS 7.
Writing Secure Code, Second Edition by Michael Howard – Microsoft Press, 2003, 798 p., ISBN 0735617228
Improving Web Application Security: Threats and Countermeasures by J.D. Meier, Alex Mackman, Michael Dunner, Srinath Vasireddy, Ray Escamilla and Anandha Murukan (онлайн книга)

Дополнительно

iXBT BRAND 2016

«iXBT Brand 2016» — Выбор читателей в номинации «Процессоры (CPU)»:
Подробнее с условиями участия в розыгрыше можно ознакомиться здесь. Текущие результаты опроса доступны тут.

Нашли ошибку на сайте? Выделите текст и нажмите Shift+Enter

Код для блога бета

Выделите HTML-код в поле, скопируйте его в буфер и вставьте в свой блог.