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

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

Какой «минимальный» уровень качества вы всегда будете применять к своему коду?

Ответов (14)

Решение

В моей работе наш код должен быть высокого качества.
Итак, акцентируем внимание на двух основных вещах:

  1. Тестирование
  2. Проверки кода

Те приносят домой деньги.

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

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

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

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

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

Хотя я не делаю ничего формального, я обычно провожу некоторое время, просматривая каждый класс и удостоверяясь, что:

  1. если они находятся в допустимом состоянии, они остаются в допустимом состоянии
  2. нет возможности построить их в недопустимом состоянии
  3. В исключительных случаях они выйдут из строя настолько элегантно, насколько это возможно (часто это очистка и выброс).

Я бы рекомендовал защищать данные, которые входят в «компонент» или структуру. В рамках «компонента» или структуры следует думать, что данные «правильные».

Думая так. Вызывающий должен предоставить правильные параметры, в противном случае ВСЕ функции и методы должны проверять каждый входящий параметр. Но если проверка выполняется только для вызывающего абонента, проверка требуется только один раз. Таким образом, параметр должен быть «правильным» и, таким образом, может передаваться на более низкие уровни.

  1. Всегда проверяйте данные из внешних источников, пользователей и т. Д.
  2. «Компонент» или фреймворк всегда должен проверять входящие вызовы.

Если есть ошибка и в вызове используется неправильное значение. Что действительно нужно делать? У одного есть только указание на то, что «данные», над которыми работает программа, неверны, и некоторым нравится ASSERTS, но другие хотят использовать расширенный отчет об ошибках и возможное восстановление после ошибок. В любом случае обнаруживается, что данные ошибочны, и в некоторых случаях полезно продолжить работу над ними. (учтите, хорошо, если серверы хотя бы не умирают)

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

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

Другие вещи, такие как хеширование паролей, шифрование строк подключения и т. Д., Также являются стандартом.

С этого момента это зависит от конкретного приложения.

К счастью, если вы работаете с такими фреймворками, как .Net, многие средства защиты уже встроены.

Это зависит.

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

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

  • минимальные магические числа
  • лучшие имена переменных
  • полностью проверенные и определенные длины массивов / строк
  • программирование по утверждениям контракта
  • проверка нулевого значения
  • исключения (в зависимости от контекста кода)
  • основные пояснительные комментарии
  • доступная документация по использованию (если Perl и т. д.)

Я очень считаю, что правильное программирование защитит от этих рисков. Такие вещи, как избегание устаревших функций, которые (по крайней мере, в библиотеках Microsoft C++) обычно не рекомендуются из-за уязвимостей безопасности, и проверка всего, что пересекает внешнюю границу.

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

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

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

Я воспользуюсь другим определением защитного программирования, например, предложенным Джошем Блохом в Эффективной Java ». В книге он говорит о том, как обрабатывать изменяемые объекты, которые вызывающие объекты передают вашему коду (например, в установщиках), и изменяемые объекты, которые вы передаете вызывающим объектам (например, в геттерах).

  • Для сеттеров обязательно клонируйте все изменяемые объекты и сохраните клон. Таким образом, вызывающие абоненты не могут изменить переданный объект постфактум, чтобы нарушить инварианты вашей программы.
  • Для геттеров: либо вернуть неизменяемое представление ваших внутренних данных, если интерфейс позволяет это; или же вернуть клон внутренних данных.
  • При вызове предоставленных пользователем обратных вызовов с внутренними данными отправьте неизменяемое представление или клон, если это необходимо, если только вы не намерены обратный вызов для изменения данных, и в этом случае вы должны проверить его постфактум.

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

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

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

Я рекомендую людям писать фашистский код в среде разработки и доброжелательный в производственной среде.

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

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

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

inline const Vector3 Normalize( Vector3arg vec )
{
    const float len = Length(vec);
    ASSERTMSG(len > 0.0f "Invalid Normalization");
    return len == 0.0f ? vec : vec / len;
}

Java, подписанные JAR-файлы и JAAS.

Java для предотвращения переполнения буфера и взлома указателя / стека.

Не используйте JNI. (Java Native Interface) он предоставляет вам DLL / общие библиотеки.

Подписанные JAR-файлы, чтобы загрузка класса перестала быть проблемой безопасности.

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

J2EE имеет (по общему признанию, ограниченную) встроенную поддержку безопасности на основе ролей.

Для некоторых из них есть некоторые накладные расходы, но дыры в безопасности исчезнут.

Простой ответ: это зависит от обстоятельств . Слишком много защитного кода может вызвать серьезные проблемы с производительностью.