Как вы можете обеспечить безопасное кодирование с помощью Test Driven Development? - PullRequest
21 голосов
/ 21 марта 2011

Я быстро набираю обороты по последней тенденции, которая называется Test Driven Development (TDD). Большая часть разработки, которую я делаю, происходит на C или C ++. Меня поражает, что существует очень очевидный конфликт между общими практиками TDD и общими методами безопасного кодирования. По сути, TDD говорит вам, что вы не должны писать новый код для чего-то, для чего у вас нет провального теста. Для меня это означает, что я не должен писать безопасный код, если у меня нет модульных тестов, чтобы проверить, является ли мой код безопасным.

Это поднимает две проблемы:

  1. Как эффективно написать модульные тесты для проверки переполнения буфера, переполнения стека, переполнения кучи, ошибок индекса массива, ошибок форматирования строк, несоответствий размера строки ANSI и Unicode против MBCS, безопасной обработки строк (от Howard и LeBlanc "Написание безопасного кода")?

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

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

Я ищу любые прямые ответы на вышеуказанные вопросы, любое исследование, которое относится к этому (я уже искал и не нашел много), или любое место, где живет гуру TDD, чтобы я мог постучаться в их дверь ( виртуально) и посмотреть, есть ли у них хорошие ответы.

Спасибо!

EDIT:

Появилась тема Fuzzing, которая, я думаю, является отличным подходом к этой проблеме (в целом). Возникают вопросы: подходит ли Fuzzing к TDD? Где в процессе TDD подходит фаззинг?

Параметризованное модульное тестирование (возможно, автоматизированное) также пришло мне в голову. Это может быть способом получения размытых результатов ранее в процессе тестирования. Я не уверен точно, куда это вписывается в TDD.

РЕДАКТИРОВАТЬ 2:

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

Это хорошо известная проблема в безопасности программного обеспечения: если вы защищаетесь от 5 сценариев атаки, злоумышленник просто ищет и использует 6-ю атаку. Это очень сложная игра в кошки-мышки. Дает ли нам TDD какое-либо преимущество против этого?

Ответы [ 5 ]

10 голосов
/ 27 марта 2011

Да, TDD - это инструмент / метод, который может помочь обеспечить безопасное кодирование.

Но, как и во всех вещах в этой отрасли: представьте, что это серебряная пуля, и вы выстрелите себе в ногу.

Неизвестные угрозы

Как вы указали в правке 2: «вы защищаетесь от 5 сценариев атаки, атакующий просто ищет и использует 6-ю атаку». TDD не собирается защитить вас от неизвестных угроз. По своей природе вы должны знать, что вы хотите проверить, чтобы написать тест в первую очередь.

Итак, предположим, что угроза номер 6 обнаружена (надеюсь, не из-за нарушения, а скорее из-за другого инструмента / техники, которая пытается найти потенциальные векторы атаки).

TDD поможет следующим образом:

  • Могут быть написаны тесты для проверки угрозы.
  • Может быть реализовано решение для блокирования угрозы и быстрого подтверждения его работоспособности.
  • Что еще более важно, при условии, что все остальные тесты все еще проходят, вы можете быстро проверить, что:
    • Все остальные меры безопасности по-прежнему работают правильно.
    • Все остальные функции по-прежнему работают корректно.
  • В основном TDD помогает быстро сократить время с момента обнаружения угрозы до того, как решение становится доступным.
  • TDD также обеспечивает высокую степень уверенности в том, что новая версия ведет себя правильно.

Тестируемый код

Я читал, что TDD часто неверно истолковывают как методологию тестирования, хотя на самом деле это скорее методология проектирования. TDD улучшает дизайн вашего кода, делая его более тестируемым .

Специализированное тестирование

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

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

Подобный модуль тестирования безопасности может перекрывать набор тестов и искать известные проблемы безопасности, такие как защищенные данные, оставленные в памяти, переполнения буфера или любой новый вектор атаки, который становится известным. Такое наложение будет иметь определенную степень достоверности, поскольку оно было проверено для всех известных функций системы.

Улучшенный дизайн

