Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript: анализируем динамику статикой

JavaScript: анализируем динамику статикой

Доклад Владимира Кочеткова и Валерия Пушкаря (Positive Technologies) для PDUG-секции на форуме PHDays 9.

More Decks by Positive Development User Group

Other Decks in Programming

Transcript

  1. Заголовок 2 • Руководитель отдела исследований по анализу защищённости приложений

    в Positive Technologies • AppSec- и CS-исследователь в области формальных методов анализа и защиты приложений • Активный участник и организатор Positive Development User Group [email protected] :~$ whoami
  2. Заголовок 3 • «Сделайте так, чтобы это был анализатор JavaScript

    и всем нравился» • «Нужно, чтобы всё было, как в других модулях, но только для JavaScript» • «Обязательно обеспечьте поддержку Node.js» • «А ещё фронтенд было бы неплохо. React.js – обязательно, ну и Angular тоже. Наверное» Базовые требования к исследованию
  3. Заголовок 4 • Абстрактная интерпретация в symbolic-семантике • Возможность выполнять

    отдельные фрагменты кода в песочнице • Доказательство существования уязвимостей через построение векторов атак • Генерация информации для ручного доказательства подозрений на уязвимость Чуть более формально
  4. Заголовок 6 • v7 (2016): Array.prototype.includes(), exponentiation operator. • v8

    (2017): async/await, shared memory and atomics, Object.values/Object.entries, string padding, Object.getOwnPropertyDescriptors(),trailing commas in function parameter lists and calls. • v9 (2018): shared memory and atomics, the Rest/Spread properties, asynchronous iteration, Promise.prototype.finally, RegExp Features: s (dotAll) flag, named capture groups, lookbehind assertions, unicode property escapes; Развитие JavaScript (ECMAScript)
  5. Заголовок 7 • ChakraCore - интерпретатор языка JavaScript от компании

    Microsoft (https://github.com/Microsoft/ChakraCore) • написан на С++, кроссплатформенный (Windows, Linux, OS X), поддерживает основные архитектуры: x86, x64, arm; • регистровая архитектура (данные хранятся в регистрах); • использование аппаратного обеспечения по максимуму, потребление минимального количества ресурсов; • оптимизация быстрого запуска и выполнения JS кода; Microsoft ChakraCore
  6. Заголовок 8 ChakraCore OpCodes Control flow Br - unconditional branch

    BrEq_A - branch if '==' (general equals) BrNeq_A - branch if '!=' (not general equals) BrGt_A - branch if '>' BrSrEq_A - branch if '===' (strict equals) BeginSwitch - the start of a switch statement EndSwitch - equivalent to Br OpCode Case - equivalent to Branch if '===' (strict equals) Call - call (direct) registered function Throw - throw exception Ret - return from function Object operations LdFld - load from object instance's direct field StFld - store into object instance's direct field InitFld - declare a property with an initial value DeleteFld - remove a property Instancing operations NewScObject - create new object instance NewScArray - create new array instance NewScFunc - create new function instance NewRegEx - create a new RegEx expression
  7. Заголовок 9 Ветвления в ChakraCore 0002 ProfiledLdRootMethodFld R5 = root.checkCondition

    #2 <2> 0008 StartCall ArgCount: 1 000b ArgOut_A Out0 = R2 000e ProfiledCallIWithICIndex R5 = R5(ArgCount: 1) <2> <0> 0018 BrFalse_A x:003e ( 34) R5 001c ProfiledLdRootFld R6 = root.console #1 <1> 0022 ProfiledLdMethodFld R5 = R6.log #0 <0> 0026 StartCall ArgCount: 2 0029 ArgOut_A Out0 = R6 002c ProfiledArgOut_A Out1 = R3 <1> 0031 ProfiledCallIWithICIndex R5(ArgCount: 2) <0> <1> 003b Br x:005d ( 31) 003e ProfiledLdRootFld R6 = root.console #1 <1> 0044 ProfiledLdMethodFld R5 = R6.log #0 <0> 0048 StartCall ArgCount: 2 004b ArgOut_A Out0 = R6 004e ProfiledArgOut_A Out1 = R4 <2> 0053 ProfiledCallIWithICIndex R5(ArgCount: 2) <0> <2> 005d Ret if (checkCondition()) { console.log('True branch') } else { console.log('False branch') }
  8. Заголовок 11 Вычисление условий для значений переменных C1 (C1 and

    !C2) or (C1 and C2) C1 and !C2 False Line Br C2 Сохраняем значения и их условия Переписываем значения, используя условие С1 and !C2 Мержим текущие значения для условия перехода C1 and C2 C1 and C2
  9. Заголовок 12 Слияние состояний OR Ничего не делаем Значения до

    перехода с их условиями AND условие перехода Fi-нода Множество значений до прыжка и после различаются Множество значений до прыжка и после совпадают Значения и их условия уже верны Значения и их условия на момент слияния
  10. Заголовок 13 • string • number • boolean • null

    • undefined • symbol • object Типы в JavaScript
  11. Заголовок 14 Прототипная система типов //Создание нового объекта var foo

    = {name: "foo", one: 1, two: 2}; //Создание еще одного нового объекта var bar = {two: "two", three: 3}; bar.__proto__ = foo; // foo теперь является прототипом для bar //Если теперь мы попробуем получить доступ к полям foo из bar, то всё получится bar.one // Равно 1 //Свои поля тоже доступны bar.three // Равно 3 //Собственные поля выше по приоритету полей прототипов bar.two; // Равняется "two"
  12. Заголовок 15 Конструирование объектов (1/2) var bike = { name:

    'SuperSport', manufacturer: 'Ducati', start: function () { console.log('Starting the engine...'); } }; bike.wheelType = 'Alloy'; console.log(bike.wheelType); //Output: Alloy bike.start(); //Output: Starting the engine... //Adding method stop() later to the object bike.stop = function () { console.log('Applying Brake...'); } bike.stop(); //Output: Applying Brake... function Vehicle(name, manufacturer) { this.name = name; this.manufacturer = manufacturer; } var car1 = new Vehicle('Fiesta', 'Ford'); var car2 = new Vehicle('Santa Fe', 'Hyundai’); console.log(car1.name); //Output: Fiesta console.log(car2.name); //Output: Santa Fe
  13. Заголовок 16 Конструирование объектов (2/2) var car = Object.create(Object.prototype, {

    name: { value: 'Fiesta', configurable: true, writable: true, enumerable: false }, manufacturer: { value: 'Ford', configurable: true, writable: true, enumerable: true } }); console.log(car.name); //Output: Fiesta class Vehicle { constructor(name, manufacturer, engine) { this.name = name; this.manufacturer = manufacturer; this.engine = engine; } } var bike1 = new Vehicle('Hayabusa', 'Suzuki', '1340cc'); var bike2 = new Vehicle('Ninja', 'Kawasaki', '998cc'); console.log(bike1.name); //Output: Hayabusa console.log(bike2.maker); //Output: Kawasaki
  14. Заголовок 18 // C# string parm = Request.Params["parm"]; var data

    = Convert.FromBase64String(parm); string str1 = Encoding.UTF8.GetString(data); Response.Write("<b>" + str1 + "</b>"); // JavaScript const parm = req.query.parm; var str1 = base64decode(parm); res.send("<b>" + str1 + "</b>"); Связывание выражений (1/3)
  15. Заголовок 19 // C# string parm = Request.Params["parm"]; var data

    = Convert.FromBase64String(parm); string str1 = Encoding.UTF8.GetString(data); Response.Write("<b>" + str1 + "</b>"); // JavaScript const parm = req.query.parm; var str1 = base64decode(parm); res.send("<b>" + str1 + "</b>"); Связывание выражений (2/3) ????????????????????????????????????????????????? byte[] global::System.Convert.FromBase64String(string)
  16. Заголовок 20 // C# string parm = Request.Params["parm"]; var data

    = Convert.FromBase64String(parm); string str1 = Encoding.UTF8.GetString(data); Response.Write("<b>" + str1 + "</b>"); // JavaScript const parm = req.query.parm; var str1 = base64decode(parm); res.send("<b>" + str1 + "</b>"); Связывание выражений (3/3) byte[] global::System.Convert.FromBase64String(string) global['require']('nodejs-base64').base64decode()
  17. Заголовок 21 • Entity-литералы, суть – именованные ссылки на сущности

    среды выполнения JavaScript трёх типов: • Объекты • Массивы • Функции • Каждый entity-литерал хранит ссылку на родительскую сущность, скоупу которой он принадлежит • Кроме корневого entity-литерала «global» Entity-литералы
  18. Заголовок 22 В определённых ситуациях интерпретатор JavaScript пытается осуществить приведение

    типов с помощью одного из алгоритмов (набора правил): • ToString • ToBoolean • ToNumber (ToInteger, ToInt32, ToUint32, ToInt16, ToUint16, ToInt8, ToUint8, ToUint8Clamp) • ToPrimitive • OrdinaryToPrimitive • NumberToString • ToObject Неявное приведение типов
  19. Заголовок 25 Создание точек входа в Express.js (и не только):

    var express = require('express'); var app = express(); app.get('/', function (req, res) { // это оно и есть res.send('Hello World!'); }); app.listen(3000, function () { console.log('Example app listening on port 3000!'); }); Динамика во всём
  20. Заголовок 27 false => ![] true => !![] undefined =>

    [][[]] NaN => +[![]] 0 => +[] 1 => +!+[] 2 => !+[]+!+[] 10 => [+!+[]]+[+[]] Array => [] Number => +[] String => []+[] Boolean => ![] Function => []["filter"] eval => []["filter"]["constructor"]( CODE )() window => []["filter"]["constructor"]("return this")() ... (ещё «100500» паттернов) JSF**k • Как анализировать ТАКОЕ в статике? • Распознавание и выполнение подобного кода – не сработает • Реализация всех правил неявного преобразования в семантике symbolic вычислений === (слабоумие && отвага || самоубийство)
  21. Заголовок 28 var a = "x"; var b = "ss";

    var c = a + b; var o = {}; o[c.toUpperCase()] = function () { return http_request_params["param1"]; } http_response.send(o.XSS()); // XSS by param1 Общий случай: динамический код • В JavaScript динамическим является практически всё: • иерархия, структура и состояния объектов; • граф связывания выражений; • структура интерпретируемых модулей; • граф вызовов; • и т.д. • Чтобы проанализировать это в статике нужна… динамика!
  22. Заголовок 29 • Частичное вычисление – рекурсивная интерпретация отдельных узлов

    symbolic-выражений в реальной среде выполнения: • JavaScript runtime • Node.js runtime • Интерпретатор pure-семантики булевых выражений • Основная стратегия: «в любой непонятной ситуации, вычисляй всё, что можно вычислить (но не больше)». Частичное вычисление
  23. Заголовок 30 var a = "x"; var b = "ss";

    var c = a + b; var o = {}; o[c.toUpperCase()] = function () { return http_request_params["param1"]; } http_response.send(o.XSS()); // XSS by param1 Процесс частичного вычисления "x" – литерал "ss" – литерал Evaluate("x" + "ss") === "xss" {} – пустой объект Evaluate("xss".toUpperCase()) === "XSS" http_request_params["param1"] – неизвестная o.XSS – известная функция, «проваливаемся» в неё и получаем в ответе: http_request_params["param1"]
  24. Заголовок 31 Наряду с истинными и ложными булевыми, выражения в

    JavaScript могут также быть истинноватыми (truthy) и ложноватыми (falsy) – буленоватыми. Буленоватым называют выражение, не принадлежащее множеству { true, false }, но приводимое к одному из этих значений неявно, например, в условных операторах. Может ли данный код вывести в консоль значение 'false': if (x) { console.log(x.valueOf()) } ? Буленоватая логика (1/3)
  25. Заголовок 32 Наряду с истинными и ложными булевыми, выражения в

    JavaScript могут также быть истинноватыми (truthy) и ложноватыми (falsy) – буленоватыми. Буленоватым называют выражение, не принадлежащее множеству { true, false }, но приводимое к одному из этих значений неявно, например, в условных операторах. Может ли данный код вывести в консоль значение 'false': if (x) { console.log(x.valueOf()) } ? Буленоватая логика (2/3)
  26. Заголовок 33 Наряду с истинными и ложными булевыми, выражения в

    JavaScript могут также быть истинноватыми (truthy) и ложноватыми (falsy) – буленоватыми. Буленоватым называют выражение, не принадлежащее множеству { true, false }, но приводимое к одному из этих значений неявно, например, в условных операторах. Может ли данный код вывести в консоль значение 'false': if (x) { console.log(x.valueOf()) } ? Буленоватая логика (3/3)
  27. Заголовок 35 Pure-семантика булевых выражений Значения PureTrue PureFalse Boolean PureBoolean

    JSBoolean Значения: JSTrue JSFalse Операции: JSAnd JSOr JSNot Операторы JS Операции PureAnd PureOr PureNot ToPureBoolean Выражение типа PureBoolean не может содержаться в выражении любого типа JavaScript (но не наоборот, т.к. есть ToPureBoolean)!
  28. Заголовок 36 SMT-солвер условий JavaScript-выражений! Преобразование уравнения в формат SMT-LIB

    Решение уравнения в SMT-солвере Применение «ad-hoc» теорий Преобразование модели решения в symbolic-представление Microsoft Z3 CVC4 SMT via Automata.NET Equality assertions Fixed points Invertible functions Отображение модели решения на исходное уравнение Частичное вычисление узлов уравнения Решены новые узлы? Преобразования в семантике pure boolean Выделение доп.условий Возврат решения да нет
  29. Заголовок 38 • JS Runtime package processor: • require; •

    console; • promise; • ExpressJS package processor: • get, post, …, all; • use; • send; • listen; • Nodejs-base64 package processor: • base64decode; • base64encode; Package-процессоры
  30. Заголовок 39 Верхнеуровневая архитектура анализатора Report Detection manager … CSRF

    detector Injection detector SMT solver Bytecode interpreter Code provider ChakraCore dumper Project system tooling Package processors … Express.js processor JS runtime processor Register-based symbolic VM Interpretation manager Symbolic scopes Evaluator Pure-Boolean transformer Node.js runtime ChakraCore runtime
  31. Заголовок 41 Экспоненты: M потока управления var express = require('express');

    var app = express(); app.get('/', function (req, res) { var response = req.query.param; if (req.query.cond1 == 'ok') { return; } else if (req.query.cond2 == 'ok') { return; } else ... if (req.query.condM == 'ok') { return; } else ; res.send(response); }); app.listen(3000, function () { }); M Память Время 1 20549632 00:00:00.5937500 ... 18 33894400 00:00:00.8750000 19 49442816 00:00:01.2968750 20 75538432 00:00:02.1406250 21 119848960 00:00:04.6875000 22 218234880 00:00:12.8593750 23 398086144 00:00:42.2656250 24 781717504 00:02:25 25 1675882496 00:08:55.1406250 26 3306426368 00:34:33.2343750
  32. Заголовок 42 Экспоненты: M потока вычисления var express = require('express');

    var app = express(); app.get('/', function (req, res) { var response = req.query.param; if (req.query.cond1 == 'ok') { response = '1'; } else if (req.query.cond2 == 'ok') { response = '2'; } else ... if (req.query.condM == 'ok') { response = 'M'; } else ; res.send(response); }); app.listen(3000, function () { }); M Память Время 1 20828160 00:00:00.5937500 ... 12 25251840 00:00:00.7500000 13 26234880 00:00:00.8437500 14 28987392 00:00:01.1250000 15 34451456 00:00:01.9687500 16 44302336 00:00:05.0781250 17 107339776 00:00:16.3906250 18 276717568 00:01:01.9531250 19 579211264 00:04:16.7812500 20 1014247424 00:18:38.0781250
  33. Заголовок 43 Экспоненты: M потока вычисления с обратной связью var

    express = require('express'); var app = express(); app.get('/', function (req, res) { var response = req.query.param; if (req.query.cond1 == 'ok') { response = response + '1'; } else if (req.query.cond2 == 'ok') { response = response + '2'; } else ... if (req.query.condM == 'ok') { response = response + 'M'; } else ; res.send(response); }); app.listen(3000, function () { }); M Память Время 1 20398080 00:00:00.6562500 ... 8 24850432 00:00:00.7812500 9 25255936 00:00:00.9531250 10 25178112 00:00:01.4062500 11 27291648 00:00:02.8125000 12 30334976 00:00:08.7656250 13 31444992 00:00:29.5156250 14 32161792 00:01:51.1093750 15 37257216 00:07:55.5625000 16 51576832 00:40:27.6718750