Взломы на JavaScript: угрозы для ПК и защита
Размер текста: A+ A-

Взломы на JavaScript: угрозы для ПК и защита

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]

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

В этой статье разберём, какие атаки действительно возможны через JavaScript, как злоумышленник пытается приблизиться к пользовательскому ПК и почему сценарий «запустил JS на сайте — получил полный контроль над Windows» в нормальных условиях не работает.

Отдельно рассмотрим безопасные учебные примеры XSS, DOM XSS, postMessage, небезопасного использования DOM API и способы построения лаборатории для анализа подобных атак без обхода защит браузера.

Содержание скрыть

Что вообще может сделать JavaScript из браузера

Чтобы правильно изучать браузерные атаки, сначала нужно разделить три разных уровня:

  1. JavaScript внутри веб-страницы.
  2. Возможности самого браузера.
  3. Операционная система пользователя.

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

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

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

Поэтому фраза «взлом ПК через JavaScript» технически слишком широкая. Реальный сценарий обычно выглядит как цепочка:

уязвимость веб-приложения → выполнение JavaScript → получение доступа к доступным данным → 

 → злоупотребление разрешениями или интеграциями → дальнейшая атака.

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

Почему браузер не даёт JavaScript полный доступ к компьютеру

Главная идея веб-безопасности заключается в изоляции происхождений и ресурсов.

Для браузера имеет огромное значение понятие origin — комбинация схемы, хоста и порта. Скрипт с одного origin не должен произвольно читать содержимое другого origin.

Например, страница:

https://example.com

не получает автоматический доступ к внутреннему DOM:

https://mail.example.net

только потому, что пользователь одновременно авторизован там.

Это фундаментальное ограничение называется Same-Origin Policy.

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

При этом Same-Origin Policy не означает, что сайты вообще не могут взаимодействовать друг с другом. Браузер поддерживает CORS, postMessage, iframe, редиректы и множество других механизмов. Проблема возникает тогда, когда разработчик неправильно использует эти механизмы.

Самый важный класс: XSS

XSS, или Cross-Site Scripting, возникает тогда, когда атакующий получает возможность заставить приложение интерпретировать контролируемые им данные как HTML или JavaScript.

Для обучения особенно полезно различать:

  • Stored XSS;
  • Reflected XSS;
  • DOM XSS.

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

Именно здесь становится понятна настоящая опасность XSS.

Допустим, пользователь  уже авторизован на сайте:

https://portal.example

Если атакующий добился выполнения собственного JavaScript именно в этом origin, скрипт получает возможности, которыми обладает обычный JavaScript данного сайта.

Это может означать изменение страницы, чтение доступного JavaScript-кода, обращение к API сайта от имени пользователя и работу с данными, доступными текущему origin.

Но XSS всё равно не превращает браузерный JavaScript в универсальную программу для управления компьютером.

Учебный пример DOM XSS

Рассмотрим намеренно уязвимый код:

<div id="output"></div> 
<script> const value = new URLSearchParams(location.search).get("name"); document.getElementById("output").innerHTML = value; 
</script>

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

https://example.test/?name=...

Проблема заключается не в URLSearchParams. Проблема в том, что внешние данные передаются непосредственно в innerHTML.

Если в параметре присутствует HTML, браузер будет воспринимать его как разметку, а в определённых условиях это превращается в XSS.

Для учебной лаборатории можно использовать совершенно безвредную демонстрацию:

<img src=x onerror="alert('DOM XSS')">

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

Как исправить эту уязвимость ?

Если HTML действительно не требуется, использовать innerHTML для пользовательского текста вообще не нужно.

Безопасный вариант:

<div id="output"></div> 
<script> const value = new URLSearchParams(location.search).get("name"); document.getElementById("output").textContent = value ?? ""; 
</script>

Теперь строка воспринимается именно как текст.

Это одна из самых важных практик при разработке JavaScript-приложений: данные и код должны оставаться разными сущностями.

Почему eval() особенно опасен

Отдельная категория проблем связана с динамическим выполнением строк как JavaScript.

Например:

const code = userInput; 
eval(code);

Такой код фактически говорит программе: «считай внешние данные JavaScript-программой».

Если источник данных контролируется атакующим, граница между данными и кодом исчезает.

Гораздо правильнее использовать явные структуры данных:

const config = JSON.parse(userInput);

