JavaScript XSS: получение данных от facebook и вконтакте

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

facebook vkontakte

О виджетах популярных социальных сетей знают практически все web мастера. Использовать их достаточно просто. В большинстве случаев нужно вставить блок с JavaScript кодом в шаблон страницы.

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

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

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

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

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

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

Условно, виджет можно разбить на две части.

1) Кнопка публикации.
2) Счетчик публикаций.

Рассмотрим их подробнее.

Кнопка публикаций

Здесь все предельно просто. В обоих случаях информация передаётся в параметрах GET запроса. Т.е. достаточно сформировать ссылки следующего вида.

Для facebook:

<a href="http://www.facebook.com/sharer.php?u=url&t=text" target="_blank">facebook</a>

Для вконтакте:

<a href="http://vkontakte.ru/share.php?url=url" target="_blank">вконтакте</a>';

Вместо url и text нужно подставить адрес и описание страницы, соответственно.

Счетчик публикаций

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

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

1) Любой запрос занимает какое-то время, которое прибавляется к общему времени формирования страницы. Кэширование в какой-то степени решает эту проблему (запросы будут отправляться не при каждом просмотре страницы), но не полностью.

2) При большом количестве запросов, социальная сеть может заблокировать ваш IP (зависит от политики социальной сети).

3) Создаётся дополнительная нагрузка на ваш сервер.

Второй вариант – отправлять запросы со стороны клиента, с помощью JavaScript.

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

Но, при реализации такого подхода, возникают две проблемы.

1) Встроенная защита браузеров от кросс-доменных запросов.

Т.е. вы не можете отправить AJAX запрос на другой домен, но можно вставить в страницу тег script и в его атрибуте src указать адрес нужного скрипта на другом домене. В результате браузер загрузит нужный вам скрипт и выполнит код, находящийся в нём.

При использовании jQuery задача решается с минимумом усилий. Например, так:

$.getJSON('http://vkontakte.ru/share.php?act=count&index=1&url=page_url&callback=?', function(response) {
	…
});

Обратите внимание на параметр callback=?. Если он указан, то jQuery вставит тег script внутрь страницы. Вообще параметр callback разработан для поддержки JSONP и указывает серверу, что ответ в JSON формате нужно передать в качестве параметра указанной функции. Имя этой функции jQuery формирует автоматически и подставляет вместо знака вопроса.

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

2) Формат ответа социальной сети.

С facebook всё отлично. Эта социальная сеть поддерживает JSONP и если мы отправим запрос вида.

$.getJSON('http://graph.facebook.com/' + encodeURI(page_url)
		+ '&callback=?', function(response) {
	if (response.shares !== undefined) {
		fb_counter = response.shares;
	}
	$('#facebook_count').html(response.shares);
});

То получим следующий ответ.

jQuery16105957901661749929_1306057803097({
   "id": "http://www.smashingmagazine.com/",
   "shares": 5454
});

jQuery16105957901661749929_1306057803097 – имя функции, которое сгенерировала jQuery.
В первом параметре этой функции передаётся строка в JSON формате с адресом страницы и количеством публикаций.
В результате, в обработчик запроса будет передан JS объект с нужными данными. В этом примере он называется response, т.е. получить количество публикаций ссылки можно с помощью вызова response.shares.

В случае с вконтаке всё сложнее. Эта социальная сеть JSONP не поддерживает и на запрос

$.getJSON('http://vkontakte.ru/share.php?act=count&index=1&url='
	+ encodeURI(page_url) + '&callback=?', function(response) {});

отвечает следующей строкой:

VK.Share.count(1, 9);

Естественно, это приводит к возникновению ошибки

VK is not defined

Вообще ответ сервера вконтакте – это обычный вызов метода count, присвоенного параметру Share объекта VK. Откуда берется этот объект? Его создаёт скрипт, который нужно подключить при использовании стандартного виджета. Но этот скрипт нам не нужен, т.к. виджет мы не используем, а количество публикаций ссылки нужно вставить в заранее подготовленный блок о котором стандартный скрипт ничего не знает.

Т.е. нам нужен собственный объект VK с соответствующими параметрами. Создать его совсем не сложно.

