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

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

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

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

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

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

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

int risky_programming(char *input) {
  char str[1000]; 
  
  // ...
  
  strcpy(str, input);  // Copy input.
  
  // ...
}

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

int secure_programming(char *input) {
  char str[1000+1];  // One more for the null character.

  // ...

  // Copy input without exceeding the length of the destination.
  strncpy(str, input, sizeof(str)); 

  // If strlen(input) >= sizeof(str) then strncpy won't null terminate. 
  // We counter this by always setting the last character in the buffer to NUL,
  // effectively cropping the string to the maximum length we can handle.
  // One can also decide to explicitly abort the program if strlen(input) is 
  // too long.
  str[sizeof(str) - 1] = '\0';

  // ...
}

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

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

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

Чрезмерно защитное программирование
const char* trafficlight_colorname(enum traffic_light_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    return "black"; // To be handled as a dead traffic light.
    // Warning: This last 'return' statement will be dropped by an optimizing
    // compiler if all possible values of 'traffic_light_color' are listed in
    // the previous 'switch' statement...
}
Наступательное программирование
const char* trafficlight_colorname(enum traffic_light_color c) {
    switch (c) {
        case TRAFFICLIGHT_RED:    return "red";
        case TRAFFICLIGHT_YELLOW: return "yellow";
        case TRAFFICLIGHT_GREEN:  return "green";
    }
    assert(0); // Assert that this section is unreachable.
    // Warning: This 'assert' function call will be dropped by an optimizing
    // compiler if all possible values of 'traffic_light_color' are listed in
    // the previous 'switch' statement...
}

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

Чрезмерно защитное программирование
if (is_legacy_compatible(user_config)) {
    // Strategy: Don't trust that the new code behaves the same
    old_code(user_config);
} else {
    // Fallback: Don't trust that the new code handles the same cases
    if (new_code(user_config) != OK) {
        old_code(user_config);
    }
}
Наступательное программирование
// Expect that the new code has no new bugs
if (new_code(user_config) != OK) {
    // Loudly report and abruptly terminate program to get proper attention
    report_error("Something went very wrong");
    exit(-1);
}

Методы

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

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

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

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

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

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

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

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

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

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

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

Канонизация

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

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

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

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

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

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

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

  1. ^ "Архив Fogo: Пол Викси и Дэвид Конрад о BINDv9 и безопасности в Интернете Джеральда Оскобойни <gerald@impressive.net>" . Впечатляющий.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 .

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