а затем проверять допустимые поля:

if (typeof config.name === "string") 
{ console.log(config.name); }

JSON.parse() разбирает данные. Он не должен использоваться как средство произвольного исполнения JavaScript.

Почему XSS опасен даже без доступа к файлам Windows

Ошибка часто возникает из-за неверного представления о том, что злоумышленник обязательно должен получить cmd.exe.

На практике атакующему не всегда это нужно.

Если JavaScript выполняется внутри доверенного приложения, скрипт может:

  1. читать данные, которые приложение уже показывает пользователю;
  2. отправлять запросы к API от имени пользователя;
  3. изменять интерфейс;
  4. подменять ссылки и формы;
  5. вмешиваться в рабочий процесс пользователя;
  6. атаковать другие части самого приложения;
  7. использовать полномочия текущей учётной записи там, где приложение не разделяет права достаточно строго.

Поэтому XSS может быть критической уязвимостью даже при полностью исправной Windows.

Опасная ошибка с postMessage

window.postMessage() нужен для безопасного обмена сообщениями между окнами разных происхождений.

Проблема возникает, когда разработчик доверяет любому отправителю.

Небезопасный код:

window.addEventListener("message", event => { 
document.querySelector("#output").innerHTML = event.data; 
}
);

В данном варианте приложение фактически принимает произвольную строку из внешнего окна и помещает её в опасный DOM-sink.

Более безопасная архитектура начинается с проверки источника:

window.addEventListener("message", event => { if (event.origin !== "https://trusted.example") 
{ return; } 
if (typeof event.data !== "string") { return; } 
document.querySelector("#output").textContent = event.data; }
);

Здесь исправлены сразу две проблемы:

  1. проверяется origin;
  2. сообщение выводится как текст, а не как HTML.

При сложных протоколах обмена лучше проверять не только origin, но и структуру сообщения.

Например:

window.addEventListener("message", event => { 
if (event.origin !== "https://trusted.example") { return; } 
const message = event.data; if (!message || typeof message !== "object") { return; } 
if (message.type !== "status") { return; } if (typeof message.value !== "string") { return; } 
console.log(message.value); }
);

Такой подход значительно надёжнее, чем доверие любому message.

Как JavaScript может взаимодействовать с файлами

Современный браузер располагает более мощными API для работы с файлами, чем старые версии веб-платформы.

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

Например, приложение может попросить пользователя выбрать файл:

const picker = document.createElement("input"); 
picker.type = "file"; 
picker.addEventListener("change", () => { const file = picker.files?.[0]; 
if (!file) { return; } console.log(file.name); 
console.log(file.size); }); 
picker.click();

Сам факт существования File и FileList не означает, что страница может самостоятельно открыть:

C:\Users\User\Documents\private.txt

без участия пользователя.

Это принципиальное различие между «браузер умеет работать с файлами» и «любой сайт может читать диск».

Что изменилось с современными файловыми API

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

Но разрешения остаются частью модели безопасности.

Вместо:

сайт → любой файл диска

модель строится ближе к:

сайт → запрос доступа → решение браузера/пользователя → разрешённый ресурс

Для защиты разработчику важно не считать наличие API доказательством наличия полного доступа.

Могут ли через JavaScript получить файлы пользователя без его участия

В нормальной современной реализации браузера произвольный веб-сайт не должен иметь возможность просто выполнить:

readFile("C:\\Users\\User\\Passwords.txt");

и получить содержимое.

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

Это важное различие при анализе инцидентов.

Что такое «выход из sandbox»

Sandbox браузера можно рассматривать как границу между веб-контентом и более привилегированными компонентами системы.

Упрощённо:

Веб-страница 
↓ 
JavaScript engine 
↓ 
Browser process / renderer 
↓ 
Sandbox
↓ 
Операционная система

Если обычная JavaScript-программа внезапно получает возможности уровня ОС, значит нарушена одна из границ.

Это может быть:

  • ошибка движка JavaScript;
  • ошибка браузерного IPC;
  • ошибка renderer process;
  • ошибка sandbox;
  • уязвимость привилегированного процесса;
  • ошибка расширения;
  • взаимодействие с установленной программой;
  • цепочка нескольких уязвимостей.

Такие атаки уже относятся к классу browser exploitation и не являются обычным использованием JavaScript API.

Почему код обхода sandbox нельзя заменить «хитрым ES2026»

