В ответ на этот вопрос, мой коллега Алексей Гончаров kukumumu ответил лаконично: «JavaScript это панковский язык» и кинул ссылку на статью Jasper Cashmore «A Javascript journey with only six characters», которая действительно погружает нас в путешествие в эзотерический мир JSFuck и сразу все ставит на свои места.
Мне настолько понравилось, что я решил перевести статью на русский язык.
Перевод статьи “A Javascript journey with only six characters”
Javascript — это странный и замечательный язык, позволяющий писать чокнутый код, который все равно работает. Он пытается помочь нам, преобразуя данные в определенные типы на основе того, как мы обращаемся с ними.
Если добавим знаки плюса или минуса перед чем-то, JS предположит, что мы хотим добавить текст, и преобразует тип данных в
String
. JS решит, что мы имеем в виду число, и данные переведутся в тип
Number
(если это возможно).Если мы будем отрицать какие-то данные, они переведутся в
Boolean
.Мы можем использовать JS, чтобы вытворять всякие магические вещи, используя только символы
[,
],
(,
),
! и +
. Если вы читаете это не с мобильного устройства, можете открыть JS-консоль, чтобы проследить за рассказом и проверить работоспособность приведенных примеров, просто скопировав их.
Начнем с основ. Несколько золотых правил, которые нужно запомнить:
Начав с
!
получаем тип Boolean
Начав с
+
получаем тип Number
Добавляя
[]
получаем тип String
Вот эти правила в действии:
![] === false
+[] === 0
[]+[] === ""
Еще одна вещь которую важно знать, — это то, что можно возвращать определенные символы из строк используя скобки, вот таким образом:
"hello"[0] === "h"
Кроме того, помните, что можно получать числа, добавляя их составляющие в виде строк, а потом переведя результат в тип Number с помощью правила №2:
+("1" + "1") === 11
Вот так. Теперь давайте скомбинируем все вышеперечисленное, чтобы получить символ
a
.![] === false
![]+[] === "false"
+!![] === 1
------------------------
(![]+[])[+!![]] === "a" // same as "false"[1]
Клево!
Получается, что с относительно простыми комбинациями мы можем получить любую из букв, составляющих слова
true
и false
. a
,e
,f
,l
,r
,s
,t
,u
. Как же мы можем получить остальные буквы?Ну, есть, например,
undefined
, который мы можем получить, написав глупости типа [][[]]
. Переводим в тип String
, используя одно из наших Золотых Правил, и дополнительно получаем буквы d
,i
и n
.[][[]] + [] === "undefined"
Из всех букв, которые у нас уже есть, мы можем получить такие слова, как
fill
, filter
и find
. Конечно, можно получить и другие, но эти примечательны тем, что являются методами массивов. Это значит, что они являются объектами Array и могут быть вызваны напрямую в массивах, например, [2,1].sort()
.Другая важная вещь, которую нужно знать о JS, — это то, что свойства объекта могут быть доступны с помощью точечной или скобочной нотации. Так как упомянутые выше методы массива являются свойствами самого массива, мы можем вызывать эти методы, используя квадратные скобки вместо точечной нотации.
Так получается что
[2,1]["sort"]()
тоже самое что [2,1].sort()
.Давайте пойдем дальше и посмотрим, что получится, если мы попробуем использовать один из наших методов массива, записанный с помощью текущей коллекции букв, без его вызова.
[]["fill"]
Получается
function fill() { [native code] }
. Мы можем превратить этот метод в строку, используя наше золотое правило:[]["fill"]+[] === "function fill() { [native code] }"
Вот так мы и получаем следующие символы:
c
,o
,v
,(
,)
,{
,[
,]
,}
,?
.С новоприобретенными
c
и o
мы можем сформировать слово constructor
. constructor
это метод, который имеют все объекты JS, возвращающий их функцию-конструктор. Давайте получим в виде строки представление функций-конструкторов для объектов, с которыми мы до сих пор имели дело:
true["constructor"] + [] === "function Boolean() { [native code] }"
0["constructor"] + [] === "function Number() { [native code] }"
""["constructor"] + [] === "function String() { [native code] }"
[]["constructor"] + [] === "function Array() { [native code] }"
Так мы добавим в наш арсенал следующие символы:
B
,N
,S
,A
,m
,g
,y
.Теперь мы можем создать "
toString
", функцию, которую можно использовать с квадратными скобками.(10)["toString"]() === "10"
Но мы ведь и так можем превратить что угодно в строку, используя наше золотое правило, так как же это может быть полезно?
Что если я скажу вам, что у метода
toString
типа Number
есть секретный аргумент секретный аргумент под названием radix
, который может изменить основание системы счисления заданного числа перед переводом в строку? Взгляните:(12)["toString"](10) === "12" // base 10 - normal to us
(12)["toString"](2) === "1100" // base 2, or binary, for 12
(12)["toString"](8) === "14" // base 8 (octonary) for 12
(12)["toString"](16) === "c" // hex for 12
Но зачем останавливаться на 16? Максимум — это 36, что по сути дает нам все символы от
0-9
и a-z
. Так что мы можем вызвать любую цифру или букву:(10)["toString"](36) === "a"
(35)["toString"](36) === "z"
Замечательно! Но как насчет других символов типа заглавных букв и знаков препинания? Копаем глубже.
В зависимости от того, где выполняется ваш код, у вас может быть доступ к предустановленным объектам или данным. Если вы запускаете код в браузере, велики шансы, что у вас есть доступ к некоторым методам обертки HTML.
Например, bold — это метод
String
который добавляет теги <
b
>
."test"["bold"]() === "<b>test</b>"
Это дает нам символы
<
, >
и /
.Она конвертирует строку в URI-совместимый формат, который простые браузеры в состоянии переварить. Эта функция — важная часть нашего квеста, так что нам нужно получить к ней доступ. Мы можем ее написать, но сможем ли мы ее выполнить? Это не типичная функция, как все предыдущие, а функция глобального уровня.
Что представляет собой конструктор функции?
Ответ — function
Function() { [native code] }
, сам объект Function и есть конструктор..
[]["fill"]["constructor"] === Function
Используя это, мы можем передать строку кода для создания функции.
Function("alert('test')");
Получается:
Function anonymous() {
alert('test')
}
Уже этот код мы способны вызвать, просто используя
()
в конце. Так что теперь мы используем функцию escape следующим образом:
[]["fill"]["constructor"]("return escape(' ')")() === "%20"
Если мы передаем нашу
<
описанную ранее в функцию escape, мы получаем %3C
. Это заглавная C
очень важна, чтобы получить остальные символы, которых нам не хватает.[]["fill"]["constructor"]("return escape('<')")()[2] === "C"
Используя ее, мы можем написать написать функцию
fromCharCode
, которая возвращает символы Unicode из заданного десятичного представления. Это часть объектов String
, которую мы можем получить точно так же, как делали ранее.""["constructor"]["fromCharCode"](65) === "A"
""["constructor"]["fromCharCode"](46) === "."
Мы можем проверить любые десятичные представления символов Юникод здесь: Unicode lookup.
Фух. Вроде все!
Теперь у нас есть возможность вызвать почти любой символ на свете, составить из них код и даже выполнить. Это значит, что мы получаем полноту по Тьюрингу в Javascript, используя всего шесть символов:
[
,]
,(
,)
,+
и !
.Хотите доказательство? Запустите этот код в своем браузере:
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(+(!+[]+!+[]+!+[]+[!+[]+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(+![]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]+!+[]])+(!![]+[])[+[]]+(![]+[])[+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()
Если вы читаете это с мобильного, код выше это alert(«wtf»).
Есть даже тулза под названием JSFuck которое автоматизирует преобразование, а тут можно посмотреть, как она переводит каждый символ.
Какое практическое применение?
Никакое. Никак! Правда, недавно eBay сделал несколько плохих вещей, благодаря которым продавцы теперь могут внедрять JS-код в свои страницы, используя только эти символы, но это достаточно необычный вектор атаки. Еще некоторые люди вспомнят про обфускацию, но, будем честны, есть способы обфускации получше этого.
Извините.
Надеюсь вам понравилось путешествие!
Спасибо VladimirKochetkov и Ксеня Кириллова за помощь с переводом.
Комментарии (18)
sshikov
05.09.2017 20:07+5Не нашел ответа на вопрос из заголовка. Так почему анализ защищенности-то не того?
Aingis
05.09.2017 21:45(Абзацы: второй и за последним заголовком.) Когда-то ebay решил, что если резать буквы и цифры, то это достаточно безопасно. Кто-то додумался сделать атаку на JS вообще без использования этих символов, и она работала. Ну, и как вы такое автоматизируете? Тут всё сводится к тому, что исследовательские и творческие задачи практически не автоматизируются потому, что как только кто-то придумал что-то новое, ваш старый подход не работает.
sshikov
05.09.2017 22:06+1Ну вы извините, но "Когда-то ebay решил, что" говорит только об уровне интеллекта тамошних программистов — не более того. Делать из этого глубокие выводы — это как-то непредусмотрительно.
Насколько я понял, тут предлагается что-то типа кодирования Черча для js кода под названием jsfuck. Это конечно интересно — но в целом далеко не ново.
Ну, и как вы такое автоматизируете?
Ну, интерпретатору вообще говоря все равно, что интерпретировать. Статический анализ это может и поломает, но от него в случае js и так не слишком много толку. Поломает наколенные методы защиты на базе регулярок — ну так туда им и дорога.Ну в общем, заголовок все равно кажется слегка желтоватым.
Aingis
05.09.2017 23:15Так соль в том, что как только будет принято решение «а давайте автоматизируем анализ защищённости», то это решение именно такого уровня. Потому что стоит кому-то придумать новую атаку, как система станет беззащитной. Хуже того, некому будет защищать. Всё ж автоматизировано, людей нет.
VladimirKochetkov
06.09.2017 00:48+1Ответ тут читается между строчек :) «Более интересные подходы к анализу», о которых упомянул автор в начале, будут анализировать более-менее серьёзный проект на JS до второго пришествия. Почему так — недавно подробно расписывал на RSDN (https://rsdn.org/forum/philosophy/6761512.flat#6761512, см. стартовое сообщение и комменты).
VladimirKochetkov
06.09.2017 00:55Простите, никак не привыкну к местному парсеру :( Вот ссылка на обсуждение, в виде ссылки ^_^
rami0 Автор
06.09.2017 00:49Возможно и желтоватый заголовок, но он не лишен правды. В начале я привел ссылку на статью Владимира Кочеткова о методах автоматизации анализа, станет понятно, что с анализом Java Script методом абстрактной интерпретации машина без бутылки не справится.
Если вы приведете пример успешной автоматизации поиска уязвимостей в Java Script причем желательно такой, чтобы еще выдавал на выходе готовый экслойт, буду благодарен!
Vkuvaev
06.09.2017 00:28Автоматизация без головы, на текущем уровне развития ИТ — это как на Тесле врубить автопилот и сесть на заднее сидение. Не нужно удивляться результату, короче.
Добрыми людьми, внедрение статических анализаторов, предлагается как часть процесса, включая всякую динамику и анализ головой как результатов работы автоматических тулзов, так и всякие исследования.
rami0 Автор
06.09.2017 00:52Мы в Positive Technologies стремимся к тому, чтобы анализ кода проводился пока вы сидите на заднем сидении.
L0N3_W01F
06.09.2017 01:11+2К слову, мы на Tproger эту статью перевели уже год назад, когда она только вышла: https://tproger.ru/translations/js-magic-with-6-symbols/
Shannon
06.09.2017 09:53+2Если верить датам, то еще месяцем раньше ее перевели на хабре:
https://habrahabr.ru/post/312172/
Amareis
Ну что я могу сказать? JavaScript очень… Эээ… Гибкий, во! Гибкий и удивительный. И беспощадный.