Глава 2. JavaScript без скобок

Вызов функций без скобок

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


1 let obj = {valueOf(){return 1}};
2 obj+1//2
 	   

Это интересно по той причине, что valueOf позволяет определить функцию, которая вызывается при применении данного объекта в качестве примитива. Таким образом, мы можем просто воспользоваться функцией оповещения вместо некой пользовательской функции и она будет вызываться вместо неё, верно? Ну, это не так просто, давайте попробуем и посмотрим что получится:


1 let obj = {valueOf:alert};
2 obj+1//недопустимая активация
 	   

Мы определяем alert как функцию для вызова при помощи valueOf, данный объект применяется с оператором сложения, который вызывает метод valueOf, valueOf присваивается самой функции alert, но при попытке вызова alert JavaScript выдаёт ошибку "недопустимая активация" (illegal invocation). Это вызвано тем, что функция alert требует чтобы "this" был объектом window. Когда вызывается ваша функция alert, сам объект "this" будет нашим пользовательским объектом с названием obj, поэтому возбуждается исключительная ситуация. Большинство объектов JavaScript наследуются от его прототипа Object, а valueOf на самом деле определяется в самом прототипе Object, а это означает, что нам не нужно применять объект пользователя; мы можем воспользоваться неким уже имеющимся объектом, поскольку почти каждый объект обладает функциональностью valueOf! Вы видите к чему это ведёт? Сам объект window обладает методом valueOf и мы можем перекрыть его:


1 window.valueOf=alert;window+1//calls alert()
 	   

На этот раз вызов предупреждения корректен и это благодаря тому, что мы изменили метод window valueOf и когда вызывается valueOf, объектом "this" будет window и ошибка недопустимой активации не будет возбуждена. Вы могли бы заметить, что приводимое выше можно сократить, потому как вам не обязательно указывать "window.", это обусловлено тем, что подразумевается что применяется объект window, а потому вы и в самом деле можете удалить "window.", а valueOf по- прежнему будет применять объект window. Выше я включил его для ясности, но на момент написания данных строк это срабатывает:


1 valueOf=alert;window+1//calls alert())
 	   

Вы также можете воспользоваться toString точно также как и valueOf:


1 toString=alert;window+''
 	   

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

Вызов функций с параметрами без скобок

Теперь мы можем слегка расширить свою цель и вместо того чтобы просто вызывать некую функцию, мы можем попытаться вызвать такую функцию и передать ей все параметры без каких бы то ни было круглых скобок. Однажды во время взлома JS меня посетил момент вдохновения и я задумался об обработчике исключительных ситуаций и о том как вы можете предоставить свою собственную функцию. На тот случай, если вы не знаете, сам объект window обладает глобальным обработчиком с названием onerror, а когда вы будете предоставлять своей функции некий обработчик, ваша функция будет получать все имеющиеся исключительные ситуации с этой страницы. Такой обработчик вызывается самым первым сообщением в первом аргументе, URL адресом во втором, номером строки в третьем, номером столбца в четвёртом и, наконец, самим объектом ошибки в самом последнем аргументе. Меня интересует именно аргумент сообщения, потому как если мы сможем повлиять на него, соответствующий обработчик будет вызван со строкой в самом первом аргументе. Вас может удивить как вызвать сам обработчик с выбранным аргументом, конечно же, мы могли бы установить такой обработчик, а затем возбудить исключительную ситуацию! Вы можете осуществить это выработав недопустимый код или воспользовавшись оператором throw.

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


1 throw new Error('Some exception');
 	   

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


1 onerror=alert;throw 'foo'
 	   

Это приводит к тому, что в Chrome появляется, как минимум, окно с предупреждением "Uncaught foo" (Не перехваченное foo). Вы можете задуматься о том как же мы сможем исполнить произвольный код? Если вы замените "alert" на "eval", то при возникновении исключительной ситуации оно будет оцениваться в качестве кода. Но это оставляет нас с неподходящим "Uncaught " с пробелом. Для обхода этого мы можем просто ввести некое равенство чтобы "Uncaught" превратился в присваивание переменной, а следовательно был бы допустимым JavaScript:


1 onerror=eval;
2 throw"=alert\x281\x29";
 	   