Новая версия ECMAScript не отменяет архитектуру безопасности браузера.

Даже если в языке появляются новые возможности синтаксиса или стандартных объектов, они не означают автоматического получения доступа к:

Windows API 
↓ 
процессам 
↓ 
файловой системе 
↓ 
ядру ОС

JavaScript — язык.

Браузерные API — интерфейс.

Sandbox — граница безопасности.

Эти вещи нельзя смешивать.

Поэтому задача «найти специальную конструкцию ES2026, которая заставит обычный сайт выйти из sandbox» принципиально отличается от поиска новой языковой возможности.

Настоящие пути от JavaScript к атаке на ПК

Для специалиста по безопасности полезнее анализировать не мифический «JS-взлом», а реальные цепочки.

XSS → данные приложения

Сценарий:

уязвимый веб-сайт
↓ 
XSS 
↓ 
JavaScript в доверенном origin 
↓ 
доступ к данным приложения

Это наиболее распространённая категория.


Вредоносное расширение → повышенные права

Расширение браузера может обладать возможностями, которых обычная веб-страница не имеет.

Поэтому заражённое или подменённое расширение — совершенно другой класс угроз.


Веб-сайт → локальная программа

Система может содержать установленное ПО, которое принимает команды из браузера.

Например:

браузер 
↓ 
локальный service/helper 
↓ 
операционная система

Именно здесь появляются реальные риски выполнения операций вне sandbox.

Атака в таком случае направлена уже не только на JavaScript, а на плохо защищённый интерфейс между браузером и локальным компонентом.

Browser exploit chain и пример

Наиболее сложный сценарий:

уязвимость в веб-контенте
        ↓
уязвимость browser engine
        ↓
выход из renderer/sandbox
        ↓
дополнительная уязвимость
        ↓
повышение привилегий

Это уже полноценная эксплуатация программного продукта.

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

Например, для обучения можно сделать искусственную модель Browser Exploit Chain, где каждый этап только имитируется и не содержит реального обхода sandbox или повышения привилегий:

<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Browser Exploit Chain Lab</title>
</head>
<body>
<h1>Учебная модель Browser Exploit Chain</h1>

<button id="run">Запустить цепочку</button>

<pre id="log"></pre>

<script>
const log = document.getElementById("log");

function write(message) {
log.textContent += message + "\n";
}

function vulnerableWebContent() {
write("[1] Веб-контент: получен контролируемый ввод");
return {
payload: "<img src=x onerror='demo()'>"
};
}

function browserEngineSimulation(input) {
write("[2] Browser Engine: обнаружена условная ошибка обработки");

// Никакой реальной уязвимости здесь нет.
// Просто меняем состояние модели.
return {
rendererCompromised: true,
input
};
}

function sandboxEscapeSimulation(state) {
if (!state.rendererCompromised) {
throw new Error("Renderer не скомпрометирован");
}

write("[3] Sandbox: имитируется успешный выход");

return {
...state,
sandboxEscaped: true
};
}

function privilegeEscalationSimulation(state) {
if (!state.sandboxEscaped) {
throw new Error("Выход из sandbox не выполнен");
}

write("[4] Privilege Boundary: имитируется повышение привилегий");

return {
...state,
elevated: true
};
}

document.getElementById("run").addEventListener("click", () => {
log.textContent = "";

try {
const input = vulnerableWebContent();
const renderer = browserEngineSimulation(input);
const escaped = sandboxEscapeSimulation(renderer);
const finalState = privilegeEscalationSimulation(escaped);

write("");
write("[+] Учебная цепочка завершена");
write(JSON.stringify(finalState, null, 2));
} catch (error) {
write("[!] Ошибка: " + error.message);
}
});
</script>
</body>
</html>

Здесь такая схема:

вредоносный ввод
↓
уязвимость веб-приложения
↓
"компрометация renderer" — имитация
↓
"выход из sandbox" — имитация
↓
"повышение привилегий" — имитация

При нажатии кнопки получится примерно:

[1] Веб-контент: получен контролируемый ввод
[2] Browser Engine: обнаружена условная ошибка обработки
[3] Sandbox: имитируется успешный выход
[4] Privilege Boundary: имитируется повышение привилегий

[+] Учебная цепочка завершена