var VK = {
    Share: {
        count: function(value, count) {
            $('#vkontakte_count').html(count);
        }
    }
}

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

Обратите внимание – этот код должен находится в глобальной области видимости.

Привожу весь код целиком.

Страницы со счетчиками

<!DOCTYPE html>
<html>
<head>
    <title>Счетчики</title>
    <meta charset="utf-8" />
</head>

<body>
    <div>Facebook <span id="facebook_count"></span></div>
    <div>ВКонтакте <span id="vkontakte_count"></span></div>
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script src="social_counters.js"></script>
</body>
</html>

Скрипт social_counters.js

jQuery(document).ready(function($) {
    var fb_counter = 0;
    var vk_counter = 0;
    var page_url = 'http://www.smashingmagazine.com/';

	$.getJSON('http://vkontakte.ru/share.php?act=count&index=1&url='
		+ encodeURI(page_url) + '&callback=?', function(response) {});
	
	
	$.getJSON('http://graph.facebook.com/' + encodeURI(page_url)
			+ '&callback=?', function(response) {
		if (response.shares !== undefined) {
			fb_counter = response.shares;
		}
		$('#facebook_count').html(response.shares);
	});
});

var VK = {
    Share: {
        count: function(value, count) {
            $('#vkontakte_count').html(count);
        }
    }
}

В заключение пара замечаний

1) Поддержка JSONP значительно упрощает жизнь разработчикам и в конечном итоге способствует популяризации социальной сети или сервиса.

2) Отсутствие этой поддержки не означает, что вы не сможете интегрировать ваш сайт с данной социальной сетью. Главное, чтобы была возможность получить нужные данные в какой-нибудь форме. Естественно, при этом придётся написать какое-то количество дополнительного кода.

Если вы заметили ошибку или знаете более удачное решение данной проблемы, пишите, будет интересно обсудить.
Успехов!

Интересно почитать

Не знаете, что подарить подруге на день рождения? Фотоальбом с ее лучшими снимками!

  • Спасибо. Интересные мысли, попробую это прикрепить еще к комментариям от вконтакте…

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

    А так спасибо за исследование. Возможно пригодится.

    • Строго говоря, дополнительные запросы к вашему серверу могут повлиять на работоспособность сайта в целом 😉

    • Так вот в том-то и дело, чтобы избежать лишних запросов к собственному серверу.

  • Forsoleg

    Спасибо большое за статью! очень выручила сегодня! но яб еще добавил твиттер) вдруг так же кому пригодится.

    $.getJSON('http://urls.api.twitter.com/1/urls/count.json?url=' + encodeURI(page_url) + '&callback=?', function(response) { if (response.count !== undefined) { tw_counter = response.count; } $('#twitter_count').html(response.count); });

    ну размеется добавив вначале переменную и соответствующий див)

  • Пост интересный, но причем тут XSS? =)

    • Формально вы правы, XSS — тип уязвимости. Но если смотреть шире :), то речь идет об выполнении скриптов с других ресурсов. Эти скрипты не обязательно наносят вред.

  • Отличная идея про использование подложного объекта VK для обработки ответа.
    Столкнулся с подобной проблемой, но пока решить не получилось:

    Получаю ответ вида: key1{param1=1,param2=1.2.3},key2{param3=0}
    Именно так, без кавычек и двоеточий.

    Возможно ли создать объекты JavaScript, которые корректно отработают такую строку?

    • нет, стандартными методами JS Вы такую строку не обработаете (я имею в
      виду JSON.parse), потому что приведённый Вами формат не является
      валидным JSON. Т.е. придётся написать свой парсер для обработки такого
      ответа.

      Я бы делал парсинг в 2 этапf:

      1) выбрать подстроки вида key{…}

      2) в каждой подстроке найти название ключа (текст до первой фигурной скобки) и пары параметр=значение

      • Антон

        Не могли бы Вы привести пример

        • Регулярное выражение для подстроки вида key{…} будет выглядеть примерно так:
          (w+){(.*?)}
          для пары параметр=значение:

          (w+)s*?=s*?([^,]+)

  • Innokenty Longway

    Спасибо! С vk помогло.