При написании кода вы сознательно программируете для защиты, чтобы обеспечить высокое качество программы и избежать возможности злонамеренного использования вашего кода, например, из-за эксплойтов переполнения буфера или внедрения кода?
Какой минимальный уровень качества вы всегда применяете к своему коду?
В моей работе наш код должен быть высшего качества.
Итак, мы ориентируемся на две основные вещи:
- тестирование
- Кодовые обзоры
Те приносят домой деньги.
Я всегда работаю, чтобы предотвратить такие вещи, как инъекционные атаки. Однако, когда вы работаете на внутреннем сайте интрасети, большинство функций безопасности воспринимаются как потраченные впустую усилия. Я все еще делаю их, может быть, просто не так хорошо.
Ну, есть определенный набор лучших практик для безопасности. Как минимум, для приложений баз данных вы должны остерегаться SQL-инъекций.
Другие вещи, такие как хеширование паролей, шифрование строк подключения и т. Д., Также являются стандартом.
С этого момента, это зависит от фактического применения.
К счастью, если вы работаете с фреймворками, такими как .Net, встроена большая защита.
Вы должны всегда программировать с защитой, я бы сказал, даже для внутренних приложений, просто потому, что пользователи могут просто по счастливой случайности написать что-то, что сломает ваше приложение. Конечно, вам не нужно беспокоиться о попытке обмануть вас из денег, но все же. Всегда программируйте в обороне и предполагайте, что приложение не будет работать.
Подобно abyx, в команде я на разработчиков всегда использую модульное тестирование и обзоры кода. В дополнение к этому, я также хочу удостовериться, что я не включаю код, который могут использовать люди - я склонен писать код только для базового набора методов, требуемых для того, чтобы объект под рукой функционировал, как было определено , Я обнаружил, что включение методов, которые могут никогда не использоваться, но обеспечивающих функциональность, может непреднамеренно ввести в систему «черный ход» или непреднамеренное / непредвиденное использование.
Гораздо проще вернуться позже и ввести методы, атрибуты и свойства, для которых запрашиваются, а не предвидеть то, что может никогда не прийти.
Использование Test Driven Development, безусловно, помогает. Вы пишете один компонент за раз, а затем перечисляете все потенциальные случаи для входных данных (с помощью тестов) перед написанием кода. Это гарантирует, что вы охватили все основы и не написали ни одного классного кода, который никто не будет использовать, но который может сломаться.
Хотя я не делаю ничего формального, я обычно провожу некоторое время, изучая каждый класс и обеспечивая, чтобы
- если они находятся в действительном состоянии, что они остаются в действительном состоянии
- нет возможности построить их в недопустимом состоянии
- При исключительных обстоятельствах они потерпят неудачу настолько изящно, насколько это возможно (часто это чистка и бросок)
По-разному.
Если я действительно что-то взломаю для собственного использования, я напишу лучший код, о котором мне не нужно думать. Пусть компилятор будет моим другом для предупреждений и т. Д., Но я не буду автоматически создавать типы для этого.
Более вероятно, что код будет использоваться, даже иногда я увеличиваю уровень проверок.
- минимальные магические числа
- лучшие имена переменных
- полностью проверенная и определенная длина массива / строки
- программирование по контрактным утверждениям
- проверка нулевого значения
- исключения (в зависимости от контекста кода)
- основные пояснительные комментарии
- доступная документация по использованию (если Perl и т. д.)
Я возьму другое определение защитного программирования, как то, которое Джош Блох отстаивал в Effective Java . В книге он рассказывает о том, как обрабатывать изменяемые объекты, которые вызывающие абоненты передают в ваш код (например, в установщики), и изменяемые объекты, которые вы передаете вызывающим (например, в получатели).
- Для сеттеров обязательно клонируйте любые изменяемые объекты и сохраните клон. Таким образом, вызывающие абоненты не могут изменить переданный объект после факта, чтобы сломать инварианты вашей программы.
- Для получателей, либо верните неизменное представление ваших внутренних данных, если интерфейс позволяет это; или же вернуть клон внутренних данных.
- При вызове предоставленных пользователем обратных вызовов с внутренними данными отправьте в неизменяемом представлении или клонируйте, в зависимости от случая, если только вы не намереваетесь использовать обратный вызов для изменения данных, и в этом случае вы должны проверить их после факта.
Главная задача сообщения - убедиться, что никакой внешний код не может содержать псевдоним любых изменяемых объектов, которые вы используете внутри, чтобы вы могли поддерживать свои инварианты.
Я бы рекомендовал защищаться от данных, которые входят в «компонент» или структуру. В рамках «компонента» или структуры следует думать, что данные являются «правильными».
Думая, как это. Вызывающий должен предоставить правильные параметры, иначе ВСЕ функции и методы должны проверять каждый входящий параметр. Но если проверка выполняется только для вызывающего абонента, проверка необходима только один раз. Таким образом, параметр должен быть «правильным» и, таким образом, может передаваться на более низкие уровни.
- Всегда проверяйте данные из внешних источников, пользователей и т. Д.
- «Компонент» или структура всегда должны проверять входящие вызовы.
Если есть ошибка и неправильное значение используется в вызове. Что действительно является правильным? У одних только указание на то, что «данные», над которыми работает программа, неверны, а некоторые, например, ASSERTS, а другие хотят использовать расширенные отчеты об ошибках и возможное восстановление после ошибок. В любом случае данные оказываются неверными, и в некоторых случаях полезно продолжить работу с ними. (обратите внимание, это хорошо, если серверы не умирают по крайней мере)
Изображение, отправленное со спутника, может послужить причиной для расширенного восстановления после ошибки на ... изображении, загруженном из Интернета, чтобы установить значок ошибки для ...
Я придерживаюсь мнения, что правильное программирование защитит от этих рисков. Такие вещи, как избегание устаревших функций, которые (по крайней мере в библиотеках Microsoft C ++) обычно не рекомендуются из-за уязвимостей безопасности, и проверка всего, что пересекает внешнюю границу.
Функции, которые вызываются только из вашего кода, не должны требовать чрезмерной проверки параметров, поскольку вы управляете вызывающей стороной, то есть внешняя граница не пересекается. Функции, вызываемые чужим кодом, должны предполагать, что входящие параметры в какой-то момент будут недействительными и / или вредоносными.
Мой подход к работе с открытыми функциями состоит в том, чтобы просто завершить работу с полезным сообщением, если это возможно. Если вызывающая сторона не может правильно определить параметры, проблема заключается в их коде, и они должны это исправить, а не вы. (Очевидно, вы предоставили документацию для своей функции, так как она выставлена.)
Внедрение кода является проблемой только в том случае, если ваше приложение способно повысить статус текущего пользователя. Если процесс может внедрить код в ваше приложение, он может легко записать код в память и выполнить его в любом случае. Не имея возможности получить полный доступ к системному коду, атаки бессмысленны. (Вот почему приложения, используемые администраторами, не должны быть доступны для записи меньшим пользователям.)
По моему опыту, использование защитного программирования не обязательно означает, что вы в конечном итоге улучшаете качество своего кода. Не поймите меня неправильно, вам нужно защищаться от программ, чтобы уловить проблемы, с которыми сталкиваются пользователи - пользователям не нравится, когда ваша программа падает на них, - но это вряд ли облегчит поддержку кода, тест и т. д.
Несколько лет назад мы установили политику использования утверждений на всех уровнях нашего программного обеспечения, и это - наряду с модульным тестированием, обзорами кода и т. Д., А также нашими существующими наборами тестов приложений - оказало значительное положительное влияние на качество нашего кода.
Я рекомендую людям писать код, который является фашистским в среде разработки и доброжелательным в производстве.
Во время разработки вы хотите как можно раньше отловить неверные данные / логику / код, чтобы предотвратить либо проблемы, оставшиеся незамеченными, либо последующие проблемы, в которых первопричину трудно отследить.
На производстве справляйтесь с проблемами максимально изящно. Если что-то действительно является неисправимой ошибкой, обработайте ее и предоставьте эту информацию пользователю.
В качестве примера вот наш код для нормализации вектора. Если вы предоставляете им неверные данные в процессе разработки, они будут кричать, а в процессе производства они возвращают значение безопасности.
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 / Shared.
Подписанные JAR, чтобы остановить загрузку классов, являющихся проблемой безопасности.
JAAS может позволить вашему приложению не доверять никому, даже себе.
J2EE имеет (по общему признанию ограниченный) встроенную поддержку безопасности на основе ролей.
Есть некоторые накладные расходы для некоторых из этого, но дыры в безопасности исчезают.
Простой ответ: это зависит . Слишком много защитного кодирования может вызвать серьезные проблемы с производительностью.