Что здесь соответствует реальной цепочке:

  1. vulnerableWebContent() имитирует первый этап, когда атакующий получает возможность влиять на веб-контент.
  2. browserEngineSimulation() представляет гипотетическую ошибку в движке браузера. В реальной атаке здесь могла бы находиться уязвимость класса use-after-free, type confusion или ошибка работы с памятью, но в лаборатории мы её не реализуем.
  3. sandboxEscapeSimulation() показывает следующий уровень: renderer больше не ограничен первоначальной моделью изоляции.
  4. privilegeEscalationSimulation() показывает последний этап, когда атаке удалось перейти через ещё одну границу привилегий.

Главное здесь — состояния, а не настоящий exploit:

{
rendererCompromised: true,
sandboxEscaped: true,
elevated: true
}

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

if (!input) {
write("[BLOCK] Нет вредоносного ввода");
return;
}

или:

if (!state.rendererCompromised) {
write("[BLOCK] Browser Engine остановил цепочку");
return;
}

Это хороший способ объяснить программистам, что browser exploit обычно не является одной магической командой: это цепочка независимых нарушений границ безопасности.

Реальный код для выхода из sandbox и повышения привилегий на актуальном браузере уже был бы практически применимым exploit-кодом, поэтому для обучения безопаснее моделировать эти этапы таким образом.

Пример эксплойта на JavaScript

Рабочий JS-эксплойт, который реально обходит sandbox современного браузера, я не дам: такой код можно непосредственно использовать для компрометации ПК.

Для обучения можно показать безопасную имитацию той же логики:

// Учебная модель browser exploit chain
const browser = {
renderer: true,
sandbox: true,
privileges: "user"
};

function exploitEngine() {
// Имитация уязвимости движка
browser.renderer = false;
console.log("[+] Renderer скомпрометирован");
}

function escapeSandbox() {
if (!browser.renderer) {
browser.sandbox = false;
console.log("[+] Sandbox условно обойдён");
}
}

function elevatePrivileges() {
if (!browser.sandbox) {
browser.privileges = "admin";
console.log("[+] Привилегии условно повышены");
}
}

exploitEngine();
escapeSandbox();
elevatePrivileges();

console.log(browser);

Это не ломает браузер и не получает доступ к ОС: код лишь моделирует типичную цепочку уязвимость движка → компрометация renderer → выход из sandbox → повышение привилегий, чтобы было понятно, какие границы должен преодолеть настоящий browser exploit.

Вывод при запуске (например, в Node.js или консоли браузера):

[+] Renderer скомпрометирован
[+] Sandbox условно обойдён
[+] Привилегии условно повышены
{ renderer: false, sandbox: false, privileges: 'admin' }
  1. exploitEngine() «компрометирует» рендерер, устанавливая browser.renderer = false.

  2. escapeSandbox() проверяет, что рендерер скомпрометирован, и обходит песочницу (browser.sandbox = false).

  3. elevatePrivileges() проверяет, что песочница обойдена, и повышает привилегии до "admin".

Безопасная имитация атаки на ПК

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

Например:

const events = [ "Получен ввод пользователя", "Попытка доступа к защищённому ресурсу", 
"Браузер запросил разрешение", "Доступ запрещён политикой браузера", "Веб-страница осталась внутри sandbox" ]; 

for (const event of events) { console.log(event); }

Затем преподаватель может визуализировать каждый этап атаки.

Это позволяет объяснить архитектуру без создания работающего инструмента компрометации.

Учебный XSS-полигон

Для практики лучше создать локальное приложение.

Например:

xss-lab/
├── index.html
├── vulnerable.html
├── secure.html
└── app.js

Уязвимая версия:

const input = new URLSearchParams(location.search).get("q"); 
document.querySelector("#result").innerHTML = input ?? "";

Защищённая:

const input = new URLSearchParams(location.search).get("q"); 
document.querySelector("#result").textContent = input ?? "";

Затем можно сравнить поведение на безопасном тестовом payload:

<img src=x onerror="alert('XSS')">

После этого эксперимент следует повторить с:

textContent

и увидеть, что строка больше не интерпретируется как HTML.

Это уже полноценный практический урок по механике XSS.

Как изучать CSP

Content Security Policy позволяет серверу объявить правила, какие источники ресурсов и какие способы выполнения кода допустимы для страницы.

Для лабораторной среды можно начать с простой политики:

Content-Security-Policy: default-src 'self'; script-src 'self'

После этого отдельно исследовать:

  • inline JavaScript;
  • внешние скрипты;
  • eval();
  • iframe;
  • изображения;
  • сетевые подключения;
  • nonce;
  • hash-based policies.

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

При этом CSP нельзя рассматривать как замену безопасному программированию. Если приложение без причины использует опасные DOM API, исправлять саму уязвимость всё равно необходимо.

Trusted Types и DOM XSS

Современная защита веб-приложений постепенно смещается от общего принципа «программист должен помнить, где нельзя использовать innerHTML» к более формальным ограничениям опасных DOM-sink.

Концепция Trusted Types предназначена именно для того, чтобы сделать передачу произвольных строк в чувствительные DOM API контролируемой.

Упрощённо идея выглядит так:

обычная строка 
↓ 
опасный DOM API 
↓ 
потенциальный XSS

против:

данные 
↓ 
проверенная политика 
↓ 
Trusted Type 
↓ 
опасный DOM API

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

Опасные DOM-sink, которые нужно искать при аудите

При ручном анализе JavaScript-кода в первую очередь стоит обращать внимание на конструкции, которые превращают данные в HTML или JavaScript.

Например:

element.innerHTML = value;
element.outerHTML = value;
document.write(value);
new Function(value);
eval(value);

Но сам факт наличия такого вызова ещё не означает уязвимость.

Нужно определить происхождение данных:

данные пользователя
↓ 
обработка 
↓ 
валидация / sanitization 
↓ 
опасный sink

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

Supply-chain атаки через npm-пакеты

Современное JS-приложение тянет за собой не десятки, а зачастую тысячи транзитивных зависимостей, и у каждой из них при установке или сборке оказываются те же привилегии, что и у собственного кода проекта.

Это открывает целый класс атак на цепочку поставки: тайпсквоттинг, когда публикуется пакет с именем, похожим на популярный, в расчёте на опечатку при установке; захват аккаунта легитимного мейнтейнера с последующей публикацией вредоносной версии в доверенный пакет; dependency confusion, когда публичный пакет с тем же именем, что и внутренний приватный, подсовывается сборочной системе вместо настоящего; и вредоносные postinstall-скрипты, которые запускаются автоматически при обычной команде установки, без какого-либо дополнительного действия со стороны разработчика.

Опасность здесь обычно скрыта не в основном коде библиотеки, а в поле scripts файла package.json: postinstall-хук способен обратиться к внешнему серверу и передать содержимое переменных окружения или файлов конфигурации, и происходит это в момент обычного npm install, до того как разработчик успеет открыть код пакета и что-либо проверить.

Защита строится на нескольких независимых слоях. Lock-файлы (package-lock.json, yarn.lock, pnpm-lock.yaml) фиксируют не только версию, но и контрольную сумму пакета, поэтому подмена содержимого при той же версии обнаруживается автоматически. Регулярный аудит зависимостей инструментами вроде npm audit и специализированных сканеров помогает поймать уже известные уязвимости в дереве пакетов. Флаг --ignore-scripts или установка только по lock-файлу в CI-окружении убирает риск автоматического запуска произвольного postinstall-кода. Минимизация числа зависимостей и предпочтение крупных, активно поддерживаемых библиотек снижает саму площадь атаки. А для скриптов, подключаемых напрямую с CDN в HTML, стоит использовать Subresource Integrity — атрибут integrity с хэшем ожидаемого содержимого, из-за которого браузер откажется выполнять файл, если CDN отдаст что-то, отличное от заявленного хэша.

Безопасно показать механику можно на локальном учебном пакете:

package.json:

{
"name": "demo-package",
"version": "1.0.0",
"scripts": {
"postinstall": "node postinstall.js"
}
}

postinstall.js:

console.log("[demo] postinstall запущен автоматически");
console.log("[demo] пакет выполняет свой код во время npm install");

Теперь:

npm install ./demo-package

После установки npm автоматически запустит postinstall.js.

В реальной supply-chain атаке вместо безобидного console.log() злоумышленник мог бы поместить туда вредоносную логику; именно поэтому опасность заключается в том, что скрипт зависимости запускается как часть обычного процесса установки. Для проверки такого сценария в CI применяют npm install --ignore-scripts, а содержимое зависимостей фиксируют lock-файлами.