Следует отметить, что оператор throw это именно оператор, что подразумевает, что его нельзя применять в качестве выражения, например, вы не можете применять его в вызове функции:


1 alert(throw'test');//Это завершится неудачей
 	   

Это означает, что вы обязаны знать где вы его применяете, а если вы желаете обойти WAF (Web Application Firewall), вам нужно знать какие именно символы можно применять. Давайте установим здесь небольшую подчинённую цель: как мы можем применять оператор throw для вызова произвольного JavaScript без применения точки с запятой? В JavaScript имеются блочные операторы; они не нашли широкого применения, потому как "var" не был блочным, однако введение "let" позволяет применять блочную область действия. Так что, вероятно, они приживутся. При применении оператора блока вам нет нужды включать точку с запятой после блока, а это легко обойдёт данное ограничение точки с запятой:


1 {onerror=eval}throw"=alert\x281337\x29"
 	   

Другой способ обхода ограничения символов — использовать JavaScript ASI (автоматическая вставка точки с запятой). Вы можете использовать новые строки вместо точек с запятой, а JavaScript вставит их автоматически:


1 onerror=alert
2 throw 1337
 	   

JavaScript также поддерживает разделители абзацев и строк для символов новых строк, что очень полезно для обхода WAF, поскольку "onerror=", скорее всего будет заблокирован, но регулярные выражения часто пропускают альтернативные символы. Я воспользуюсь eval чтобы проиллюстрировать это, так как такие символы не очень хорошо будут выводиться на печать:


1 eval("onerror=\u2028alert\u2029throw 1337");
 	   

Экранированная последовательность unicode \u2029 представляет собой разделитель параграфов, а \u2028 представляет разделитель строк. Они оба действуют как символы новой строки и везде, где вы можете размещать символ новой строки вы можете пользоваться этими символами.

Выражения Throw

Один интересный момент в операторе throw состоит в том, что вы можете применять некое выражение, а самая правая его часть будет отправлена обработчику исключительных ситуаций. Если вы не очень поняли что я имею в виду, давайте рассмотрим оператор запятой. Оператор запятая выполняет оценку выражения слева направо и возвращает самый последний операнд. Например, давайте присвоим значение "foo" и воспользуемся оператором запятой:


1 let foo = ('bar', 'baz');
 	   

Значением foo; будет ..., вы можете догадаться? Baz. Это связано с тем, что оператор запятой возвращает самую последнюю часть своего выражения. Когда мы применяем оператор throw, он принимает выражение JavaScript, а потому оператор запятой здесь работает просто великолепно. Поэтому вы можете злоупотреблять данной функциональностью для уменьшения количества применяемых символов и создать удивительный JavaScript:


1 throw onerror=alert,1337
 	   

Приведённый выше код пользуется оператором throw и назначает обработчик onerror, затем используется оператор запятой е результат нашего выражения "1337" передаётся в соответствующий обработчик исключительной ситуации, что имеет результатом вызов предупреждения с "Uncaught 1337". Можно применять любое количество операндов, раз они являются частью одного и того же выражения, а самый последний операнд всегда будет передаваться обработчику исключительных ситуаций:


1 throw onerror=alert,1,2,3,4,5,6,7,8,9,10//Uncaught 10
 	   

Ещё одной функциональной особенностью в JavaScript является необязательная переменная исключительной ситуации внутри предложения catch. Это позволяет нам try (испробовать) блоки catch (ловушки) без круглых скобок и просто throw (пробрасывать) эту исключительную ситуацию снова для вызова соответствующего обработчика исключительной ситуации:


1 try{throw onerror=alert}catch{throw 1337}
 	   

Шаблоны с тегами

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


1 alert`1337`
 	   

В примере выше вызывается alert с "1337", строка шаблона также поддерживает заполнители, которые могут встраивать выражения JavaScript. Заполнители можно определять при помощи ${} и даже поддерживать вложенные строки шаблонов:


1 `${alert(1337)}`
2 `${`${alert(1337)}`}`
 	   

При применении помеченных строк шаблонов в качестве самого первого аргумента соответствующей функции передаётся некий массив строк, когда нет заполнителей, это будет одна строка, однако когда имеются заполнители, эти строки будут разделены:


1 alert`foobar`//foobar
2 alert`foo${1}bar`//foo,bar
 	   

Мы можем пользоваться этой функциональной особенностью для оценки кода, однако когда применяется eval, этот код не будет исполняться:


1 eval`alert\x281337\x29`//предупреждение не будет вызываться
 	   

Можете ли вы догадаться о причине этого? Это обусловлено тем, что функция eval просто возвращает некий массив и не выполняет преобразование переданного ей аргумента в строку. Если вы воспользуетесь альтернативной функцией, например setTimeout , которая осуществляет преобразование аргумента в строку, то это будет работать нормально:


1 setTimeout`alert\x281337\x29`//вызывается alert(1337)
 	   

Есть ещё кое- что в помеченных шаблонах, когда вы пользуетесь заместителем, который выполняет оценку строки, тогда он не будет добавлен в массив строк в самом первом аргументе, а на самом деле будет применяться в качестве второго аргумента и так далее:


1 function x(){
2     console.log(arguments);//Arguments(4) [Array(4), 'foo', 'bar', 'baz',...
3 }
4 
5 x`${'foo'}${'bar'}${'baz'}`
 	   

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


1 Function`x${'alert\x281337\x29'}`//вырабатывает функцию
 	   

Если вы выполните оценку приведённого выше, вы заметите, что там вырабатывается анонимная функция с "x" в качестве аргумента и с телом функции, но она не будет выполняться. Для её выполнения нам требуется вызвать эту функцию, как мы узнали в Главе 1, причём в помеченном шаблоне мы можем воспользоваться любым выражением. Итак, чтобы вызвать выработанную нами функцию, нам просто требуется добавить `` в самом конце своего выражения:


1 Function`x${'alert\x281337\x29'}```//вырабатывает функцию и выполняет её вызов
 	   

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

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


1 setTimeout`${alert}${0}${1337}`//не работает
 	   

Причина, по которой это завершается неудачей, состоит в том, что в качестве первого аргумента отправляется пустой массив строк, а не наша функция оповещения! Нам придётся найти иной способ исполнения произвольного JavaScript, в Главе 1 мы узнали о применении и вызове, мы могли бы воспользоваться здесь вызовом для присвоения массива строк значению "this" данной функции, а это означало бы что самый первый заполнитель применялся бы в качестве первого аргумента функции setTimeout. Давайте попробуем и посмотрим что получится:


1 setTimeout.call`${alert}${0}${1337}`//не работает, недопустимая активация
 	   

Это не срабатывает, а происходит это по той причине, что значение this больше не является объектом window и setTimeout будет throw недопустимую ошибку активации. Если мы попробуем воспользоваться вместо заполнителя строкой, это будет работать нормально, потому как в качестве первого аргумента нашей функции setTimeout передаётся только строка, а второй и третий будут опущены, при этом значением "this" будет соответствующий объект window:


1 setTimeout`alert\x281337\x29`
 	   

Наша подчинённая цель в данном случае состоит в вызове любой функции при помощи заполнителей, причём нам необходимо каким- то образом обойти ограничение ошибок недопустимой активации. Для этого давайте рассмотрим способы вызова alert. мы уже наблюдали, что для его вызова вы можете применять onerror=alert, но как насчёт некоторых иных вариантов? Если вы задумаетесь об этом, вам необходим некий API JavaScript, который позволяет вам вызывать функцию, передавая на неё ссылку, что сделает для нас возможным вызывать функции из заполнителей. Первое что необходимо здесь сделать, это проверить различные доступные нам методы, причём вы можете осуществить это при помощи консоли, веб- приложения или индивидуального инспектора. Я выбрал последнее и воспользовался написанным мной инструментом Hackability Inspector. Я применил данный инспектор для перечисления строковых методов и вспомнил, что функция замены позволяет вам применять некую функцию в случае нахождения соответствия замене. Если я передам ссылку на функцию alert, тогда, вероятно, для вызова alert можно будет воспользоваться replace:


1 'a'.replace(/./,alert)//вызывает alert при помощи a
 	   

