Глава 1. Введение

JavaScript всегда был моей страстью, я очарован способами, которые помогают мне лучше понимать JS. Вы часто можете видеть, как я пишу в Твиттере о способах вызова функций без круглых скобок, безумных векторах XSS (cross-site scripting, сценариев перекрёстных сайтов) и общих способах найти более глубокие варианты понимания конкретной функции. Меня часто спрашивают, как можно применять твит для обхода WAF или для эксплойта браузера. Мне это не важно, конечно, вы могли бы использовать способы вызова функций JavaScript без круглых скобок, для обхода WAF, но смысл моих твитов зачастую заключается в том, чтобы быстро получить знания, которые можно было бы применить позже.

Одним из таких способов выступает [].sort, sort принимает некую функцию и, как я обнаружил, вы можете злоупотреблять этим чтобы косвенно вызывать функцию оповещения (alert), о которой я расскажу позднее. Что я пытаюсь сделать, так это взломать своё сознание чтобы превратить такие векторы в достаточно важные чтобы запомнить их, вы обнаружите как я публикую варианты конкретных методов, а это, несомненно, помогает мне получать удовольствие от поиска подобных методик. Как часто вы ловили себя на мысли о том, что вы читаете книгу или конкретную статью, а она не привлекает ваше внимание? В поисках способов взлома JavaScript вы изучаете функциональную возможность, а затем применяете такие знания для достижения цели, причём не важно какова такая цель, если она имеется, она послужит запоминанию.

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

Среда

Чтобы следовать этим процессом, вам требуется быстрое окружение для проведения проверок. Это означает, что вам требуется нечто, в чём вы можете исполнить свой код мгновенно и получить результаты. Это может быть большим разнообразием вариантов, консолью браузера, локальным веб- сервером или таким веб- приложением как JS Fiddle. Лично я решил написать своё собственное прикладное приложение с названием "Hackvertor", предоставив ему возможность оценки кода, проверки объектов, написания HTML, но подходят и иные варианты. Просто я желал большей мощности и быть уверенным в том, что мои входные данные нигде не регистрируются. Ваша среда должна обладать возможностью оценки JavaScript и, как минимум, возвращать получаемые результаты.

Установка цели

После того, как вы настроили выбранную среду, следующим шагом будет постановка задачи. Когда у вас нет цели, вы можете смотреть на пустую страницу и не двигаться с места. Цель позволяет вам убедиться, что вы всегда пытаетесь что-то сделать, и она также может быть гибкой. Например, одной из моих целей было "выполнять JavaScript без круглых скобок". Если вы поставили перед собой хорошую цель, она, скорее всего, никогда не исчерпается, а хорошие цели также мутируют в иную цель, к примеру, цель, о которой я упоминал ранее, мутировала в "выполнять функции JavaScript без круглых скобок и передавать аргументы". Теперь вы понимаете, насколько полезны эти две цели, потому что сейчас у вас есть чёткое представление о том, что вам необходимо делать, и вы можете неверно употреблять функциями JavaScript для достижения этой цели. В приведённом выше примере вторая цель более сложная, чем первая, но вторая цель позволяет вам получить знания для достижения ещё более сложной цели.

Распыление

Распыление (fuzzing) — один из самых важных инструментов в наборе инструментов хакеров JavaScript, он позволяет вам действительно быстро отвечать на вопросы и открывать новые вещи, заставляя компьютер сообщать о результатах. Распыление — это просто написание кода, который перечисляет символы, код или данные, чтобы отыскать интересное поведение. В двоичной эксплуатации вы бы применяли распыление, для поиска DoS или сбоя, которые можно использовать, но когда взламываете JavaScript, основная идея состоит в том, чтобы достичь своей цели, получив ответы на вопросы. Например, я поставил себе цель понять, какие символы разрешены в качестве пробелов, вы можете удивиться: почему бы просто не посмотреть спецификацию? Вам не следует использовать спецификацию в качестве единственного источника сведений при попытке выявления поведение браузера, потому как браузеры иногда не следуют спецификации, это может происходить по причине того, что они совершили ошибку или просто не делают этого по разным причинам, например, обратная совместимость.

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

Постоянство и удача

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

Социальная среда

Когда я взламываю JS, я часто применяю Твиттер. Это полезно, потому как вы можете получать мгновенную обратную связь о своей методике, причём как положительную, так и отрицательную. По мере набора подписчиков вы обнаружите других людей, которым нравится то же самое, и вы обнаружите, что они указывают вам вариант или нечто, упущенное вами. Это замечательно, потому как обучаетесь не только вы, но и все те, кто наблюдает за преобразованием, также учатся. Представьте, если бы все применяли этот подход, все бы быстро обучались, а мы бы нашли действительно интересное поведение JavaScript. Когда я что-то чирикаю в Твиттер, это также застревает в моей голове, а если я что- то забуду, я всегда смогу отыскать в Твиттере или скачать свои твиты, чтобы отыскать конкретную методику. Одна важная вещь, тем не менее, состоит в том, что Твиттер не подходит для долгосрочного хранения данных. Если вы нашли нечто, чем вы особенно гордитесь, вам лучше написать сообщение в блоге, а затем твитнуть ссылку на него.

