Статьи

Анализ мобильной платформы перенаправления и запутанных регулярных выражений

Я не часто читаю Haaretz , но время от времени появляются статьи, которыми друзья делятся на Facebook или попадают в результаты поиска — и я оказываюсь на сайте Haaretz. Довольно часто это происходит на моем мобильном телефоне — и каждый раз я оказываюсь перенаправленным на очень примитивную версию сайта. Сравните для себя:

образобраз
(Снимок экрана справа, полученный при изменении пользовательского агента в сборке Chrome Canary . Очень хорошая встроенная функция.)

Мне было любопытно, какие критерии использовались веб-сайтом Haaretz для этого перенаправления, и я начал обнюхивать трафик с помощью Fiddler . После того, как большая часть главной страницы Haaretz была загружена, браузер неожиданно выдал запрос на g.watap.net/w2w/haaretz , который выдает не одно, а два перенаправления 302 и в конечном итоге попадает в поврежденную мобильную версию.

образ

Интересно, что я попробовал более одного мобильного пользовательского агента, и полученный мобильный веб-сайт был почти таким же (поэтому я получаю такой же опыт с iPhone, Android или телефоном с функцией). Я считаю, что это плохой выбор от имени Haaretz, поэтому я начал немного расследовать.

Я начал с запроса whois на watap.net и обнаружил, что он зарегистрирован через Go Daddy для PassCall Advanced Technologies. Затем я обратил свое внимание на PassCall и обнаружил на их веб-сайте, что они предоставляют платформу, которая адаптирует существующие веб-сайты для мобильного просмотра . Действительно, я нахожу Haaretz в их списке клиентов . Насколько я могу судить, все клиенты — это израильские компании, а хост g.watap.net использует израильский IP-адрес, который, вероятно, находится у NetVision, крупного израильского интернет-провайдера.

Каков точный процесс, используемый PassCall, чтобы определить, следует ли перенаправить мой браузер на неработающую мобильную версию? Я был достаточно смел, чтобы начать читать ~ 8500 строк HTML-кода и сценария, который является главной страницей Haaretz. Очень близко к началу есть уведомление об авторских правах PassCall с минимизированным сценарием. Я не буду вставлять все это, но вот начало:

var passcall_pcmdt={i$i:function(){try{eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('3 f=D(p,q,o,2,e){4(6.7.a(\'5=0\')>-1)m;3 b=s r();b.t(b.n()+B);3 d=s r();d.t(d.n()+1);4(9.c.a(\'y=1\')>-1){6.7=\'5=0; j=/; i=\'+d.l();m}E 4(9.c.a(\'G=1\')>-1){6.7=\'5=0; j=/; i=\'+b.l();m}3 8=6.7.a(\'5=1\')>-1;4(!8){3 v=p.h(k.g);3 x=q.h(k.g);3 u=!o.h(k.g);8=(v||x)&&u;6.7=\'5=\'+F(I(8))+"; j=/; i="+b.l()}4(8){2=9.c.w(9.H,2);4(C e!=\'z\'&&e.A){2+=2.a(\'?\')>-1?\'&\':\'?\';2=2.w(\'?&\',\'?\');2+=e}9.c=2}}',45,45,'||r\x65\x64\x69rt\x6f|\x76\x61r|\x69\x66|___\x70\x63\x6d\x64\x74___|\x64\x6f\x63\x75men\x74|\x63\x6f\x6f\x6b\x69\x65|\x72\x65\x64\x69\x72|l\x6f\x63\x61ti\x6fn|\x69n\x64\x65x\x4ff||\x68\x72\x65\x66|\x62\x62|p\x61r\x61ms||u\x73\x65rAge\x6e\x74|\x74\x65\x73\x74|\x65\x78\x70\x69\x72\x65\x73|\x70a\x74h|\x6e\x61\x76\x69\x67\x61t\x6fr|toU\x54\x43S\x74\x72in\x67|\x72e\x74\x75\x72\x6e|g\x65\x74D\x61te|\x723|\x721|r2|\x44\x61\x74\x65|\x6e\x65\x77|\x73\x65\x74D\x61\x…

Я перенес эту красоту на jsbeautifier.org, где она приобрела более красивую форму Вот первая часть, украшенная:

var passcall_pcmdt = {
    i$i: function () {
        try {
            eval(function (p, a, c, k, e, d) {
                e = function (c) {
                    return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
                };
                if (!''.replace(/^/, String)) {
                    while (c--) {
                        d[e(c)] = k[c] || e(c)
                    }
                    k = [function (e) {
                        return d[e]
                    }];
                    e = function () {
                        return '\\w+'
                    };
                    c = 1
                };
                while (c--) {
                    if (k[c]) {
                        p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
                    }
                }
                return p
            }(…

Хорошо, это, очевидно, распаковщик — там даже написано, что функция (p, a, c, k, e, d) прямо здесь. Спасибо за подсказку. Таким образом, первая часть — это слегка уменьшенный распаковщик, и строки в шестнадцатеричном коде (здесь не показаны), вероятно, являются реальным кодом. Вместо того, чтобы пытаться запустить алгоритм распаковки ручкой и бумагой, я просто поставил точку останова в начале скрипта и начал входить и выходить, пока не получил красивую функцию с именем f , которая выполняет интересную часть:

var f = function(r1, r2, r3, redirto, params) {
  if (document.cookie.indexOf('___pcmdt___=0') > -1)
    return;
  var b = new Date();
  b.setDate(b.getDate() + 360);
  var bb = new Date();
  bb.setDate(bb.getDate() + 1);
  if (location.href.indexOf('snopcmdt=1') > -1) {
    document.cookie = '___pcmdt___=0; path=/; expires=' + bb.toUTCString();
    return
  } else if (location.href.indexOf('nopcmdt=1') > -1) {
    document.cookie = '___pcmdt___=0; path=/; expires=' + b.toUTCString();
    return
  }
  var redir = document.cookie.indexOf('___pcmdt___=1') > -1;
  if (!redir) {
    var b1 = r1.test(navigator.userAgent);
    var b2 = r2.test(navigator.userAgent);
    var b3 = !r3.test(navigator.userAgent);
    redir = (b1 || b2) && b3;
    document.cookie = '___pcmdt___=' + parseInt(Number(redir)) + "; path=/; expires=" + b.toUTCString()
  }
  if (redir) {
    redirto = location.href.replace(location.host, redirto);
    if (typeof params != 'undefined' && params.length) {
      redirto += redirto.indexOf('?') > -1 ? '&' : '?';
      redirto = redirto.replace('?&', '?');
      redirto += params
    }
  location.href = redirto
  }
}

Обратите внимание, что это больше не запутано и отлично читается. Сценарий начинается с проверки наличия файла cookie, который инструктирует его о необходимости перенаправления на мобильный телефон или нет. Жирным шрифтом выделены интересные части — это то, что мы получаем, если нам нужно принять новое решение, — а затем сам редирект просто заменяет location.href новым местоположением. Вся логика перенаправления или нет сводится к трем регулярным выражениям ( r1 , r2 , r3 ). Давайте посмотрим на эти регулярные выражения. Вот r1 :

^(((A|3|Q)l(v|5|c)a(t|3|2)e(l|3|x))|((X|6|E)Z(O|2|j)S)|((9|H|6)D(q|2|h)_(h|3|T))|((H|7|1)D_m(i|0|j)n)|((9|I|6)C(v|1|o)p(q|8|p)i(q|e|j)d(v|8|F)r(v|o|q)m(4|P|9)a(s|2|0)s(c|4|X)a(v|7|l)l(q|7|C)o(q|6|d)e(j|4|0)h(a|6|0)a(r|0|q)e(t|5|x)z)|((v|8|L)G(E|7|3)?[-/_])|((0|M|6)a(u|3|Q)i B(r|7|x)o(w|4|0)s(q|6|e)r)|((P|0|h)C(L|2|q)[4-6][4-6])|((q|6|S)E(h|C|Q)-)|((v|S|j)G(H|2|3)-)|((S|0|2)I(E|X|h)-)|((j|4|S)K_)|((q|6|S)O(j|4|N)I(M|x|q))|((8|S|4)e(0|n|4)d(j|o|Q))|((j|8|T)e(j|l|X)i(4|t|6))|((h|p|q)o(q|r|5)t(q|a|h)l(q|m|v)m(h|8|m)))

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

((A|3|Q)l(v|5|c)a(t|3|2)e(l|3|x))

Что это может быть? Очевидно, это «Alcatel»:

((A|3|Q)l(v|5|c)a(t|3|2)e(l|3|x))

Как насчет этого парня?

((h|p|q)o(q|r|5)t(q|a|h)l(q|m|v)m(h|8|m))

Это «portalmmm», который, очевидно, является мобильным пользовательским агентом, используемым мобильными браузерами i-mode . Наконец, что это:

((9|I|6)C(v|1|o)p(q|8|p)i(q|e|j)d(v|8|F)r(v|o|q)m(4|P|9)a(s|2|0)s(c|4|X)a(v|7|l)l(q|7|C)o(q|6|d)e(j|4|0)h(a|6|0)a(r|0|q)e(t|5|x)z)

Довольно долго быть мобильным пользовательским агентом. Действительно, он становится ICopiedFromPasscallCode? Haaretz — который является элементарным механизмом защиты от копирования.

Сразу скажу, что r2 ничем не отличается:

((a|3|Q)n(v|5|d)r(o|3|2)i(d|3|x))|((X|6|b)l(a|2|j)c(k|X|q)B(4|e|8)r(r|Q|0)y)|((G|x|h)T-P(v|9|1)0(q|0|x)0)|((H|7|Q)T(q|6|C))|((Q|6|H)u(6|a|5)w(X|5|e)i[u/-])|((i|1|0)p(h|5|a)d)|((q|i|v)p(h|9|0)o(h|6|n)e)|((j|2|m)o(5|t|8)o(4|r|8)o(l|2|q)a)|((4|M|7)O(T|7|0)[-_])|((8|n|6)o(k|3|x)i(h|2|a))|((s|Q|1)o(x|4|n)y(x|8|e)r(q|8|i)c(s|5|Q)s(o|3|Q)n)|((h|6|s)a(4|m|5)s(h|u|x)n(g|6|3))|((j|3|P)a(l|X|h)m)|((x|5|p)h(h|7|i)l(i|7|2)p(3|s|5))|((v|U|Q)P.(v|6|B)r(o|0|j)w(s|7|Q)e(r|3|q))|((w|5|2)i(7|n|5)d(q|3|o)w(6|s|4) (((9|p|5)h(o|X|q)n(e|x|q))|((h|c|q)e)))|((8|I|7)E(X|8|M)o(h|5|b)i(h|6|l)e)|((9|V|7)o(d|Q|j)a(f|8|Q)o(X|4|n)e)|((X|9|o)p(q|e|j)r(h|a|j) (v|m|q)o(v|b|x)i)|((4|o|5)p(e|0|2)r(v|4|a) (2|m|5)i(v|9|n)i)|((q|7|s)y(q|m|0)b(q|6|i)a(n|0|q))|((X|8|1)o(o|X|0)m)|( P(q|r|h)e[/])

Это приводит к таким вещам, как «android», «ipad», «iphone», «windows phone», «Xoom» и многим другим. И еще есть r3 , который лишит агента пользователя возможности перенаправлять на мобильный сайт:

((i|3|Q)p(v|5|a)d)|((q|9|V)i(7|e|8)w(j|P|X)a(Q|8|d))|((M|7|Q)Z(q|6|9)0(1|X|q))|((q|8|G)T-P(q|2|1)0(3|0|8)0)|((q|6|G)T-P(v|7|Q)5(v|8|0)0)|((j|5|X)o(3|o|5)m)

… И здесь мы снова имеем «ipad» и «Xoom» , что довольно глупо, потому что мы только что видели их в r2 . Вероятно, слой запутывания мешает разработчикам PassCall вносить изменения 🙂

В общем, вот (частичный) набор пользовательских агентов, которые PassCall будет перенаправлять на мобильный сайт, и (частичный) набор пользовательских агентов, которые они не будут (список основан на том, что я видел на веб-сайте Haaretz 31 января 2012 г.):

Перенаправляет, если начинается с : Alcatel, ICopiedFromPasscallCode? Haaretz, LGE-, Maui Browser, SEC-, SGH-, SIE-, SK-, SONIM-, Sendo-, Telit-, portalmmm

Перенаправит, если содержит : Android, BlackBerry, GTP-, HTC, Huawei, Ipad, Iphone, Motorola, MOT-, Nokia, SonyEriccson, Samsung, Palm, Philips, UP.Browser, Windows Phone, Windows CE, IEMobile, Vodafone, опера моби, опера мини, симбиан, Xoom, Pre

Не будет перенаправлять, если содержит : Ipad, ViewPad, MZ601, Xoom

Мне любопытно узнать о других планшетах, таких как Samsung Galaxy Tab, соответствующих критериям мобильного устройства. Действительно, с помощью пользовательского агента Galaxy Tab (« Mozilla / 5.0 (Linux; U; Android 3.0; xx-xx; GT-P7100 Build / HRI83) AppleWebkit / 534.13 (KHTML, как Gecko) Версия / 4.0 Safari / 534.13 ») мы получают мобильную версию. И самая раздражающая вещь? Я не вижу способа в мобильной версии переключиться обратно на настольную версию, если захочу. И это запасной вариант номер один, который вы должны иметь, если используете тупые регулярные выражения, чтобы определить, какой веб-сайт мне показывать.

Подвести итоги:

  • Haaretz использует передовые технологии PassCall для перенаправления своих мобильных посетителей на поврежденную мобильную версию веб-сайта
  • PassCall использует набор регулярных выражений, чтобы определить, представляет ли пользовательский агент пользователя мобильное устройство и выполняет ли безусловное перенаправление
  • PassCall обеспечивает одинаковые возможности мобильного телефона для iPhone 4S 2011 года и функционального телефона Nokia 2005 года.
  • Похоже, что нет способа вернуться к настольной версии с тупой мобильной версии.

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