Статьи

Что разработчики ASP.Net MVC могут извлечь из проблем безопасности GitHub


Ранее в этом году несколько историй прошли через Ruby On Rails и более широкое сообщество разработчиков как одну из своих ярких звезд, GitHub был
взломан, чтобы привлечь внимание к некоторым слабым сторонам, которые могут возникнуть из-за связывания моделей на основе конвенций ROR. Интересно, что дыра в безопасности, обнаруженная на GitHub, заключается в том, что она не обязательно ограничена Ruby On Rails, но часто возникает из-за использования инфраструктуры, которая поддерживает связывание моделей из коробки без предварительного понимания ограничений безопасности. Это также ставит вопрос на передний план: является ли роль разработчиков фреймворков заставить любую конфигурацию безопасности быть настройкой по умолчанию вместо того, чтобы быть примененной явно?

образРазработчик ROR Егор Хомаков обнаружил уязвимость конфигурации в каркасе платформы ROR. Эта уязвимость затронула все сайты ROR, которые были разработаны с использованием базовых строительных лесов, которые входят в комплект поставки без учета проблем безопасности, и реализации связей между классами доступа к данным.

Первоначально Егор пытался привлечь к этому внимание, разместив сообщение о проблеме на сайте Ruby On Rails Github . Не получив ответа, он подумал, что сообщество Rails должно было исправить столь распространенную ошибку конфигурации безопасности, опубликовал сообщение с отметкой времени в 1000 лет в будущем, а затем добавил свой закрытый ключ в группу Rails и загрузил (совершенно безобидный) ) файл в репозиторий Rails Git — все, чтобы доказать свою точку зрения.

Ниже я расскажу о проблеме GitHub, о том, как это может происходить в гипотетической ситуации в ASP.Net MVC, и о том, что вы можете с этим поделать.

Давайте разберемся с проблемой

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

В Ruby On rails: если у вас есть модель данных, которая выглядит следующим образом (внешний ключ фактически не будет включен в ваш объект модели, ROR внедрит это для вас):

образ

Когда вы компилируете таблицы, вы получите код, который выглядит следующим образом (я удалил любой код, о котором мы не будем говорить):

# == Schema Information
# Table name: users
#
#  id                         :integer(11)     not null, primary key
#  email                      :string(255)     
#  username                   :string(255)     
#  password                   :string(255)
class User < ActiveRecord::Base
  belongs_to :securityroles

  validates_presence_of :email, :password
  validates_uniqueness_of :email

  ...
  #class scaffolding code
  ...

end

# == Schema Information
# Table name: securityroles
#
#  id                         :integer(11)     not null, primary key
#  roleName                   :string(255)     not null
class SecurityRole < ActiveRecord::Base
  has_many :users

  validates_presence_of :roleName
  ...
  #class scaffolding code
  ...

  def update
    @user = User.find(params[:id])
 
    respond_to do |format|
      if @user.update_attributes(params[:user])
        flash[:notice] = 'User was successfully updated.'
        format.html { redirect_to(@user) }
      else
        format.html { render :action => "edit" }
      end
    end
  end
end

Вы заметите выше, что главное, на что нужно обратить внимание в этом скаффолд-коде, это часть, где он говорит следующее:

def update
  @user = User.find(params[:id])
 
  respond_to do |format|
    if @user.update_attributes(params[:user])
      flash[:notice] = 'User was successfully updated.'
      format.html { redirect_to(@user) }
    else
      format.html { render :action => "edit" }
    end
  end
end

ROR делает здесь функцию, которую они называют «Массовое назначение».

Что делает приведенный выше код:

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

Атака Егора на GitHub была очень проста: он просто добавил новое поле на страницу, которая отправляла новые SSH-ключи для пользователя с именем поля id, и затем продолжил делать значение этого поля идентификатором для Ruby On. Владелец проекта Rails . Это привело к тому, что приведенный выше код скаффолдинга по умолчанию сохранял свой закрытый ключ GitHub SSH не в своей учетной записи GitHub, а в учетной записи Ruby On Rails GitHub. Sneaky. Также очень простой контроль со стороны GitHub.

Чтобы быть справедливым по отношению к Ruby On Rails, есть способ остановить такого рода атаки, пометив ваш класс свойством attr_accessible nil, чтобы отключить поддержку массового присвоения для класса, если он не указан, поэтому вы можете белый список только тех полей, которые вы хотите связать вручную — проблема с этой функцией заключается в том, что она не используется по умолчанию . Вы должны сделать это вручную — и мы все знаем, как легко пропустить или забыть об этих вещах в спешке, чтобы уложиться в срок.