Основы

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

  Шестнадцатеричные

Для начала мы займёмся шестнадцатеричным кодированием, оно работает только внутри строк, если вы попытаетесь применять их в качестве идентификаторов, это завершится неудачей. Hex использует базу 16, а его экранирование имеет префикс "x". Вот некоторые примеры:


1 '\x61'//a
2 "\x61"//a
3 `\x61`//a
4 
5 function a(){}
6 \x61()//отказывает
 	   

В приводимых выше примерах вы видите, что первые три правильно срабатывают внутри строк, однако последний не выполняет вызов, поскольку в нём не разрешены шестнадцатеричные экранированные последовательности. Ещё один занимательный момент состоит в том, что шестнадцатеричные экранированные последовательности обязаны пользоваться строчным символом "x", если вы воспользуетесь прописным "X", это не будет рассматриваться как шестнадцатеричная последовательность, а механизм JavaScript просто обработает строку как литерал X со следующим за ним символами.

  Unicode

Экранирование Unicode также срабатывает в строках, но к тому же допускается в идентификаторах. Существуют два вида экранирования Unicode "\u" и \u{}. Первый предоставляет вам возможность воспроизведения символов внутри диапазона 0x00- FFFF, в то время как последний делает для вас возможным определять весь диапазон пуктов кодирования Unicode целиком. Вот некоторые образцы экранированных последовательностей Unicode:


1 '\u0061'//a
2 "\u0061"//a
3 `\u0061`//a
4 
5 function a(){}
6 \u0061()//корректно вызывает данную функцию
 	   

Приведённый выше код отлично выполнится, все строки допускаю экранирование Unicode, а функция вызовется верно. Некоторые важные моменты относительно данной формы экранирования Unicode: вы обязаны указывать четыре шестнадцатеричных символа, к примеру, \u61 не допускается, большинство браузеров возбудит исключительную ситуацию. Имелся ряд браузеров, которые допускали не разрешённые последовательности экранирования, например, \u61, но это было ошибкой браузера. Вы не можете кодировать круглые скобки или иные символы просто как идентификаторы вне строк. Ниже следует ещё один тип экранированной последовательности, который предоставляет вам возможность указывать кодовые пункты Unicode для всего диапазона Unicode. Они обладают характеристиками, аналогичными стандартным последовательностям экранирования Unicode в том смысле, что вы можете применять их в строках и идентификаторах, но, в отличие от стандартных последовательностей экранирования Unicode, вы не ограничены четырьмя шестнадцатеричными символами. Для применения таких последовательностей экранирования Unicode вы применяете \u{}, а внутри фигурных скобок вы указываете шестнадцатеричный код Unicode. Приводимые далее примеры хорошо работают с таким типом экранированной последовательности Unicode:


1 '\u{61}'//a
2 "\u{000000000061}"//a
3 `\u{0061}`//a
4 
5 function a(){}
6 \u{61}()//корректно вызывает эту функцию
7 
8 \u{3134a}=123//символ unicode "3134a" допустим в качестве переменной
 	   

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

  Восьмеричные

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


1 '\141'//a
2 "\8"//число вне восьмеричного диапазона, поэтому возвращается 8
3 `\9`//число вне восьмеричного диапазона, поэтому возвращается 9
 	   

  Оценка и экранирование

Теперь, когда вы знакомы с различными доступными последовательностями экранирования, вы можете применять их с оценками (eval) или иными формами оценки, такими как setTimeout. При их применении с такими функциями вам следует дважды экранировать их, или даже больше, если они вложены! Поскольку оценка работает со строками, она попытается декодировать переданный ей ввод, поэтому при фактическом исполнении JavaScript его механизм наблюдает декодированную строку, что позволяет нам нарушать ряд определённых ранее правил. Например, помните что шестнадцатеричные можно применять исключительно со строками? Ну, если вы воспользуетесь eval, ваше шестнадцатеричное сначала будет декодировано, а затем исполнено, поэтому идущее следом совершенно верно:


1 eval('\x61=123')//a = 123
 	   

Вы можете выполнять точно это же и с последовательностями экранирования Unicode, однако поскольку они допускаются в качестве идентификаторов, вы можете кодировать их дважды:


1 eval('\\u0061=123')
2 //\u0061 = 123
3 //a = 123
 	   

Как это показано в приводимом выше примере, вы можете экранировать обратную косую черту внутри строки, что создаёт экранированную последовательность Unicode, которая далее применяется в качестве идентификатора. Данное экранирование последовательности Unicode действенно транслируется в a=123. Вы к тому же не ограничены экранированием обратной косой черты; вы можете экранировать любую часть экранированной последовательности при помощи eval и, конечно же, можете смешивать и сопоставлять соответствующие кодировки. Это достаточно сложно продемонстрировать чтобы не запутаться. Поэтому я продемонстрирую по частям:


1 eval('\\u0061=123')//экранированная последовательность unicode, применяющая присваивание "a"
2 eval('\\u\x30061=123')//шестнадцатеричное кодирование самого первого нуля
3 eval('\\u\x300\661=123')//восьмеричное кодирование значения 6
 	   

  Строки

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