Это выглядит многообещающим. Мы применяем строку "a", регулярное выражение для её нахождения, а затем указываем функцию alert, которая будет вызываться когда будет найдено совпадение. Однако имеется пара проблем: мы пользуемся регулярным выражением, а регулярное выражения обязано совпадать, а такое совпадение это то, что отправляется данной функции в качестве аргумента. К счастью, наша функция replace позволяет вам указывать строку, а также регулярное выражение, что означает, что мы можем применять получаемый массив совпадений в соответствующем помеченном шаблоне, поскольку он будет преобразован из массива в строку:


1 'a,'.replace`a${alert}`//вызывает alert при помощи a,
 	   

Это слегка странно. Нам также требуется передавать запятую, потому как массивы преобразуются в строку. Как мы можем обойти это? Один из способов состоит в применении вызова для замены значения "this", что означает, что мы способны управлять аргументом match и применять регулярное выражение для установления соответствия любому символу:


1 'a'.replace.call`1${/./}${alert}`//вызывает alert при помощи 1
 	   

То что происходит в приводимом выше, заключается в том, что функция replace вызывается при помощи call, а это выполняет присвоение "this" совпадениям строк помеченных шаблонов "1,", мы применяем регулярное выражение, а поскольку используется call, мы передаём это регулярное выражение в первый аргумент функции replace и данное регулярное выражение соответствует любому одиночному символу (за исключением символа новой строки), затем мы передаём ссылку на функцию alert, в результате чего alert вызывается с 1. Уф. Это достаточно безумно, не так ли? Кто знал, что строками шаблонов можно злоупотреблять таким образом? Хорошая задача на данный момент состоит в том, можете ли вы вызвать предупреждение с 1337 вместо 1.

При помощи данного метода вы можете вызывать практически любую функцию, а также любые аргументы с применением объекта Reflect. Так что же такое этот объект Reflect? Ну, он позволяет вам для любого объекта выполнять такие операции как call, get/set. Вы просто передаёте функцию, объект и аргументы, которые желаете вызвать. Как уже упоминалось это можно применять и для прочих операций, но сейчас давайте сосредоточимся конкретно на методе apply:


1 Reflect.apply.call`${alert}${window}${[1337]}`//вызывает alert при помощи 1337
 	   

Давайте разберём что здесь происходит, мы передаём ту функцию, который намерены вызывать, в данном случае alert, в эту функцию мы передаём значение "this", в данном случае "window", что позволяет избежать ошибки недопустимой активации (illegal invocation) и, наконец, мы передаём массив аргументов, который мы бы хотели передать этой функции. Чтобы избежать передачи пустого массива строк в качестве первого аргумента, как упоминалось ранее, применяется "call". Метод apply - Reflect - не требует определённого значения "this" и будет успешно исполняться с любым назначенным "this" объектом.

Мы можем проделать то же самое для таких операций как set. Метод set объекта Reflect позволяет вам выполнять операции установки для любого объекта. Его можно продемонстрировать назначением значения местоположения объекта:


1 Reflect.set.call`${location}${'href'}${'javascript:alert(1337)'}`
2 //назначаем url javascript
 	   

Данный метод set требует своим первым аргументом допустимый объект, в данном случае "location", свойства вторым аргументом и назначаемого значения в третьем.

Символ hasInstance

Последний метод, который я бы хотел обсудить это применение символа has instance. Символы позволяют вам определять уникальный маркер в ключах свойств. Они выступают способом гарантии уникальности вашего ключа. Существуют встроенные символы, которые вы можете применять для выполнения различных операций. JavaScript применяет символы таким образом чтобы избегать использования особых свойств, которые входят в разногласие с имеющимися в Интернете кодами. Значение символа "has instance" позволяет вам персонализировать поведение оператора instanceof, когда вы устанавливаете данный символ, он передаётся самому левому операнду в соответствующей функции, определяемом данным символом. Это предлагает аккуратный способ выполнения JavaScript без круглых скобок:


1 'alert\x281337\x29'instanceof{[Symbol['hasInstance']]:eval}//вызывает alert(1337)
 	   

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


1 'alert\x281337\x29'instanceof{[Symbol.hasInstance]:eval}//calls alert(1337)
 	   

Выводы

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