Clickjacking: атака, которую не видно на экране

Кликджекинг строится на том, что браузер по умолчанию разрешает встраивать практически любую страницу в <iframe> с чужого сайта.

Атакующий размещает поверх безобидной с виду кнопки прозрачный iframe с реальной страницей жертвы — например, кнопкой «подтвердить» или «удалить аккаунт» на сервисе, где пользователь авторизован, — так, чтобы видимый клик по декоративному элементу на самом деле попадал по невидимому элементу внутри iframe.

css:
.decoy-frame { 
position: absolute; 
top: 0; 
left: 0; 
width: 300px; 
height: 50px; 
opacity: 0;
z-index: 10; }

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

Главная защита живёт на уровне HTTP-заголовков, а не клиентского JavaScript. Заголовок X-Frame-Options со значением DENY или SAMEORIGIN запрещает встраивание страницы в чужой фрейм; более гибкая современная замена — директива frame-ancestors в Content Security Policy, которая, в отличие от X-Frame-Options, позволяет перечислить конкретный список разрешённых источников.

Скрипты для «взлома» фрейма на стороне клиента (frame-busting) существуют, но считаются ненадёжным резервным вариантом: они сами обходятся через sandbox-атрибуты iframe, поэтому полагаться стоит на заголовки, а не на JS-проверку.

Учебный пример clickjacking:

<style>
.decoy {
position: relative;
width: 300px;
height: 50px;
background: #ddd;
text-align: center;
padding: 15px;
}

.target {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 50px;
opacity: 0;
z-index: 2;
}
</style>

<div class="decoy">Нажмите здесь, чтобы продолжить</div>

<iframe
class="target"
src="https://example.com/account">
</iframe>

В учебной лаборатории iframe размещается поверх видимой кнопки и становится прозрачным через opacity: 0; поэтому пользователь видит один элемент, а фактический клик может попасть в элемент внутри iframe. Реальная защита должна находиться на стороне страницы, которую пытаются встроить: например, Content-Security-Policy: frame-ancestors 'none' или X-Frame-Options: DENY, а не в клиентском JavaScript атакуемой страницы.

Prototype Pollution

Ещё один интересный класс JavaScript-уязвимостей — prototype pollution.

Проблема возникает, когда приложение позволяет внешним данным неконтролируемо изменять свойства объектов или их прототипов.

Упрощённый небезопасный пример:

function merge(target, source) { 
for (const key in source) { target[key] = source[key]; 
} 
return target; }

В реальном приложении необходимо дополнительно учитывать специальные ключи и способ обхода объекта.

Безопаснее использовать строго определённую схему данных:

function parseUserOptions(input) { 
if (!input || typeof input !== "object") { return { theme: "light" }; 
} 
return { theme: input.theme === "dark" ? "dark" : "light" }; 
}

Здесь приложение не пытается слепо копировать всю структуру внешнего объекта.

Почему prototype pollution иногда превращается в серьёзную атаку ?

Prototype pollution сама по себе не означает «получение управления ПК».

Но если загрязнённые свойства начинают влиять на:

  1. конфигурацию приложения;
  2. URL;
  3. шаблоны;
  4. параметры команд;
  5. настройки серверной библиотеки;
  6. обработчики объектов;

то уязвимость может стать частью более длинной цепочки.

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

Как атакующий использует JavaScript против пользователя, не ломая браузер

На практике достаточно часто злоумышленнику вообще не требуется обходить sandbox.

Например, страница может имитировать интерфейс известного сервиса:

«Для продолжения войдите снова»

После этого JavaScript занимается не взломом браузера, а социальной инженерией.

Это уже фишинг.

Другая модель:

вредоносная реклама 
↓ 
перенаправление 
↓ 
страница с социальной инженерией 
↓ 
пользователь выдаёт разрешение 
↓ 
легитимный API получает доступ

С точки зрения защитника это очень важный момент.

Не каждая успешная атака на пользователя является exploit’ом.

Пример

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

Например, такой код создаёт правдоподобное окно авторизации, но намеренно не отправляет введённые данные на сервер:

<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Учебная демонстрация фишинга</title>
</head>
<body>
<h2>Для продолжения войдите снова</h2>

<form id="login">
<input id="email" type="email" placeholder="Почта" required>
<input id="password" type="password" placeholder="Пароль" required>
<button type="submit">Войти</button>
</form>