Одним из ключевых улучшений конструкции, возникающих как побочный эффект TDD, является явная зависимость . Многие системы страдают от веса неявных или производных зависимостей. И это сделало бы тестирование практически невозможным. В результате конструкции TDD имеют тенденцию быть более модульными в нужных местах . С точки зрения безопасности это позволяет вам делать такие вещи, как:

  • Тестирование компонентов, которые получают сетевые данные без фактической отправки их по сети.
  • Можно легко смоделировать объекты, чтобы они вели себя неожиданным / «нереалистичным» образом, как это может происходить в сценариях атаки.
  • Испытание компонентов в изоляции.
  • Или с любым желаемым сочетанием производственных компонентов.

Модульное тестирование

Следует отметить, что TDD предпочитает сильно локализоваться (модульное тестирование). В результате вы можете легко проверить, что:

  • SecureZeroMemory() правильно удалит пароль из ОЗУ.
  • Или, что GetSafeSQLParam() будет правильно защищать от внедрения SQL.

Однако становится все труднее проверить, что все разработчики использовали правильный метод в каждом месте, где это требуется.
Тест для проверки новой функции, связанной с SQL, подтвердил бы, что эта функция работает - она ​​будет одинаково хорошо работать как с «безопасной», так и с «небезопасной» версиями GetSQLParam.

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

  • Стандарты кодирования
  • Обзоры кодов
  • Тестирование
5 голосов
/ 21 марта 2011

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

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

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

Для переполнения буфера, проверка правильности работы подпрограмм, если слишком много данных также довольно просто проверить.Просто напишите тест и отправьте им слишком много данных.Утверждают, что они сделали то, что вы ожидали.Но некоторые переполнения буфера и переполнения стека немного сложнее.Вы должны быть в состоянии вызвать это, но вам также нужно выяснить, как определить, произошло ли это .Это может быть так просто, как выделение буферов с дополнительными байтами в них и проверка того, что эти байты не меняются во время тестов ... Или это могут быть некоторые другие творческие приемы.

Я не уверен, что есть простойответь же.Тестирование требует творчества, дисциплины и приверженности, но, как правило, оно того стоит.

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

Надеюсь, это поможет

4 голосов
/ 21 марта 2011

TDD - лучший способ построить безопасную систему.Все программное обеспечение, разработанное Microsoft, нечетко, и это, пожалуй, причина номер один для значительного сокращения найденных уязвимостей.Я настоятельно рекомендую использовать Peach Framework для этой цели.Лично я с большим успехом использовал Peach для обнаружения переполнения буфера.

Файлы Peach pit предоставляют способ описания данных, используемых вашим приложением.Вы можете выбрать, какой интерфейс вы хотите проверить.Ваше приложение читает файлы?Есть ли у него открытый порт?После того, как вы расскажете персику, как выглядит ввод и как взаимодействовать с вашим приложением, вы можете отключить его, и я знаю все неприятные данные, чтобы ваше приложение вырвалось изо всех сил.

Чтобы все работало, у peach есть отличная testing harness. Если ваше приложение вылетает, peach будет знать, потому что к нему подключен отладчик.Когда ваше приложение падает, Peach перезапустит его и продолжит тестирование.Peach может классифицировать все сбои и сопоставлять дампы ядра с входными данными, которые он использовал для сбоя приложения.

0 голосов
/ 14 октября 2015

Подошла тема Fuzzing, которая, я думаю, является отличным подходом к этой проблеме (в целом).Возникают вопросы: подходит ли Fuzzing к TDD?Где в процессе TDD подходит фаззинг?

Я полагаю, что он вполне подходит!Существуют фаззеры, такие как american fuzzy lop , которые можно создавать с помощью скриптов и самостоятельно адаптировать к изменениям в формате ввода-вывода.В этом конкретном случае вы можете интегрировать его с Travis CI , сохранить использованные вами входные тесты и выполнить регрессионное тестирование для них.

Я мог бы расширить этот ответ, если вы придумали какой-либовопросы для деталей в комментариях.

0 голосов
/ 22 марта 2011

Параметризованные тесты

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

Моя практика TDD

Мы проводим приемочные разработки на основе моей работы. Большинство наших тестов близки к функциональным тестам с полным стеком. Причина в том, что мы обнаружили, что было более ценно тестировать и проверять поведение действий пользователя. Мы используем такие методы, как генерация динамических тестов из параметризованных тестов, чтобы обеспечить нам больший охват с минимумом работы Мы делаем это для ASCII против UTF8, соглашений API и хорошо известных тестов вариантов.

...