В ASP.net MVC эта функциональность ROR была бы аналогична нашей модели привязки. Очевидно, что код, который создает инструмент ROR scaffolding, гораздо более функциональный, чем мы получаем из коробки с ASP.Net MVC, но функциональность CRUD, которую они создают, очень похожа на всех веб-платформах. В ASP.Net MVC вы должны написать свой собственный код CRUD для добавления, редактирования и обновления записей, и ASP.Net MVC пытается упростить это, добавив привязку к модели в смесь.

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

Как это поведение выглядит на практике

Чтобы взглянуть на то, что это повлечет за собой в ASP.Net MVC, давайте подумаем о том, как на самом деле вышеприведенное приложение модели данных Ruby on Rails будет работать в реальной жизни с использованием ASP.Net MVC.

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

образ

Если вы не были включены, и вы были новичком в ASP.Net MVC и веб-разработке, было бы легко написать страницу настроек пользователя, которая выглядела бы так:

@using (Html.BeginForm()) {
    @Html.ValidationSummary()
    <div>
        <fieldset>
            <legend>Account Information</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.Name)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Name)
                @Html.ValidationMessageFor(m => m.Name)
            </div>
            
             <div class="editor-label">
                @Html.LabelFor(m => m.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Email)
                @Html.ValidationMessageFor(m => m.Email)
            </div>
            <p>
                <input type="submit" value="Change Account Details" />
            </p>
        </fieldset>
    </div>
}

И ваш контроллер настроек может выглядеть так:

[Authorize]
public ActionResult ChangeAccountDetails()
{
    var dbUser = DAL.UserRepository.Load(x => x.Username = User.Identity.Name);
    var model = new UserModel(dbUser);
    return View(model);
}
[Authorize]
[HttpPost]
public ActionResult ChangeAccountDetails(UserModel model)
{
    DAL.User dbUser = DAL.UserRepository.Load(x => x.Username = User.Identity.Name);
    if (TryUpdateModel(dbUser))
    {
        DAL.User.Save(dbUser);
        return RedirectToAction("ChangeDetailsSuccess");
    }
       
    ModelState.AddModelError("", "Something went wrong");
    return View(model);
}

Что не так с этим кодом?

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

Точно такая же проблема, которая затрагивает сообщество Ruby On Rails в хаке GitHub с их «ошибкой» массовых назначений (если мы даже называем это так), заключается в том, что приведенный выше код ASP.Net MVC слепо связывает любые входящие параметры запроса с нашей моделью базы данных. ,

образ

Это та же самая проблема, которая позволила Егору взломать GitHub:

Мы не указываем, какие входящие параметры нас действительно волнуют.

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

Как мы можем взломать вышеуказанный кусок кода MVC?

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

  1. Сайт дает нам данные.
  2. Мы отправляем обновленные данные обратно.
  3. Но мы также добавили что-то подлое для поездки.

Давайте изменим нашу страницу настроек учетной записи и добавим новое поле.

@using (Html.BeginForm()) {
    @Html.ValidationSummary()
    <div>
        <fieldset>
            <legend>Account Information</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.Name)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Name)
                @Html.ValidationMessageFor(m => m.Name)
            </div>
            
             <div class="editor-label">
                @Html.LabelFor(m => m.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Email)
                @Html.ValidationMessageFor(m => m.Email)
            </div>
            <!--                 
                #HACK ATTACK#                
                THIS FIELD WILL SET OUR ROLE ID TO "1" 
            -->
            <input type="text" name="SecurityRoles_Id" value="1"/>
            <!-- -->
            <p>
                <input type="submit" value="Change Account Details" />
            </p>
        </fieldset>
    </div>
}
  1. Сайт дает нам нашу страницу.
  2. Мы добавляем новое поле с именем SecurityRoles_Id на страницу, используя Firebug и т. Д.
  3. Когда мы отправляем страницу обратно, сайт устанавливает внешний ключ нашей роли безопасности в 1.

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

Вспомните, когда вы впервые настраивали свою базу данных. Каким был первый пользователь (запись № 1), которого вы добавили в свою базу данных, администратор? Какую первую роль (запись № 1) вы добавили в таблицу ролей безопасности, Admin?

Эта проблема становится еще проще, если у вас есть пользовательская таблица, в которой просто есть логическое поле, называемое чем-то вроде «IsAdmin», для определения разрешений пользователей, так как злоумышленнику не придется угадывать, для чего установлен идентификатор хорошей сочной роли.

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