<p id="result"></p>

<script>
document.getElementById("login").addEventListener("submit", event => {
event.preventDefault();

document.getElementById("result").textContent =
"Демонстрация: форма имитирует фишинговый экран. " +
"Данные никуда не отправляются.";
});
</script>
</body>
</html>

Здесь JavaScript вообще не взламывает браузер и не обходит sandbox.

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

Вторая модель — злоупотребление разрешениями — также не требует обхода sandbox. Например, браузер позволяет веб-приложениям запросить доступ к камере или микрофону, но пользователь должен предоставить соответствующее разрешение. Учебный пример можно сделать без записи данных:

const button = document.querySelector("#request");

button.addEventListener("click", async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});

document.querySelector("#status").textContent =
"Разрешение получено. В лаборатории поток сразу остановлен.";

stream.getTracks().forEach(track => track.stop());
} catch {
document.querySelector("#status").textContent =
"Пользователь отказал или доступ недоступен.";
}
});

Такой пример хорошо показывает принцип цепочки «страница → запрос разрешения → пользователь → легитимный API».

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

Разница между exploit и abuse легитимного API

Полезно разделять:

Exploit:

  • Используется ошибка безопасности, которой вообще не должно существовать.

Abuse:

  • Используется штатная функция системы так, как разработчик не предполагал.

Пример:

Exploit: уязвимость браузера → произвольный код вне sandbox 
Abuse: пользователь сам выдал веб-сайту разрешение → сайт использует разрешённые возможности

Защита в этих двух случаях совершенно разная.

Что можно исследовать без создания вредоносного exploit

Для обучения разработчиков гораздо полезнее построить набор лабораторных задач.

Первая лаборатория — DOM XSS:

innerHTML + пользовательский ввод

Вторая — postMessage:

message.origin + message.data

Третья — небезопасные редиректы:

location.href = userInput;
  • Четвёртая — prototype pollution.
  • Пятая — небезопасный доступ к API.
  • Шестая — CSP.
  • Седьмая — разрешения браузера.
  • Восьмая — защита файловых API.
  • Девятая — уязвимое браузерное расширение в изолированной тестовой среде.

Такой набор даёт программисту значительно более полезное понимание реальной безопасности, чем копирование готового browser exploit.

Какие признаки должны насторожить разработчика

При чтении чужого JavaScript-кода особенно внимательно стоит искать следующие конструкции:

innerHTML
outerHTML
document.write()
eval()
new Function()
window.postMessage()
message.data
location.href = ...
location.assign(...)
location.replace(...)
iframe.src = ...
Object.assign(...)
for ... in

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

Самая важная часть аудита — определить, откуда пришли данные и куда они затем попали.

Почему «обойти ограничения браузера» — неправильная цель обучения ?

Если задача курса заключается в обучении безопасности, полезнее сформулировать её иначе:

«Понять, при каких условиях стандартная модель безопасности браузера ломается и как обнаружить подобную цепочку».

Это позволяет изучать действительно интересные темы:

XSS 
↓ 
same-origin 
↓ 
CORS 
↓ 
CSRF 
↓ 
postMessage 
↓ 
CSP 
↓ 
Trusted Types 
↓ 
permissions 
↓ 
extensions 
↓ 
browser vulnerabilities 
↓ sandbox

В таком подходе разработчик понимает не только «как что-то взломать», но и почему защита существует.

Что действительно относится к атаке на сам браузер

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

HTML parser 
JavaScript engine 
DOM implementation 
IPC renderer 
GPU process 
sandbox 
privileged browser components

Именно здесь появляются такие классы ошибок, как:

  • use-after-free;
  • out-of-bounds access;
  • type confusion;
  • integer overflow;
  • race condition;
  • logic flaw;
  • sandbox escape.

Но практический exploit-chain для выхода из sandbox и выполнения произвольных команд на машине жертвы — это уже эксплуатация реальной уязвимости, а не обычный JavaScript-пример.

Для безопасного обучения такие вещи лучше исследовать на специально созданных vulnerable targets, CTF-задачах или искусственных бинарях, где нет риска воздействия на реальные системы.

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

При подозрительном поведении необходимо задать несколько вопросов.

Сначала нужно выяснить:

Работает ли это в чистом браузере?

Затем:

Нужно ли разрешение пользователя?

Далее:

Есть ли установленное расширение?

