Защитное программирование


Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

Защитное программирование - это подход к улучшению программного обеспечения и исходного кода с точки зрения:

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

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

Безопасное программирование

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

int  risky_programming ( char  * input )  {  char  str [ 1000 ]; // ... strcpy ( str ,  input );  // Копируем input. // ... }

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

int  secure_programming ( char  * ввод )  {  char  str [ 1000 + 1 ];  // Еще один для нулевого символа. // ... // Копируем ввод, не превышая длины места назначения.  strncpy ( str ,  input ,  sizeof ( str )); // Если strlen (input)> = sizeof (str), тогда strncpy не завершится нулевым значением.  // Мы противодействуем этому, всегда устанавливая последний символ в буфере на NUL,  // эффективно обрезая строку до максимальной длины, которую мы можем обработать.  // Также можно решить явно прервать выполнение программы, если  //  strlen (input) слишком длинный. str [ sizeof ( str )  -  1 ]  =  '\ 0' ; // ... }

Наступательное программирование

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

Вера в достоверность внутренних данных

Чрезмерно защитное программирование
const  char *  trafficlight_colorname ( enum  traffic_light_color  c )  {  переключатель  ( c )  {  case  TRAFFICLIGHT_RED :  return  "красный" ;  case  TRAFFICLIGHT_YELLOW :  вернуть  «желтый» ;  case  TRAFFICLIGHT_GREEN :  вернуть  «зеленый» ;  }  return  «черный» ;  // Следует рассматривать как мертвый светофор.  // Предупреждение: этот последний оператор return будет отброшен при оптимизации // компилятор, если все возможные значения 'traffic_light_color' перечислены в  // предыдущем операторе 'switch' ... }
Наступательное программирование
const  char *  trafficlight_colorname ( enum  traffic_light_color  c )  {  переключатель  ( c )  {  case  TRAFFICLIGHT_RED :  return  "красный" ;  case  TRAFFICLIGHT_YELLOW :  вернуть  «желтый» ;  case  TRAFFICLIGHT_GREEN :  вернуть  «зеленый» ;  }  assert ( 0 );  // Утверждаем, что этот раздел недоступен.  // Предупреждение: этот вызов функции assert будет отброшен при оптимизации. // компилятор, если все возможные значения 'traffic_light_color' перечислены в  // предыдущем операторе 'switch' ... }

Доверие программным компонентам

Чрезмерно защитное программирование
if  ( is_legacy_compatible ( user_config ))  {  // Стратегия: не  верьте, что новый код ведет себя так же, как old_code ( user_config ); }  else  {  // Резерв: не  верьте, что новый код обрабатывает те же случаи if  ( new_code ( user_config )  ! =  OK )  {  old_code ( user_config );  } }
Наступательное программирование
// Ожидаем, что в новом коде нет новых ошибок if  ( new_code ( user_config )  ! =  OK )  {  // Громко сообщить и внезапно завершить программу, чтобы привлечь должное внимание  report_error ( «Что-то пошло очень не так» );  выход ( -1 ); }

Методы

Вот несколько приемов защитного программирования:

Интеллектуальное повторное использование исходного кода

Если существующий код протестирован и заведомо работает, его повторное использование может снизить вероятность появления ошибок.

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

Унаследованные проблемы

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

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

Многие программные продукты испытывали проблемы со старым унаследованным исходным кодом; Например:

  • Унаследованный код может не быть разработан в рамках инициативы защитного программирования и поэтому может иметь гораздо более низкое качество, чем недавно разработанный исходный код.
  • Устаревший код мог быть написан и протестирован в условиях, которые больше не применяются. Старые тесты по обеспечению качества могут больше не иметь силы.
    • Пример 1 : унаследованный код мог быть разработан для ввода ASCII, но теперь вводится UTF-8.
    • Пример 2 : унаследованный код мог быть скомпилирован и протестирован на 32-битных архитектурах, но при компиляции на 64-битных архитектурах могут возникнуть новые арифметические проблемы (например, недопустимые тесты подписи, недопустимое приведение типов и т. Д.).
    • Пример 3 : унаследованный код мог быть предназначен для автономных компьютеров, но становится уязвимым после добавления сетевого подключения.
  • Устаревший код не пишется с учетом новых проблем. Например, исходный код, написанный в 1990 году, вероятно, будет подвержен множеству уязвимостей, связанных с внедрением кода , потому что большинство таких проблем в то время не было широко изучено.

Известные примеры устаревшей проблемы:

  • BIND 9 , представленный Полом Викси и Дэвидом Конрадом как «BINDv9 - это полностью переписанный », «Безопасность была ключевым моментом при разработке» [1], называя безопасность, надежность, масштабируемость и новые протоколы ключевыми проблемами при переписывании старого унаследованного кода.
  • Microsoft Windows страдала от уязвимости метафайла Windows и других эксплойтов, связанных с форматом WMF. Центр реагирования Microsoft Security описывает WMF-функцию , как «Около 1990, была добавлена поддержка WMF ... Это было другое время в ландшафте безопасности ... все были полностью доверять» , [2] не разрабатываются в рамках инициатив в области безопасности на Microsoft.
  • Oracle борется с устаревшими проблемами, такими как старый исходный код, написанный без решения проблем, связанных с внедрением SQL и повышением привилегий , что приводит к множеству уязвимостей безопасности, на исправление которых потребовалось время, а также к созданию неполных исправлений. Это вызвало резкую критику со стороны таких экспертов по безопасности, как Дэвид Литчфилд , Александр Корнбруст , Сезар Серрудо . [3] [4] [5] Дополнительная критика заключается в том, что установки по умолчанию (в основном унаследованные от старых версий) не согласованы с их собственными рекомендациями по безопасности, такими как контрольный список безопасности базы данных Oracle, который сложно изменить, поскольку для правильной работы многим приложениям требуются менее безопасные устаревшие настройки.

Канонизация

Злоумышленники могут изобретать новые виды представления неверных данных. Например, если программа пытается отклонить доступ к файлу «/ etc / passwd », взломщик может передать другой вариант этого имени файла, например «/etc/./passwd». Канонизации библиотеки могут быть использованы , чтобы избежать ошибок из - за не- канонического ввода.

Низкая устойчивость к «потенциальным» ошибкам

Предположим, что конструкции кода, которые кажутся склонными к проблемам (аналогично известным уязвимостям и т. Д.), Являются ошибками и потенциальными недостатками безопасности. Основное практическое правило: «Я не осведомлен обо всех типах уязвимостей . Я должен защищаться от тех, о которых знаю, и тогда я должен действовать на опережение!».

Другие техники

  • Одна из наиболее распространенных проблем - это неконтролируемое использование структур и функций постоянного размера для данных динамического размера ( проблема переполнения буфера ). Это особенно характерно для строковых данных в C . Такие функции библиотеки C, как getsникогда, не должны использоваться, поскольку максимальный размер входного буфера не передается в качестве аргумента. Такие функции библиотеки C, как scanfможно использовать безопасно, но требуют, чтобы программист позаботился о выборе строк безопасного формата, очистив их перед использованием.
  • Шифруйте / аутентифицируйте все важные данные, передаваемые по сетям. Не пытайтесь реализовать свою собственную схему шифрования, а используйте вместо нее проверенную.
  • Все данные важны, пока не будет доказано обратное.
  • Все данные испорчены, пока не будет доказано обратное.
  • Весь код небезопасен, пока не будет доказано обратное.
    • Вы не можете доказать безопасность какого-либо кода в пользовательском пространстве , или, что более канонично: «никогда не доверяйте клиенту» .
  • Если необходимо проверить правильность данных, убедитесь, что они верны, а не неверны.
  • Дизайн по контракту
    • Дизайн по контракту использует предусловия , постусловия и инварианты, чтобы гарантировать, что предоставленные данные (и состояние программы в целом) очищены. Это позволяет коду документировать свои предположения и безопасно их делать. Это может включать проверку аргументов функции или метода на предмет допустимости перед выполнением тела функции. После тела функции также целесообразно выполнить проверку состояния или других хранимых данных и возвращаемого значения перед выходом (код прерывания / возврата / выброса / ошибки).
  • Утверждения (также называемые ассертивным программированием )
    • Внутри функций вы можете проверить, что вы не ссылаетесь на что-то недопустимое (т. Е. На null) и что длины массивов действительны до того, как ссылаться на элементы, особенно во всех временных / локальных экземплярах. Хорошая эвристика - не доверять библиотекам, которые вы тоже не писали. Поэтому каждый раз, когда вы им звоните, проверяйте, что вы от них получите. Часто помогает создать небольшую библиотеку функций «утверждения» и «проверки», чтобы сделать это вместе с регистратором, чтобы вы могли отслеживать свой путь и, в первую очередь, уменьшить потребность в обширных циклах отладки . С появлением библиотек протоколирования и аспектно-ориентированного программирования многие утомительные аспекты защитного программирования уменьшились.
  • Предпочитать исключения из кодов возврата
    • Вообще говоря, предпочтительнее генерировать понятные сообщения об исключениях, которые обеспечивают выполнение части вашего контракта API и направляют клиентского программиста, вместо того, чтобы возвращать значения, к которым клиентский программист, вероятно, не будет готов, и, следовательно, минимизировать их жалобы и повысить надежность и безопасность вашего программного обеспечения. . [ сомнительно ]

Смотрите также

  • Компьютерная безопасность
  • Программирование с учетом иммунитета

использованная литература

  1. ^ "Архив Fogo: Пол Викси и Дэвид Конрад о BINDv9 и безопасности в Интернете Джеральда Оскобойни <[email protected]>" . Впечатляющий.net . Проверено 27 октября 2018 .
  2. ^ "Глядя на проблему WMF, как она туда попала?" . MSRC . Архивировано из оригинала на 2006-03-24 . Проверено 27 октября 2018 .
  3. ^ Литчфилд, Дэвид. "Bugtraq: Oracle, где патчи ???" . seclists.org . Проверено 27 октября 2018 .
  4. ^ Александр, Корнбруст. "Bugtraq: RE: Oracle, где патчи ???" . seclists.org . Проверено 27 октября 2018 .
  5. ^ Cerrudo, Сезар. "Bugtraq: Re: [Полное раскрытие] RE: Oracle, где патчи ???" . seclists.org . Проверено 27 октября 2018 .

внешние ссылки

  • Стандарты безопасного кодирования CERT
Источник « https://en.wikipedia.org/w/index.php?title=Defensive_programming&oldid=1038480120 »