Как стать автором
Обновить

Несколько мыслей по поводу безопасного кода

Время на прочтение5 мин
Количество просмотров697
В этой заметке я хотел бы описать несколько очевидных казалось бы проблем безопасности, которые, однако, не всегда в полной мере решаются программистами. В основном мои мысли касаются веб-разработки, но некоторые темы актуальны и для других приложений. В статье я оперирую примерами asp.net и C#, но уверен, что все проблемы актуальны и для других платформ и языков.

Забудьте про пользователя


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

Очень часто программисты проникаются мыслями о пользователе, когда они работают в визуальных средах. Такие среды как Delphi или Visual Studio позволяют легко определить обработку событий элементов управления, с которыми работает пользователь. Легко поддаться соблазну и дважды нажав в редакторе формы на кнопке задать действие, которое определило бы обработку действия пользователя. Формально – это правильно. Но рассмотрим пример:
  protected void Button1_Click(object sender, EventArgs e)
  {
    (sender as Button).Text = «Hello»;
  }
  private void MyMethod()
  {
    Button1_Click(null, new EventArgs());
  }
* This source code was highlighted with Source Code Highlighter.


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

Контекста нет, есть только требования безопасности


Имея дело с веб-приложениями, забудьте о том, что какие-то функции могут быть вызваны или элементы управления нажаты только когда что-то где-то произошло. Что пользователь произвел логин, что сегодня понедельник, что кнопку нажал Боря, а не Петя. Все это иллюзии, к которым приводят мысли о том, что вы имеете дело с пользователем, а не с кодом.

К примеру, есть у нас страница, на которой после логина появляется панель управления некоторым функционалом. Самой большой ошибкой может стать ваше убеждение в том, что данная панель управления не может быть использована неавторизованным пользователем. Это настолько простое заблуждение, что многие даже не задумываются над ним.

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

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

Что это значит? Все просто: забудьте про контекст, следуйте правилам безопасности.

Код всегда вызывается «злым» кодом


Похоже на паранойю, но это так. Никто и никогда не может быть уверен, какой код и как вызовет ваш метод. Можно быть уверенным только в одном – ваш код работает в изменчивой среде и требует обязательной валидации всех обязательных параметров: аргументов метода, глобальных переменных, данных файлов конфигурации, строки запроса и т.д.

Итак переделаем предыдущий пример так, как он того заслуживает:
  protected void Button1_Click(object sender, EventArgs e)
  {
    Button senderButton = (sender as Button);
    if (senderButton != null)
      senderButton.Text = «Hello»;
  }
  private void MyMethod()
  {
    Button1_Click(null, new EventArgs());
  }
* This source code was highlighted with Source Code Highlighter.


Исключительные ситуации заслуживают исключительных мер


Стоит запомнить раз и навсегда:
1. возврат методом кодов ошибок, вместо вызова исключения – это зло;
2. исключение не синоним ошибки.
Тут стоит сразу же определиться, что на самом деле считать кодом ошибки. Если код возвращает описание ситуации, то это – описание ситуации, но если код возвращает значение в случае возникновения ситуации, когда он не смог продолжить работу – то это и есть код ошибки, вызов которого должен быть заменен на вызов исключения.

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

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

Все ответы – отрицательные


Посмотрите на следующий код:
  enum Access
  {
    Granted,
    Restricted
  }
  private Access CheckLogin(string password)
  {
    Access result = Access.Granted;
    
    try
    {
      if (password != GetValidPassword())
        result = Access.Restricted;
    }
    catch
    {
    }

    return result;
  }
* This source code was highlighted with Source Code Highlighter.


Код очевидно дурацкий, но это упрощение, а в жизни можно увидеть и более сложные конструкции, суть которых сводится к этому коду. Что здесь плохого? Да просто тот факт, что если в методе GetValidPassword возникнет какое-либо исключение, то любой пользователь с любым паролем получит доступ.

Для возврата результата я определил несколько правил:
• в незначительных методах, которые не будут вызывать исключения при проблемах, избегайте возвращать не nullable типы, кроме булевых значений;
• все методы, возвращающие значения, первым делом должны инициализировать это значение самым отрицательным или безопасным вариантом (для nullable типов – это может быть null, для булевых – false, для перечислений – самое негативное значение вроде Access.Restricted);
• все методы должны иметь одну точку для возврата значений.
В результате можно выделить такой паттерн:
  private Access MyMethod()
  {
    Access result = Access.Restricted;

    // помещайте свой код здесь, в коде не должно быть вызовов return, только изменение значения result

    return result;
  }
* This source code was highlighted with Source Code Highlighter.


Заключение


В статье затронуты вопросы написания безопасного кода, в итоге можно выделить следующие правила:
• ваш код работает во враждебной среде, обеспечивайте все требования безопасности в каждом методе;
• проверяйте валидна ли среда, в которой ваш код будет работать, с какими параметрами его вызвали, помните, что это делает не пользователь, но другой код;
• всегда по умолчанию возвращайте безопасные в контексте значения;
• все методы должны иметь одну точку выхода;
• используйте исключения в исключительных ситуациях, чтобы гарантированно прервать выполнение кода;
• игнорируйте, либо обрабатывайте незначительные проблемы в угоду выполнения важной задачи.
Теги:
Хабы:
Всего голосов 45: ↑41 и ↓4+37
Комментарии22

Публикации