XSS с использованием JSONP и jQuery

Владимир | | Ajax, JavaScript, PHP, Web разработка.

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

Как несложно догадаться, JSONP представляет собой расширение формата JSON. Расшифровывается эта аббревиатура так — JavaScript Object Notation with Padding, т.е. JSON с дополнением.

Об этом дополнении чуть ниже, а сейчас, напомню, что представляет собой формат JSON и зачем он нужен.

Если не вникать в нюансы, JSON представляет собой формат записи JavaScript объектов в виде обычной текстовой строки. Т.е. вы можете отправить такую строку в ответ на AJAX запрос, а затем преобразовать её на стороне клиента в объект с данными.

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

Примечание. Я уже публиковал несколько постов о JSON с примерами, например, этот и этот.

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

Для того, чтобы понять, что это даёт, взгляните на следующий JavaScript код:

function test(data) {
	alert(data.var1);
}
test({"var1":"123"});

Последняя строка записана в формате JSONP. И в тоже время она представляет собой JavaScript функции! Если отбросить текст в начале строки (test) и круглые скобки, то получим строку в формате JSON.

В результате работы этого скрипта будет вызвана функция test и в параметре data она получит объект с вашими данными. Т.е. вы увидите текст «123».

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

Проблема в том, что Same Origin Policy запрещает отправлять запросы ко всем ресурсам кроме того, с которого был загружен сам скрипт. Для обхода этого ограничения используется вставка дополнительного тега script в заголовок страницы. Атрибут src этого тега должен указывать на домен, к которому вы хотите отправить запрос.

Примечание. Подробнее о вариантах отправки XSS запросов можно почитать в статье «XSS и Same Origin Policy».

Иными словами, вы явно загружаете скрипт с нужного домена. А из этого скрипта можно отправлять запросы к этому домену, не нарушая Same Origin Policy.

Теперь разберем, что нам даёт JSONP, и тут же рассмотрим его поддержку в jQuery. Если кратко – с его помощью мы можем контролировать процесс загрузки данных и вызвать собственную JavaScript функцию после его завершения.

Самое главное, нужно четко понимать, что формат JSONP должен поддерживаться на стороне сервера. Ведь разработчик серверного API не может заранее определить, как будет называться JavaScript функция, обрабатывающая полученные данные. Т.е. заранее неизвестно какой текст нужно вставлять перед открывающей круглой скобкой.

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

1) Клиентский скрипт создает тег script и в его атрибуте src указывает адрес скрипта вместе с названием функции-обработчика в одном из параметров. Например,

<script src=”http://anothersite.domen/jsondata.php?callback=test”></script>

Здесь, test – имя функции-обработчика, а jsondata.php – серверный скрипт, который создает строку в формате JSONP.

2) Браузер загружает скрипт, т.е. выполняет запрос, указанный в атрибуте src.

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

4) После загрузки данных, клиентский скрипт выполняет функцию-обработчик и в качестве параметра передает ей данные, полученные от сервера.

5) Последний этап – удаление уже не нужного тега script.

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

Оценить всю пользу от jQuery проще всего на примере.

Клиентский скрипт.

$.getJSON("http://www.wp25.l/jsondata.php?callback=?",
	function(data) {
		alert(data.myval);
	});

Серверный скрипт.

<?php
$callback = $_GET['callback'];
echo $callback.'('."{'myval':'1234567890'}".')';
?>

Т.е. чтобы использовать JSONP вам нужно вызвать функцю getJSON. В её первом параметре указать запрос, но вместо имени функции-обработчика ставим знак вопроса. Во втором параметре передаём саму функцию-обработчик. Все остальное jQuery делает за вас.

Серверный скрипт формирует JSON строку с данными (в реальном приложении, эти данные, скорее всего, будут получены из базы) и добавляет к ней значение, переданное в параметре callback и круглые скобки.

Чтобы наглядно проиллюстрировать работу jQuery я остановил выполнение скрипта на вызове функции alert и сделал скриншот.

Как видите, в заголовке страницы появился дополнительный тег script, а знак вопроса (в параметре callback) изменился на jsonp1234988224763. После завершения работы функции тег script исчезает.

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

Действительно, принцип работы этих функций (getScript и getJSON) очень похож. Точнее не отличается реализация XSS запросов.

Но есть принципиальная разница. Функция getScript получает JavaScript код и выполняет его. getJSON – пытается преобразовать полученные данные в JavaScript объект. Если нарушить формат – возникнет ошибка.

Теперь представьте ситуацию. Вы используете чей-нибудь сервис и загружаете данные с помощью getScript. Администрация этого сервиса может вставить на вашу страницу любой JS код. Например, им ничто не помешает получить файлы cookies ваших посетителей.

Работа с getJSON выглядит безопаснее. Во всяком случае, данные будут соответствовать формату, и вы сами решаете, как их использовать. Можно, например, вставить дополнительные проверки.

Хотя, возможно, и JSON формат имеет уязвимости. Правда, я о них не слышал 😉 Если есть идеи, пишите, мне будет интересно почитать 😉