После этого:

Есть ли локальный helper или приложение-компаньон?

И только потом:

Происходит ли настоящий выход из sandbox?

Это резко сокращает количество ложных выводов.

Очень многие «эксплойты JavaScript» при детальной проверке оказываются одним из следующих случаев:

XSS 
+ 
социальная инженерия 
+ 
разрешение пользователя 
+ 
вредоносное расширение

а не уязвимостью браузера.

Минимальный набор защит для JavaScript-приложения

Безопасность должна строиться слоями.

На уровне JavaScript:

element.textContent = userInput;

вместо бездумного:

element.innerHTML = userInput;
  • На уровне архитектуры необходимо минимизировать доверие к внешним данным.
  • На уровне HTTP нужно использовать защитные заголовки и правильно настроенную CSP.
  • На уровне приложения необходимо разделять права пользователя и серверные полномочия.
  • На уровне браузера нельзя предполагать, что наличие современного API означает абсолютную безопасность.
  • На уровне инфраструктуры важно минимизировать количество установленных расширений и программ, которые создают мост между браузером и ОС.

Здесь остановимся на 2 примерах. 

Каким образом обходят слабую защиту в этих случаях ?

innerHTML — обход слабой фильтрации

Фильтр запрещает только слово script:

if (!input.includes("script")) {
element.innerHTML = input;
}

В учебном стенде проверяют другой HTML-синтаксис, например:

<img src=x onerror="alert('XSS')">

Потому что защита фильтрует конкретное слово, а не устраняет саму возможность интерпретации HTML.

Второй вариант — фильтр удаляет <script>, но оставляет HTML:

input = input.replace(/<script.*?>.*?<\/script>/gi, "");
element.innerHTML = input;

Проблема остаётся: удалён только один конкретный шаблон, а innerHTML всё ещё принимает недоверенный HTML. Правильное исправление — не пытаться бесконечно дополнять blacklist, а использовать textContent либо корректную sanitization.


postMessage — обход слабой проверки

Защита проверяет только тип данных:

window.addEventListener("message", e => {
if (typeof e.data !== "string") return;
element.innerHTML = e.data;
});

Проверка проходит, потому что вредоносное содержимое тоже является обычной строкой. Проблема остаётся в innerHTML.

Второй вариант — разработчик проверяет origin, но оставляет опасный DOM-sink:

window.addEventListener("message", e => {
if (e.origin !== "https://trusted.example") return;

element.innerHTML = e.data;
});

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

Поэтому правильная защита требует одновременно проверять источник и не интерпретировать сообщение как HTML.

Главный вывод

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

Основные реальные риски начинаются с XSS, ошибок DOM, неправильного postMessage, небезопасной работы с объектами, слабой CSP, ошибочных разрешений и опасных интеграций между браузером и локальным ПО. Если появляется возможность обойти sandbox без предусмотренного разрешения и выполнить произвольный код на ОС, это уже признак отдельной уязвимости браузера, расширения или программного компонента, а не «секретной возможности ES2026».

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

Непроверенный ввод 
↓ 
опасный API 
↓ 
ошибка приложения 
↓ 
JavaScript в привилегированном контексте
↓ 
злоупотребление доступными полномочиями

А для атак уровня самого ПК:

Web 
↓ 
Browser vulnerability 
↓ 
Renderer compromise 
↓ 
Sandbox escape 
↓ 
Privilege boundary 
↓ 
OS compromise

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

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]
Ethan Carter

Я, Итан Картер – американский разработчик и технический автор с более чем 20-летним опытом в системном и прикладном программировании. Мой основной профиль — низкоуровневая разработка на Assembler: 22 года практики, включая глубокую работу с оптимизацией кода, архитектурой процессоров и производительностью критичных по скорости решений. Я защитил PhD dissertation по Assembler, а также более 18 лет работаю с ASP.NET, создавая корпоративные веб-системы, API и масштабируемые backend-решения.

Дополнительно я имею 9 лет опыта в C++ и C#, а также 7 лет практики программирования микроконтроллеров на Assembler. Благодаря моему сочетанию академической подготовки и прикладного инженерного опыта я могу писать статьи на стыке архитектуры ПО, низкоуровневой оптимизации и современной разработки, делая сложные технические темы понятными для профессиональной аудитории.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.

О нас | Контакты


Прокрутить вверх