Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Кросс-доменные ограничения

Оригинал:The Same-Origin Policy
Авторы: Eunsuk Kang, Santiago Perez De Rosso, and Daniel Jackson,
Дата публикации: July 12, 2016
Перевод: Н.Ромоданов
Дата перевода: январь 2017 г.

Это четвертая часть статьи "Кросс-доменные ограничения".
Перейти к
началу статьи.

Свойства безопасности

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

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

Свойства потока данных

Для того, чтобы более точно задать свойства безопасности, нам сначала нужно определить, что означает для части данных поток данных из одной части системы в другую. В нашей модели мы до сих пор описывали взаимодействие между двумя конечными точками, которое осуществляется через вызовы; например, браузер взаимодействует с сервером, делая запросы HTTP, а скрипт взаимодействует с браузером с помощью обращения к операциям API браузера. Интуитивно понятно, что во время каждого вызова, часть данных может перенаправляться из одной конечной точки к другой в виде аргумента или возвращаемого значения вызова. Чтобы представить это, мы вводим в модель понятие DataflowCall и ассоциируем каждый вызов с набором полей данных аргументов args и возвращаемых значений returns:

sig Data in Resource + Cookie {}

sig DataflowCall in Call {
  args, returns: set Data,  --- аргументы и возвращаемые данные этого вызова
}{
 this in HttpRequest implies
    args = this.sentCookies + this.body and
    returns = this.receivedCookies + this.response
 ...
}

Например, во время каждого вызова типа HttpRequest клиент пересылает sentCookies и body на сервер, и принимает receivedCookies и response в качестве возвращаемых значений.

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

sig DataflowModule in Endpoint {
  -- Set of data that this component initially owns
  accesses: Data -> Time
}{
  all d: Data, t: Time - first |
     -- Эта конечная точка может получить только к части данных "d" в момент времени "t" только когда
    d -> t in accesses implies
      -- (1) Уже есть доступ на предыдущем временном шаге, или
      d -> t.prev in accesses or
      -- есть некоторый вызов "c", который завершается в "t", такой как
      some c: Call & end.t |
        -- (2) конечная точка получает вызов "c", в котором "d" в качестве его аргумента или 
        c.to = this and d in c.args or
        -- (3) конечная точка посылает вызов "c", который возвращает "d" 
        c.from = this and d in c.returns 
}

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

sig DataflowCall in Call { ... } {
  -- (1) Любой аргумент должен быть доступен отправителю
  args in from.accesses.start
  -- (2) Любые данные, возвращаемые этим вызовом, должны быть доступны получателю
  returns in to.accesses.start
}

Теперь, когда у нас есть средства для описания потока данных между различными частями системы, мы (почти) готовы сформулировать что такое будет свойство безопасности, о котором мы заботимся. Но запомните, что конфиденциальность и целостность являются контекстно-зависимыми понятиями; эти свойства имеют смысл только тогда, когда мы можем говорить о некоторых агентах в системе, которые могут быть доверенными (или злонамеренными). Точно так же, не вся информация одинаково важна: нам нужно различать элементы данных, потерю которых мы считаем критической:

sig TrustedModule, MaliciousModule in DataflowModule {}
sig CriticalData, MaliciousData in Data {}

Тогда свойство конфиденциальности можно сформулировать как утверждение о потоке критических данных в ненадежных частях системы:

// Ни один вредоносный модуль не должен иметь доступ к критически важным данным
assert Confidentiality {
  no m: Module - TrustedModule, t: Time |
    some CriticalData & m.accesses.t 
}

Свойство целостности является двойственным свойству конфиденциальности:

// Никакие вредоносные данные никогда не должны попасть в доверенный модуль
assert Integrity {
  no m: TrustedModule, t: Time | 
    some MaliciousData & m.accesses.t
}

Модель потоков

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

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

Проверка свойств

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

check Confidentiality for 5

Например, при проверке в модели нашего приложения-примера свойства конфиденциальности, анализатор генерирует сценарий, изображаемый на рис.17.4 и рис.17.5, в котором показывается, как скрипт EvilScript может получить доступ к части критически важных данных (MyInboxInfo).

Рис.17.4. Контрпример конфиденциальности в момент времени 0

Рис.17.5. Контрпример конфиденциальности в момент времени 1

В этом контрпримере показаны два шага. На первом шаге (рис.17.4) скрипт EvilScript, выполняемый внутри баннера AdBanner из домена EvilDomain, считывает содержимое страницы InboxPage, которая изначально была взята из домена EmailDomain. На следующем шаге (рис.17.5) скрипт EvilScript посылает тот же самый контент (MyInboxInfo) на сервер EvilServer, сделав для этого вызов XmlHtttpRequest. Суть проблемы в том, что скрипт, выполняемый под одним доменом, может прочитать содержимое документа из другого домена; как мы увидим в следующем разделе, это именно один из сценариев, для предотвращения которых предназначены кросс-доменные ограничения.

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

Рис.17.6. Еще одно нарушение конфиденциальности

В этом случае, вместо того, чтобы читать содержимое страницы inbox, скрипт EvilScript напрямую делает запрос GetInboxInfo на сервер EmailServer. Обратите внимание, что в запросе есть куки (MyCookie), которые позволяют получать доступ к домену, в котором находится целевой сервер. Это потенциально опасно, поскольку если куки используются для идентификации пользователя (например, сессионные куки), то скрипт EvilScript может с успехом выступить в роли пользователя и обмануть сервер, предоставив ему личные данные пользователя (MyInboxInfo). Здесь проблема вновь связана с либеральными методами, в которых скрипт может использоваться для доступа к информации в различных доменах, а именно, что скрипт, который выполняется под одним доменом, может сделать запрос HTTP на сервер в другом домене.

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

Перейти к следующей части статьи.

Перейти к началу статьи.