Pull to refresh

Искусство оборонительного программирования

Reading time6 min
Views16K
Original author: Diego Mariani
image

Почему разработчики не могут написать безопасный код? Мы не говорим здесь очередной раз про «чистый код». Мы говорим о большем с чисто практической точки зрения — о надёжности и безопасности программного обеспечения. Да, потому что небезопасное программное обеспечение в значительной степени бесполезно. Посмотрим, что значит «небезопасное» программное обеспечение:

  • Полёт №501 ракеты «Ариан-5» Европейского космического агентства был прекращён через 40 секунд после старта (4 июня 1996 г.). Экспериментальная ракета-прототип стоимостью 1 млрд. долларов США самоликвидировалась из-за ошибки в бортовом ПО управления.
  • Ошибка в программе, управлявшей установкой лучевой терапии Therac-25, стала прямой причиной смерти, как минимум, пяти пациентов в 80-х годах, когда она задавала чрезмерные дозы рентгеновского облучения.
  • Программная ошибка в зенитном ракетном комплексе MIM-104 «Patriot», вызывавшая уход его системных часов на одну треть секунды за сто часов, привела к его неспособности обнаружить и перехватить летящую ракету. Иракская ракета попала в воинскую часть в г. Дахран, Саудовская Аравия (25 февраля 1991 г.), погибло 28 американцев.

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

Первое знакомство с защитным программированием (проектирование самотестирующих и самокорректирующих программ)


Почему я думаю, что защитное (оборонительное) программирование является хорошим подходом для решения этих проблем в определённом виде проектов?
Защищайтесь от невозможного, потому что произойдёт именно оно.
Имеется много определений для термина «защитное (оборонительное) программирование»; они зависят от уровня «безопасности» и уровня ресурсов, требуемых для ваших программных проектов.
Защитное программирование является некоторой формой защитного (оборонительного) проектирования, предназначенного для обеспечения непрерывного функционирования некоторой части ПО в непредвиденных обстоятельствах. Методы защитного программирования часто используют там, где требуются высокая доступность, безопасность и надёжность (Wikipedia).
Я лично считаю, что данный подход целесообразен, когда имеется большой и долгоживущий проект, в который вовлечено много людей. Также это подходит, например, для такого проекта с открытым исходным кодом, который требует значительного и постоянного обслуживания.

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

Никогда не доверяйте входным данным от пользователя


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

image

Лучшая защита — это нападение

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

Лучшая защита — это нападение. Будьте строгими.

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


Первой из топ-10 уязвимостей ПО согласно OWASP являются инъекции. Это означает, что кто-то (или много людей) ещё не использует безопасные инструменты для обращения к своим базам данных. Используйте пакеты и библиотеки абстрактного представления баз данных. В языке PHP можно использовать PDO (переносимый распределённый объект), чтобы обеспечить базовую защиту от инъекций.

Не изобретайте велосипед


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

Именно это называют разумным многократным использованием кода. Пользуйтесь этой возможностью.

Не доверяйте разработчикам


Защитное программирование сродни тому, что автомобилисты называют «осторожным» вождением. При «осторожном» вождении мы предполагаем, что все вокруг нас могут поступить ненадлежащим образом. Таким образом, мы должны быть осторожными, тщательно отслеживая поведение окружающих. То же применимо к защитному программированию, когда мы как разработчики должны не доверять коду других разработчиков. Не должны мы доверять и нашему собственному коду.

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

Пишите НАДЁЖНЫЙ код


Это трудное дело для (защитного) программиста — написать надлежащий код. Об этом многие знают и говорят, но в действительности мало кто заботится или уделяет достаточные внимание и усилия для того, чтобы получить НАДЁЖНЫЙ код.

Рассмотрим несколько примеров ненадлежащего подхода.

Не делайте так: неинициализированные свойства

<?php
class BankAccount
{
    protected $currency = null;
    public function setCurrency($currency) { ... }
    public function payTo(Account $to, $amount)
    {
        // sorry for this silly example
        $this->transaction->process($to, $amount, $this->currency);
    }
}
// I forgot to call $bankAccount->setCurrency('GBP');
$bankAccount->payTo($joe, 100);

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

В этом случае мы сделали ещё лучше, инкапсулируя неинициализированное свойство в объект Money:

<?php
class BankAccount
{
    public function payTo(Account $to, Money $money) { ... }
}
$bankAccount->payTo($joe, new Money(100, new Currency('GBP')));

Защитите программу от неправильного обращения. Не используйте неинициализированные свойства объекта.

Не делайте так: дающее утечку состояние за пределами области действия класса

<?php
class Message
{
    protected $content;
    public function setContent($content)
    {
        $this->content = $content;
    }
}
class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(){
        var_dump($this->message);
    }
}
$message = new Message();
$message->setContent("bob message");
$joeMailer = new Mailer($message);
$message->setContent("joe message");
$bobMailer = new Mailer($message);
$joeMailer->sendMessage();
$bobMailer->sendMessage();

В этом случае Message (Сообщение) передаётся по ссылке, и результат будет в обоих случаях «joe message» («сообщение Джо»). Решением могло бы быть клонирование объекта сообщения в конструкторе Mailer. Но что мы всегда должны стараться сделать, так это использовать (неизменяемый) объект-значение вместо простого изменяемого объекта Message. Используйте неизменяемые объекты, когда вы можете.

<?php
class Message
{
    protected $content;
    public function __construct($content)
    {
        $this->content = $content;
    }
}
class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(
    {
        var_dump($this->message);
    }
}
$joeMailer = new Mailer(new Message("bob message"));
$bobMailer = new Mailer(new Message("joe message"));
$joeMailer->sendMessage();
$bobMailer->sendMessage();

Пишите тесты


Неужели об этом ещё надо говорить? Написание модульных тестов поможет вам выдерживать общие принципы, такие как сильная связность, персональная ответственность, слабое связывание и правильная композиция объектов. Это поможет тестировать не только работающий отдельный небольшой модуль, но и способ структурирования ваших объектов. Действительно, вы будете ясно видеть, тестируя ваши небольшие функции, сколько модулей надо протестировать и сколько объектов надо сымитировать, чтобы получить 100% покрытие тестами кода.

Выводы


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

Спасибо за прочтение!
Tags:
Hubs:
+4
Comments51

Articles