Батон Пух Люки

Белый список отправленных вами данных

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

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

ASP.Net MVC поддерживает защиту от этого аналогично attr_accessible nil в Ruby On Rails , позволяя вашему контроллеру использовать атрибут  Bind [Bind (Include = * явные свойства здесь *)] при определении привязки входящей модели. Как и в случае с ROR, по умолчанию это не включено …

public ActionResult ChangeAccountDetails([Bind(Include = "Name,Email")] User model)
{
    ...
}

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

public ActionResult ChangeAccountDetails([Bind(Exclude = "SecurityRoles_Id")] User model)
{
    ...
}

Вы также можете использовать дополнительные перегрузки метода TryUpdateModel, чтобы определить, какие объекты связывать, или не связывать из FormCollection, если вы используете этот метод привязки / проверки модели.

Включают;

[Authorize]
[HttpPost]
public ActionResult ChangeAccountDetails(UserModel model)
{
    DAL.User dbUser = DAL.UserRepository.Load(x => x.Username = User.Identity.Name);
    string[] includeInModel = new[] { "EmailAddress" }; 
    if (TryUpdateModel(dbUser, includeInModel))
    {
        DAL.User.Save(dbUser);
        return RedirectToAction("ChangeDetailsSuccess");
    }
    ModelState.AddModelError("", "Something went wrong");
    return View(model);
}

Исключить;

[Authorize]
[HttpPost]
public ActionResult ChangeAccountDetails(UserModel model)
{
    DAL.User dbUser = DAL.UserRepository.Load(x => x.Username = User.Identity.Name);
    string[] excludeInModel = new[] { "SecurityRoles_Id" }; 
    if (TryUpdateModel(dbUser, string.Empty, null, excludeInModel))
    {
        DAL.User.Save(dbUser);
        return RedirectToAction("ChangeDetailsSuccess");
    }
    ModelState.AddModelError("", "Something went wrong");
    return View(model);
}

Кроме того, если у вас есть сложные типы в вашей модели представления (объекты Entity Framework и т. Д.), Вы должны абстрагировать слой данных в отдельный проект. Помимо того факта, что в ваших моделях никогда не должно быть сложных типов зависимостей, это гарантирует, что даже если вы случайно натолкнетесь на наземную шахту, вы не будете напрямую общаться с базой данных и сможете иметь немного больше контроля. Если вы новичок в этом дополнительном разделении и задумываетесь над всем дополнительным кодом, который вам придется написать, взгляните на что-то вроде AutoMapper, чтобы сделать это за вас (ASP.Net MVC 3 имеет метод TryUpdateModel, который Я использовал в своем первом примере, который делает это и для вас — хотя и с меньшим контролем).

public ActionResult ChangeAccountDetails(UserModel model)
{
    //create a new User object
    DAL.User u = new DAL.User();
    // map the incoming model to the user object
    AutoMapper.Mapper.Map(u, model);

    //
    // Some code that attaches our object 
    // to the current record in the DB that 
    // matches User.Identity.Name
    //
    DAL.User.Save(u);
    return RedirectToAction("ChangeDetailsSuccess");
}

Отключить любую исключительную сигнализацию

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

Machine.config

<configuration>
  <system.web>
    <!-- turn on retail mode -->
    <deployment retail="true" />
  </system.web>
</configuration>

Web.config

<configuration>
  <system.web>    
    <!-- turn debug mode off -->
    <compilation debug="false"/>    
    <!— turn On custom errors -->
    <customErrors mode="RemoteOnly"/>
    <!-- turn tracing off (this is set to false by default) -->
    <trace enabled="false"/>
  </system.web>  
</configuration>

Резюме

Хотя более старшие веб-разработчики, работающие над стеком ASP.Net MVC, могут подумать, что поднятые мной проблемы слишком гипотетичны и их легко преодолеть, я могу заверить вас из большого опыта работы с разработчиками всех видов уровней квалификации за эти годы, что эти Типичные проблемы «ты никогда не сделаешь это, это глупо», как правило, те, которые я вижу больше всего.

Проблемы GitHub были в большей степени связаны с тем, как ROR создает свои леса, но это просто показывает, что методы разработки, которые экономят время за счет написания кода для вас (например, привязка модели ASP.Net MVC), часто могут приводить к разрыву между разработчиком. и код его приложения — если крупная компания, такая как GitHub, может пропустить эти проблемы, это доказательство того, что эти проблемы являются более распространенным явлением, чем мы все хотели бы думать.

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