Pull to refresh

Ещё одна причина переходить на SSL, или 133 КБ не лишние

Information Security *Website development *Development of mobile applications *
КДПВ



Вечерело


Мы с товарищем, сделали простенький тест (github) на проверку доступности data-uri в браузерах. Выглядит он следующим образом:



В textarea javascript'ом вставляется navigator.userAgent. В этот момент я не знаю, что меня стукнуло в голову, но вместо DOMContentLoaded, я написал . По-быстрому проверив корректную работу в десктопных браузерах и на нескольких мобильных устройствах, подключённых к интернету через wi-fi, мы успокоились и разошлись по домам.

Солнце продиралось сквозь занавески


Утром, в полупустом вагоне метро, я как всегда открыл браузер на своем телефоне, на котором со вчерашнего вечера была открыта тестовая страничка. Сказать, что я удивился, когда я не увидел вывода userAgent
внутри textarea — ничего не сказать.

Добравшись до компьютера, решил потратить немного времени на поиск проблемы. Запустив страничку на десктопе и в эмуляторе, я ничего не заметил. Открыл на телефоне. Чудеса! Всё работает.

Увидев включённый значок wi-fi, я начал догадываться о причинах проблемы. Я отключил wi-fi, подключил телефон к компьютеру и начал дебаг с помощью веб-инспектора десктопного сафари.

Горячий кофе и приятный вкус дебага




Уоу-уоу, полегче, подумал я. Откуда всё это могло взяться в шести строчках JS'а?

А вот откуда:



Отличился наш любимый оператор Beeline, с которого я ещё не до конца успел слезть. На небольшую тестовую страничку внезапно прилетело ~133КБ паразитного траффика.

Anchor.js расположен по адресу toolbar.beeline.ru/ets/scripts/Anchor.js, который доступен только из сотовой сети Билайна. Для всех желающих посмотреть на стиль кодирования на JS в Билайне, я подготовил ссылку — gist.

Сколько вешать в граммах?


Что же входит в эти 133 КБ:
* 91 КБ jQuery v1.7.2;
* 42 КБ самописного кода.

Видно, что никто не заморачивался даже использованием CDN для jQuery. Ну ОК. Смотрим дальше на самописный код.

Первые 914 строк — один большой if:

if (document.getElementById('toolbar') == null)
{
    //...много кода
}


Дальше немного хелперов с упором на мобильные устройства от Apple:
function getIOSVersionNumber(){}
function isIt(theDevice){}
function isMobile(){}
function isIPhone(){}
function isIPad(){}
function getBodyZoomLevel () {};
function isNumberPercentageBased(number) {}
function getVisualViewportInfo () {};


We need to go deeper


Испектируем код про тулбар:

(function(jQFrm){
    if (window.XDomainRequest) {
        // много кода
    }
})(jQFrm);


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

Идём дальше:

var ets_scripts = jQFrm('[name="ets-anchor"]');

jQFrm(window).resize(function()
{
    resizeIframe();
});

if (ets_scripts.length == 1)
{
    // много кода
}


Встречаем второй большой if и первое вмешательства в обработку событий на произвольном мобильном сайте.
По этому селектору находится этот самый Anchor.js, который вставляется в момент перехвата ответа от веб-сервера мобильному браузеру.



Продолжаем чтение по диагонали:


(function()
{
    var msgHandler = function(e)
    {
        var ets_frame = document.getElementById('toolBarPcFrame');
        if (e.origin == 'http://toolbar.beeline.ru')
        {
            // 500 строк кода
        }
        else
        {
            //alert(e.origin);
            throw new Error('Origin domain is not allowed.');
        }
    }
    // listen for the message event
    window.addEventListener('message', msgHandler, false);
})();


Находим механизм общения со встроенным iframe через window.postMessage.

Опять хелперы и...:
function GetWidth() {}
function GetHeight(){}
function resizeIframe(){}
window.onpageshow = function(evt){};
window.onorientationchange = function(){};
window.onload = function(){
    //etsAppMain functions (search application)
    if (InitParamForScroll)
    {
        InitParamForScroll();
    }
};
function isLandscape(){}
function orientation_changed(actionId){}
if (isMobile()){}


Oh, wait. Вот и причина изначальной проблемы — подмена window.onload

window.onload = function()
{
    //etsAppMain functions (search application)
    if (InitParamForScroll)
    {
        InitParamForScroll();
    }
};


Возврвщаясь к нашему первому if'у.
$(document).ready(function()

$('html').append('<div id="toolbar" tabindex="-1" style="border-width:0px;outline-width:0px;"></div>');

var currentTime = new Date();
/*var pathname = window.location.href;
 var  screenWidth = screen.width;*/

var ifrm = document.createElement("IFRAME");
ifrm.setAttribute("id", "toolBarPcFrame");
/*ifrm.setAttribute("src", "http://toolbar.beeline.ru/ets/ToolBarPcServlet?screenWidth="+screenWidth+"&url="+pathname);*/
ifrm.setAttribute("src", "http://toolbar.beeline.ru/ets/ToolBarPcServlet");
ifrm.setAttribute("allowtransparency", "true");
ifrm.setAttribute("background-color", "transparent");
ifrm.style.position = "fixed";
ifrm.style.zIndex = 9999999999;
// много кода


В момент инициализции страницы, паразитный скрипт вставляет в конец нашего html div, а уже внутри создаёт iframe на toolbar.beeline.ru/ets/ToolBarPcServlet. Обратите внимания на CSS-свойства position, zIndex. Это чтобы наверняка было заметно ;)

Режем по-живому


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

document.addEventListener('DOMContentLoaded', function() {
    var toolbar = document.getElementById('toolbar'),
        iframe = toolbar && toolbar.firstElementChild;
    if (iframe && /beeline/.test(iframe.getAttribute('src'))) {
        toolbar.parentNode.removeChild(toolbar);
    }
});
setTimeout(function() {
    var script = document.querySelector('script[name=ets-anchor]');
    if (script) {
        script.parentNode.removeChild(script);
    }
}, 0);

Может я подумаю ещё пару минут чуть позже и придумаю более элегантное решение. Смотрите в gist :)

Минутка морали.


Кроме капитанского "не использовать onload", могу дать несколько полезных советов:
  • если вы разработчик, по возможности, используйте SSL на ваших сайтах, чтобы _никто_ не мог перехватить ваш траффик;
  • если вы подключили на свою страницу 90КБ jQuery кода, то используйте его, а не пишите потом document.getElementById;
  • если вы оператор связи, то никогда не включайте такие услуги "Мини-Кабинет"







P.S. Этот пост ни в коем случае не наезд на компанию Билайн. Я имел положительный опыт решения проблем с помощью их твиттера Beeline_Rus. Надеюсь, что этот пост поможет хорошим людям внутри компании вырезать эту странную услугу на корню и сделать лично меня и миллионы абонентов чуточку счастливей.



update
Купил на свой домен SSL-сертфикат. Теперь никакая зараза не пройдёт. Также продолжаю переписку с Билайном, на тему отключения услуги Toolbar совсем и всем.

update, 6 октября 2014
Ещё 18-го сентября, мне пришло письмо от pomogite@beeline.ru следующего содержания:
Роман, здравствуйте!

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

Я, конечно же, пошёл проверять. На удивление, никаких лишних сетевых запросов/вставок в HTML'е я не обнаружил. Сразу написал об этом в твиттере, а вот про хабр забыл :(

Теперь вот исправляюсь, перепроверив ещё раз. Кто-нибудь проверит у себя? ;)
Tags:
Hubs:
Total votes 202: ↑194 and ↓8 +186
Views 87K
Comments Comments 79