1 '\b'//backspace - забой
2 '\f'//form feed - перевод страницы
3 '\n'//new line - новая строка
4 '\r'//carriage return - возврат каретки
5 '\t'//tab - табуляция
6 '\v'//vertical tab - вертикальная табуляция 
7 '\0'//null - символ нуль
8 '\''//single quote - одиночная кавычка
9 '\"'//double quote - двойная кавычка 
10 '\\'//backslash  - обратная косая черта
 	   

Также вы можете экранировать все символы, которые не являются частью некой последовательности экранирования и они воспринимаются как реальные символы, скажем:


1 "\H\E\L\L\O"//HELLO
 	   

Что интересно, вы можете применять обратную косую черту в самом конце некой строки для её продолжения на следующей строке:


1 'I continue \
2 onto the next line'
 	   

Данное свойство к тому же работает с именами свойств объектов:


1 let foo = {
2     'bar\
3     ': "baz"
4 };
 	   

Заключённые в одиночные или двойные кавычки строки не поддерживают множество строк пока вы не применяете \ для их продолжения на новой строке, тем не менее, строки шаблона поддерживают поведение множества строк и их продолжения. Это можно подтвердить следующим кодом:


1 x = `a\
2 b\
3 c`;
4 x==='abc'//true
5 
6 x = `a
7 b
8 c`;
9 x==='abc//false
 	   

Итак, как вы могли наблюдать выше, первый фрагмент кода подтверждает что поведение продолжения присутствует в строках шаблона, поскольку в эту строку не добавляются символы новых строк. Второй фрагмент показывает строки шаблона, поддерживающие символы новой строки, причём они включены в эту строку. Строки шаблона обладают функциональной возможностью, которая позволяет вам выполнять произвольные выражения JavaScript внутри заполнителей, причём они определяются при помощи ${}, а ваше выражение помещается внутри этих фигурных скобок:


1 `${7*7}`//49
 	   

Удивительно, но внутри имеющихся заполнителей поддерживается любое выражение JavaScript, включая строку шаблона! Это означает, что вы можете вкладывать строки шаблона в строки шаблона, что приводит к причудливому JavaScript, который допустим:


1 `${`${`${`${7*7}`}`}`}`//49
 	   

Представьте себе, что вы пытаетесь написать для этого синтаксический анализатор! Я восхищаюсь V8, JavaScript Core и Spidermonkey, потому как в наши дни JavaScript настолько сложен, что необходим грамотный синтаксический анализ чтобы разобрать это верно. В любом случае, вы также можете вызывать функции, применяя так называемые помеченные строки шаблона. Вы просто вызываете функцию или выражение, которое возвращает вызываемую функцию и применяете обратные кавычки после этого для вызова такой функции:


1 alert`1337`//вызывает функцию оповещения с аргументом 1337
 	   

Как уже упоминалось, также поддерживается и выражение, так что вы снова можете иметь дело с причудливо выглядящим JavaScript, который вызывает несколько функций. Давайте поднимем это на степень выше и продемонстрируем. Если у вас имеется возвращающая себя функция, вы можете обладать неограниченным числом обратных кавычек:


1 function x(){return x}
2 x````````````
 	   

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

  Вызов и применение

Вызов это свойство всякой функции, которое позволяет вам вызывать её и изменять "значение this" функции в самом первом аргументе, а все последующие аргументы передаются такой функции. Например:


1 function x(){
2   console.log(this.bar);//baz
3 }
4 let foo={bar:"baz"}
5 x.call(foo);
 	   

В приведённом выше примере мы пользуемся объектом "foo" в качестве значения "this" для своей функции "x" и передаём его в сам вызов функции, данная функция "x" применяет this, который теперь ссылается на наш объект foo, а значение свойства bar возвращает "baz". Когда функция "x" вызывается, аргументов нет, если вы желаете передать этой функции аргументы, тогда вы просто добавляете их к соответствующему вызову функции следующим образом:


1 function x() {
2     console.log(arguments[0]);//1
3     console.log(arguments[1]);//2
4     console.log(this);//[object Window]
5 }
6 x.call(null, 1, 2)
 	   

Если вы не укажете значение "this" своему вызову функции, она будет применять свой объект окна, когда пребывает не в строгом режиме, потому как мы применили null в самом первом аргументе данного вызова функции, "this"по умолчанию будет применять объект окна, ибо мы не в строгом режиме. Когда вы применяете директиву "use strict", "this" будет установлен в null вместо окна:


1 function x() {
2     "use strict";
3     console.log(arguments[0]);//1
4     console.log(arguments[1]);//2
5     console.log(this);//null
6 }
7 x.call(null, 1, 2)
 	   

Функция применения (apply) почти такая же как функция вызова (call) с одним важным отличием: во втором аргументе вы можете указать массив аргументов:


1 function x() {
2     console.log(arguments[0]);//1
3     console.log(arguments[1]);//2
4     console.log(this);//[object Window]
5 }
6 x.apply(null, [1, 2])
 	   

Выводы

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