Поиск багов в Javascript приложениях

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0

Авторство: hackeryaroslav

Источник: xss.is



Интро

Всем привет, сегодня мы с вами разберем очень много. Писал статью не один день, надеюсь вам зайдет. Это та самая статья которая фокусируется на практических аспектах поиска уязвимостей в JavaScript. Каждый раздел содержит рабочий код и объяснение техник. Готовы? Поехали!

a-black-and-white-photo-of-elon-musk-sitting-in-a--MucB7h-sQPKE_8fBHNSJhA-QIWzN5J2To6JBC1r9C5...jpeg


1. Манипуляции с прототипами для обнаружения уязвимостей

Prototype Pollution через конструкторы

Prototype Pollution (поллюция прототипа) — одна из критических уязвимостей в JavaScript, позволяющая атакующим модифицировать глобальный объект Object.prototype. Это может привести к неожиданному поведению в приложениях, что легко использовать для выполнения зловредного кода или обхода логики безопасности.

JavaScript: Скопировать в буфер обмена
Код:
function detectPrototypePollution(obj) {
const origProto = Object.prototype.toString;
const payload = {
toString: () => {
console.log('Prototype pollution detected!');
return origProto.call(this);
}
};

Object.setPrototypeOf(obj, payload);
return Object.prototype.hasOwnProperty.call(obj, 'toString');
}

Объяснение кода:
  1. Создание полезной нагрузки: Создается объект payload, переопределяющий метод toString, который выводит сообщение о том, что произошло загрязнение прототипа.
  2. Подмена прототипа: Object.setPrototypeOf заменяет прототип obj на payload. Если код уязвим, метод toString объекта obj будет переопределен, и вызов toString приведет к исполнению полезной нагрузки.
  3. Проверка наличия уязвимости: Возвращается результат вызова Object.prototype.hasOwnProperty.call(obj, 'toString'), чтобы проверить, был ли переопределен метод toString.
Пример уязвимого кода:

JavaScript: Скопировать в буфер обмена
Код:
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}

Этот код уязвим для загрязнения прототипа, так как source может содержать ключи вроде __proto__, которые изменят глобальный прототип объекта target.

Эксплуатация: Если передать в source объект {"__proto__": {polluted: "yes"}}, то свойство polluted добавится ко всем объектам, что позволяет обойти проверку прав доступа и изменить поведение кода.

Техника перехвата через Proxy

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

JavaScript: Скопировать в буфер обмена
Код:
function createVulnerabilityProxy(target) {
return new Proxy(target, {
get(target, prop) {
console.log(`Accessing property: ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting property: ${prop} = ${value}`);
target[prop] = value;
return true;
}
});
}

Объяснение кода:
  1. Метод get: Отслеживает доступ к свойствам, позволяя фиксировать все обращения к данным объекта, что помогает обнаружить подозрительные обращения.
  2. Метод set: Перехватывает любые попытки записи значений. Это можно использовать для обнаружения и предотвращения атак через подмену данных в реальном времени.
Практическое применение: Создание Proxy на целевой объект позволяет анализировать его поведение и выявлять, какие методы или свойства могут быть уязвимы к манипуляциям.

2. DOM-based XSS через Shadow DOM

DOM-based XSS — это атака, при которой вредоносный код выполняется на стороне клиента, манипулируя DOM. Shadow DOM позволяет инкапсулировать элементы, что затрудняет обнаружение XSS, так как традиционные механизмы защиты не видят содержимое Shadow DOM.

Техника внедрения через теневой DOM

JavaScript: Скопировать в буфер обмена
Код:
function detectShadowDOMXSS(element) {
const shadow = element.attachShadow({mode: 'open'});
const template = document.createElement('template');
template.innerHTML = `
<style>
::slotted(*) { color: red; }
</style>
<slot></slot>
`;

shadow.appendChild(template.content.cloneNode(true));

// Проверка на XSS через стили
const style = shadow.querySelector('style');
return style.textContent.includes('</style>');
}

Объяснение:
  1. Создание Shadow DOM: Создается Shadow DOM с режимом open, что позволяет обращаться к нему и модифицировать его содержимое.
  2. Инъекция стилей: Создается шаблон template, в котором можно внедрить стили или слоты. Это позволяет имитировать инъекцию кода в закрытую часть страницы.
  3. Проверка на XSS: Метод includes('</style>') проверяет, есть ли в стилях вредоносный код. Если аттакер вставил </style>, то это может быть индикацией попытки XSS, так как закрытие стиля позволяет вставить HTML-код.
Практическое применение: Shadow DOM можно использовать для обхода механизмов защиты от XSS и внедрения вредоносного кода.

3. Race Conditions в асинхронном коде
Race Condition — это ситуация, при которой два или более параллельных процесса или потока выполняются с неконтролируемым порядком выполнения, что приводит к непредсказуемым результатам. В JavaScript, благодаря его асинхронной природе, такие ситуации возникают при работе с async/await, промисами и операциями ввода-вывода. Это может привести к багам, когда, например, два асинхронных запроса конфликтуют друг с другом или возвращают разные данные.

Детектор race condition

JavaScript: Скопировать в буфер обмена
Код:
async function detectRaceCondition(asyncOperation) {
let operationStatus = [];

const execute = async (delay) => {
await new Promise(resolve => setTimeout(resolve, delay));
const result = await asyncOperation();
operationStatus.push({delay, result});
};

// Запускаем операции с разными задержками
await Promise.all([
execute(0),
execute(10),
execute(20)
]);

// Анализируем результаты на несоответствия
return operationStatus.some((status, index) =>
index > 0 && status.result !== operationStatus[0].result
);
}

Объяснение кода:
  1. Асинхронное выполнение с задержкой: Функция execute запускает асинхронную операцию с указанной задержкой, чтобы эмулировать случайное выполнение.
  2. Сбор результатов: Все вызовы asyncOperation записываются в массив operationStatus, сохраняя задержку и результат выполнения.
  3. Анализ расхождений: Проверяется, возвращают ли все операции одинаковый результат. Если результаты отличаются, это указывает на race condition.

4. Memory Leaks через замыкания

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

Детектор утечек памяти

JavaScript: Скопировать в буфер обмена
Код:
function createMemoryLeakDetector() {
const weakMap = new WeakMap();
let leakFound = false;

return {
track(object, name) {
weakMap.set(object, {
name,
timestamp: Date.now()
});
},

check(object) {
if (weakMap.has(object)) {
const data = weakMap.get(object);
const timeElapsed = Date.now() - data.timestamp;

if (timeElapsed > 30000) { // 30 секунд
leakFound = true;
console.warn(`Potential memory leak detected for object: ${data.name}`);
}
}
},

hasLeak() {
return leakFound;
}
};
}

Объяснение:
  1. Использование WeakMap: WeakMap позволяет отслеживать объекты без предотвращения их удаления сборщиком мусора. Если объект теряет все ссылки, он автоматически очищается.
  2. Отслеживание объекта: Метод track добавляет объект в WeakMap с именем и временем отслеживания.
  3. Проверка на утечку: Если объект остается в WeakMap более 30 секунд, это указывает на возможную утечку памяти, и в консоль выводится предупреждение.
Практическое применение: Этот детектор позволяет выявить случаи, когда объекты остаются в памяти дольше, чем ожидается, что может быть признаком утечки.

5. Event Loop Exploitation

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

Детектор блокировки Event Loop

JavaScript: Скопировать в буфер обмена
Код:
function detectEventLoopBlocking() {
let lastTickTime = Date.now();
let blocked = false;

const checker = setInterval(() => {
const currentTime = Date.now();
const diff = currentTime - lastTickTime;

if (diff > 100) { // Блокировка более 100мс
blocked = true;
console.warn(`Event loop blocked for ${diff}ms`);
}

lastTickTime = currentTime;
}, 50);

return {
stop() {
clearInterval(checker);
},
isBlocked() {
return blocked;
}
};
}

Объяснение:
  1. Отслеживание времени между тактами: setInterval запускается каждые 50 мс, фиксируя время каждого "тика" в lastTickTime.
  2. Выявление блокировки: Если разница между текущим и последним тактом превышает 100 мс, это указывает на возможную блокировку Event Loop, и выводится предупреждение.
  3. Функция остановки: Метод stop очищает интервал, позволяя остановить проверку при необходимости.
Практическое применение: Этот детектор выявляет участки кода, которые блокируют Event Loop и создают лаги.

6. Эксплуатация WebWorker уязвимостей

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

Детектор небезопасных сообщений

JavaScript: Скопировать в буфер обмена
Код:
function createWorkerMessageDetector() {
const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = function(e) {
// Проверяем сообщения на потенциально опасные данные
const message = e.data;
const dangerous = /(\(|\)|new|eval|function)/.test(
typeof message === 'string' ? message : JSON.stringify(message)
);

self.postMessage({
original: message,
dangerous
});
}
`])));

return {
checkMessage(message) {
return new Promise(resolve => {
worker.onmessage = (e) => resolve(e.data);
worker.postMessage(message);
});
},

terminate() {
worker.terminate();
}
};
}

Объяснение:
  1. Создание Worker'а с проверкой сообщений: Код Worker'а обрабатывает каждое полученное сообщение и проверяет его на наличие потенциально опасных конструкций (например, eval, function), которые могут быть использованы для выполнения кода.
  2. Проверка сообщения: Метод checkMessage отправляет сообщение Worker'у и ожидает его проверки. Если сообщение содержит опасные конструкции, результат будет помечен как опасное.

7. CSP Bypass детектор

Content Security Policy (CSP) — это механизм, позволяющий предотвратить выполнение вредоносного кода в браузере. Однако уязвимости в настройке CSP могут позволить атакующим обойти защиту и внедрить вредоносный JavaScript.

Анализатор CSP байпасов

JavaScript: Скопировать в буфер обмена
Код:
function detectCSPBypass() {
const csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.content;
const vulnerabilities = [];

if (!csp) {
vulnerabilities.push('No CSP found');
return vulnerabilities;
}

// Проверка на unsafe-inline
if (csp.includes("'unsafe-inline'")) {
vulnerabilities.push('unsafe-inline detected');
}

// Проверка на unsafe-eval
if (csp.includes("'unsafe-eval'")) {
vulnerabilities.push('unsafe-eval detected');
}

// Проверка на data: URLs
if (csp.includes('data:')) {
vulnerabilities.push('data: URLs allowed');
}

return vulnerabilities;
}

Объяснение:
  1. Проверка на наличие CSP: Если CSP отсутствует, это указывает на значительный риск, так как нет ограничений для выполнения внешнего кода.
  2. Проверка на уязвимые директивы: Директивы 'unsafe-inline' и 'unsafe-eval' позволяют встроенный и небезопасный код. data: позволяет загружать данные из небезопасных источников, что также является угрозой.

8. Timing Attack детектор

Атаки по времени (Timing Attacks) позволяют атакующему получить доступ к конфиденциальным данным путем анализа времени обработки операций. Разные отклики на запросы могут показать, как сервер обрабатывает данные, и таким образом, раскрыть информацию о них.

Измеритель временных атак

JavaScript: Скопировать в буфер обмена
Код:
function createTimingDetector() {
const measurements = new Map();

return {
async measure(operation, iterations = 100) {
const times = [];

for (let i = 0; i < iterations; i++) {
const start = performance.now();
await operation();
const end = performance.now();
times.push(end - start);
}

const avg = times.reduce((a, b) => a + b) / times.length;
const variance = times.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / times.length;

measurements.set(operation, {
average: avg,
variance,
suspicious: variance > 1000 // Подозрительная вариация
});

return measurements.get(operation);
},

getResults() {
return Array.from(measurements.entries());
}
};
}

Объяснение кода:
  1. Измерение времени выполнения: Каждая операция выполняется несколько раз, чтобы собрать данные о времени выполнения.
  2. Вычисление среднего времени и дисперсии: Если дисперсия времени выполнения высока (например, выше 1000), это может быть индикатором уязвимости ко временным атакам.

9. CORS Misconfiguration детектор

Cross-Origin Resource Sharing (CORS) контролирует доступ веб-ресурсов из разных доменов. Неправильная настройка CORS может позволить атакующему обойти ограничения и получить доступ к данным.

Анализатор CORS конфигурации

JavaScript: Скопировать в буфер обмена
Код:
async function detectCORSMisconfiguration(url) {
const issues = [];

// Проверяем различные методы и заголовки
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
const headers = {
'Origin': 'evil.com',
'X-Requested-With': 'XMLHttpRequest',
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'X-Custom-Header'
};

for (const method of methods) {
try {
const response = await fetch(url, {
method,
mode: 'cors',
headers
});

const corsHeaders = {
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
'Access-Control-Allow-Credentials': response.headers.get('Access-Control-Allow-Credentials')
};

if (corsHeaders['Access-Control-Allow-Origin'] === '*') {
issues.push(`Wildcard CORS origin allowed for ${method}`);
}

if (corsHeaders['Access-Control-Allow-Credentials'] === 'true' &&
corsHeaders['Access-Control-Allow-Origin'] === '*') {
issues.push(`Credentials allowed with wildcard origin for ${method}`);
}
} catch (error) {
console.log(`Error testing ${method}: ${error.message}`);
}
}

return issues;
}

Объяснение кода:
  1. Тестирование различных методов и заголовков: Программа проверяет доступные методы и настраивает заголовки для проверки потенциальных уязвимостей.
  2. Проверка на небезопасные разрешения: Если Access-Control-Allow-Origin равен "*", это позволяет любым источникам получать доступ к данным, что небезопасно.

10. Практическое использование: Комбинированный сканер уязвимостей

Этот класс объединяет все вышеперечисленные детекторы в единый сканер для комплексного поиска уязвимостей.

JavaScript: Скопировать в буфер обмена
Код:
class VulnerabilityScanner {
constructor() {
this.memoryDetector = createMemoryLeakDetector();
this.eventLoopDetector = detectEventLoopBlocking();
this.workerDetector = createWorkerMessageDetector();
this.timingDetector = createTimingDetector();
}

async scanElement(element) {
const vulnerabilities = [];

// Проверка DOM XSS
if (detectShadowDOMXSS(element)) {
vulnerabilities.push('Shadow DOM XSS vulnerability detected');
}

// Проверка прототипов
if (detectPrototypePollution({})) {
vulnerabilities.push('Prototype pollution vulnerability detected');
}

// Проверка CSP
const cspIssues = detectCSPBypass();
if (cspIssues.length > 0) {
vulnerabilities.push(`CSP issues found: ${cspIssues.join(', ')}`);
}

// Проверка CORS
const corsIssues = await detectCORSMisconfiguration(window.location.href);
if (corsIssues.length > 0) {
vulnerabilities.push(`CORS issues found: ${corsIssues.join(', ')}`);
}

return vulnerabilities;
}

async fullScan() {
const results = await this.scanElement(document.documentElement);

// Очистка ресурсов
this.eventLoopDetector.stop();
this.workerDetector.terminate();

return results;
}
}

Объяснение кода:
  1. Объединение всех детекторов: Каждый компонент сканера проверяет на наличие конкретных уязвимостей, таких как XSS, уязвимости прототипов, CSP и CORS.
  2. Очистка ресурсов после выполнения: Освобождение ресурсов Worker'ов после сканирования улучшает производительность.

Продвинутые техники для поиска багов - Часть 2

1. Эксплуатация Service Worker

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

Код детектора уязвимостей в Service Worker

JavaScript: Скопировать в буфер обмена
Код:
class ServiceWorkerVulnDetector {
constructor() {
this.vulnerabilities = new Set();
}

async analyze() {
if (!navigator.serviceWorker) {
return ['Service Worker API not supported'];
}

const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
const workerScript = await fetch(registration.active.scriptURL).then(r => r.text());

// Проверка на наличие небезопасных паттернов
const patterns = {
'Небезопасная обработка fetch': /event\.respondWith\(fetch\(event\.request\)\)/,
'Отсутствие валидации источника': /self\.addEventListener\(['"]message/,
'Небезопасное кэширование': /cache\.put\(.*?event\.request/,
'Потенциальная утечка данных': /indexedDB\.open\(/
};

for (const [issue, pattern] of Object.entries(patterns)) {
if (pattern.test(workerScript)) {
this.vulnerabilities.add(issue);
}
}

if (registration.scope === '/') {
this.vulnerabilities.add('Слишком широкий scope Service Worker');
}
}

return Array.from(this.vulnerabilities);
}

async injectPayload(payload) {
const blob = new Blob([payload], { type: 'text/javascript' });
const url = URL.createObjectURL(blob);

try {
await navigator.serviceWorker.register(url, { scope: '/' });
this.vulnerabilities.add('Возможна регистрация произвольного Service Worker');
} catch (e) {
console.log('Service Worker protection in place');
}

URL.revokeObjectURL(url);
}
}

Объяснение:

  1. Проверка поддержки API:
    • Код начинает с проверки, поддерживает ли браузер API Service Worker. Если нет, возвращается сообщение об ошибке.
  2. Регистрация и анализ скрипта:
    • Мы получаем текущую регистрацию Service Worker и загружаем ее скрипт с помощью метода fetch. Этот шаг важен для обнаружения небезопасных паттернов.
    • Определяются шаблоны для уязвимостей, которые мы будем искать в коде.
  3. Проверка на небезопасные паттерны:
    • Используем регулярные выражения для поиска уязвимостей, таких как небезопасная обработка запросов и отсутствие валидации входящих сообщений.
  4. Проверка scope:
    • Если scope равен '/', это указывает на слишком широкий доступ Service Worker, что тоже может быть уязвимостью.
  5. Инъекция произвольного кода:
    • Метод injectPayload создает Blob из переданного кода и пытается зарегистрировать его как новый Service Worker. Если это удастся, это также сигнализирует о наличии уязвимости.

2. Эксплуатация WebRTC для обхода Same-Origin Policy

WebRTC (Web Real-Time Communication) — это технология, позволяющая обмениваться данными и проводить видеозвонки в реальном времени без необходимости в промежуточных серверах. Однако это также может создавать риски для безопасности, включая утечку IP-адресов.

Код детектора утечек WebRTC

JavaScript: Скопировать в буфер обмена
Код:
class WebRTCLeakDetector {
constructor() {
this.pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
this.leaks = new Set();
}

async detectLeaks() {
return new Promise((resolve) => {
this.pc.onicecandidate = (event) => {
if (event.candidate) {
const candidate = event.candidate.candidate;

// Анализ ICE кандидата на утечки
if (candidate.includes('host')) {
this.leaks.add('Local IP address exposed');
}

if (candidate.includes('relay')) {
this.leaks.add('TURN server misconfiguration');
}
}
};

this.pc.createDataChannel('detect');
this.pc.createOffer()
.then(offer => this.pc.setLocalDescription(offer))
.catch(err => this.leaks.add(`WebRTC setup error: ${err.message}`));

setTimeout(() => {
this.pc.close();
resolve(Array.from(this.leaks));
}, 1000);
});
}

async interceptSignaling(signalServer) {
const channel = this.pc.createDataChannel('intercept');

channel.onopen = () => {
channel.send(JSON.stringify({
type: 'probe',
content: document.cookie // Тест на утечку данных
}));
};

return new Promise((resolve) => {
channel.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'probe_response') {
this.leaks.add('Signaling channel data leak');
}
} catch (e) {
// Игнорируем некорректные данные
}
resolve(Array.from(this.leaks));
};
});
}
}

Объяснение кода:

  1. Создание RTCPeerConnection:
    • Конструктор создает новое соединение WebRTC с использованием STUN-сервера для получения информации о внешнем IP.
  2. Обработка ICE кандидатов:
    • В методе detectLeaks слушаем события onicecandidate для анализа кандидатов. Если в кандидате присутствует host, это может указывать на утечку локального IP-адреса.
  3. Создание канала данных:
    • Создаем DataChannel для тестирования. Это позволяет нам отправить данные и проверить, не происходит ли утечка информации через сигнальный канал.
  4. Тестирование сигнальных сообщений:
    • Отправляем запрос через созданный канал и проверяем ответ. Если получаем ответ, который указывает на утечку данных, добавляем это к нашему списку утечек.

3. Эксплуатация SharedArrayBuffer для Spectre

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

Код детектора уязвимостей Spectre

JavaScript: Скопировать в буфер обмена
Код:
class SpectreDetector {
constructor() {
if (!crossOriginIsolated) {
throw new Error('Requires Cross-Origin Isolation');
}
}

async createTimingOracle() {
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);

const worker = new Worker(`
onmessage = function(e) {
const buffer = e.data;
const array = new Int32Array(buffer);
while (Atomics.load(array, 0) === 0) {
// Спин-лок
}
postMessage(performance.now());
}
`);

return {
async measureTiming(operation) {
worker.postMessage(sharedBuffer);
const start = performance.now();
await operation();
Atomics.store(sharedArray, 0, 1);

return new Promise(resolve => {
worker.onmessage = (e) => {
const end = e.data;
resolve(end - start);
};
});
},

cleanup() {
worker.terminate();
}
};
}

async detectVulnerability() {
const oracle = await this.createTimingOracle();
const results = new Map();

for (let i = 0; i < 1000; i++) {
const array = new Uint8Array(1024);
const timing = await oracle.measureTiming(() => {
if (i < array.length) {
const value = array[i & (array.length - 1)];
if (value < 128) {
array[0] ^= 1; // Спекулятивное исполнение
}
}
});

results.set(i, timing);
}

oracle.cleanup();

const timings = Array.from(results.values());
const avg = timings.reduce((a, b) => a + b) / timings.length;
const variance = timings.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / timings.length;

return {
vulnerable: variance > 1000,
variance,
averageTiming: avg
};
}
}

Объяснение кода:

  1. Проверка кросс-оригинальной изоляции:
    • Конструктор проверяет, что среда имеет кросс-оригинальную изоляцию, необходимую для выполнения кода.
  2. Создание Oracle для измерения времени:
    • Метод createTimingOracle создает общий буфер и устанавливает рабочий процесс для отслеживания времени выполнения операций.
  3. Измерение времени выполнения:
    • Метод measureTiming запускает заданную операцию и измеряет время её выполнения с помощью performance.now(). Это важно для определения временных задержек.
  4. Обработка спекулятивного исполнения:
    • В методе detectVulnerability мы создаем 1000 тестов, чтобы измерить время выполнения различных операций. Это позволяет выявить уязвимости, связанные с временными задержками.
  5. Анализ результатов:
    • Собранные временные значения анализируются для определения наличия уязвимости на основе вычисленной дисперсии. Если дисперсия превышает порог, это может указывать на уязвимость Spectre.

4. Эксплуатация WebAssembly для обхода CSP

WASM Injection детектор

JavaScript: Скопировать в буфер обмена
Код:
class WasmExploitDetector {
constructor() {
this.vulnerabilities = new Set();
}

async analyzeWasmModule(wasmBytes) {
// Анализ бинарного содержимого WASM
const analyzer = {
memory: {
initial: new DataView(wasmBytes.buffer).getUint32(8, true),
maximum: new DataView(wasmBytes.buffer).getUint32(12, true)
},

hasUnrestrictedGrowth() {
return this.memory.maximum === 0xFFFFFFFF;
},

hasExportedMemory(module) {
return WebAssembly.Module.exports(module)
.some(exp => exp.kind === 'memory');
}
};

try {
const module = await WebAssembly.compile(wasmBytes);

// Проверка экспортированных функций
const exports = WebAssembly.Module.exports(module);
for (const exp of exports) {
if (exp.kind === 'function' && exp.name.includes('eval')) {
this.vulnerabilities.add('Potential WASM eval export');
}
}

// Проверка импортов
const imports = WebAssembly.Module.imports(module);
for (const imp of imports) {
if (imp.module === 'env' && imp.kind === 'function') {
this.vulnerabilities.add(`Suspicious import: ${imp.name}`);
}
}

// Проверка памяти
if (analyzer.hasUnrestrictedGrowth()) {
this.vulnerabilities.add('Unrestricted memory growth');
}

if (analyzer.hasExportedMemory(module)) {
this.vulnerabilities.add('Exported memory - potential info leak');
}

} catch (e) {
this.vulnerabilities.add(`WASM compilation error: ${e.message}`);
}

return Array.from(this.vulnerabilities);
}

async injectPayload(payload) {
const memory = new WebAssembly.Memory({ initial: 1 });
const table = new WebAssembly.Table({ initial: 1, element: 'anyfunc' });

try {
const instance = await WebAssembly.instantiate(payload, {
env: {
memory,
table,
abort: () => { throw new Error('abort'); }
}
});

// Проверка доступа к DOM через WASM
if (instance.exports.manipulateDOM) {
const result = instance.exports.manipulateDOM();
if (result !== 0) {
this.vulnerabilities.add('WASM can access DOM');
}
}

} catch (e) {
console.log('WASM isolation in place');
}
}
}

Объяснение кода:

  1. Анализ бинарного модуля WASM:
    • analyzeWasmModule принимает байты WASM и проверяет наличие уязвимостей, таких как экспорт функций eval, подозрительные импорты из окружения (env), неограниченный рост памяти и экспортированная память.
  2. Проверка на уязвимости:
    • Код анализирует экспортированные функции на наличие потенциально опасных функций, таких как eval.
    • Импортированные функции проверяются на подозрительное поведение.
  3. Инъекция полезной нагрузки:
    • injectPayload создает экземпляр WebAssembly и проверяет, может ли он получить доступ к DOM через экспортированные функции. Если manipulateDOM возвращает ненулевое значение, это может указывать на уязвимость.

5. Эксплуатация Web Audio API для Side-Channel атак

Audio Timing детектор

JavaScript: Скопировать в буфер обмена
Код:
class AudioTimingDetector {
constructor() {
this.audioContext = new AudioContext();
this.measurements = new Map();
}

async createPreciseTimer() {
const buffer = this.audioContext.createBuffer(1, 44100, 44100);
const source = this.audioContext.createBufferSource();
source.buffer = buffer;

// Создаем точный таймер на основе аудио
const startTime = this.audioContext.currentTime;
source.start(startTime);

return {
async measure(operation) {
const before = this.audioContext.currentTime - startTime;
await operation();
const after = this.audioContext.currentTime - startTime;
return (after - before) * 1000; // в миллисекундах
}
};
}

async detectTimingAttack(targetFunction, iterations = 100) {
const timer = await this.createPreciseTimer();
const timings = [];

for (let i = 0; i < iterations; i++) {
const time = await timer.measure(() => targetFunction());
timings.push(time);
}

// Статистический анализ
const avg = timings.reduce((a, b) => a + b) / timings.length;
const stdDev = Math.sqrt(
timings.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / timings.length
);

return {
vulnerable: stdDev > 0.1, // Порог для определения утечки по времени
averageTime: avg,
standardDeviation: stdDev,
samples: timings
};
}

async analyzeAudioProcessing(audioData) {
const analyser = this.audioContext.createAnalyser();
analyser.fftSize = 2048;

const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

// Создаем источник из данных
const arrayBuffer = await audioData.arrayBuffer();
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
const source = this.audioContext.createBufferSource();
source.buffer = audioBuffer;

source.connect(analyser);
analyser.connect(this.audioContext.destination);

return new Promise((resolve) => {
const vulnerabilities = new Set();

const checkSpectrum = () => {
analyser.getByteFrequencyData(dataArray);

// Анализ спектра на утечки информации
const hasAnomalies = dataArray.some((value, index) => {
const expectedRange = [0, 255];
return value < expectedRange[0] || value > expectedRange[1];
});

if (hasAnomalies) {
vulnerabilities.add('Abnormal frequency pattern detected');
}
};

source.onended = () => {
resolve(Array.from(vulnerabilities));
};

source.start(0);
const interval = setInterval(checkSpectrum, 100);

setTimeout(() => {
clearInterval(interval);
source.stop();
}, audioBuffer.duration * 1000);
});
}
}

Объяснение кода:

  1. Создание точного таймера:
    • Метод createPreciseTimer использует аудиоконтекст для создания временного измерителя. Таймер позволяет точно измерять время выполнения операций.
  2. Обнаружение тайминг-атак:
    • Метод detectTimingAttack измеряет время выполнения целевой функции в нескольких итерациях и анализирует данные для выявления утечек информации по времени.
  3. Анализ аудиопотока:
    • Метод analyzeAudioProcessing использует анализатор для отслеживания частотных данных и выявления аномалий, которые могут указывать на утечки информации.

6. Эксплуатация IndexedDB для атак на хранилище

Код класса

JavaScript: Скопировать в буфер обмена
Код:
class IndexedDBSecurityScanner {
constructor() {
this.vulnerabilities = new Set();
}

async scanDatabase(dbName) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);

request.onerror = () => {
this.vulnerabilities.add(`Cannot access database: ${request.error}`);
resolve(Array.from(this.vulnerabilities));
};

request.onsuccess = async (event) => {
const db = event.target.result;

// Проверка версии
if (db.version < 2) {
this.vulnerabilities.add('Outdated database version');
}

// Анализ хранилищ объектов
const stores = Array.from(db.objectStoreNames);
for (const storeName of stores) {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);

// Проверка индексов на уязвимости
const indexes = Array.from(store.indexNames);
for (const indexName of indexes) {
const index = store.index(indexName);
if (!index.unique) {
this.vulnerabilities.add(
`Non-unique index ${indexName} could lead to data collision`
);
}
}

// Сканирование данных
const request = store.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const value = cursor.value;
if (this.containsSensitiveData(value)) {
this.vulnerabilities.add(
`Sensitive data found in ${storeName}`
);
}
cursor.continue();
}
};
}

// Проверка на quota превышение
try {
const quota = await navigator.storage.estimate();
if (quota.usage / quota.quota > 0.9) {
this.vulnerabilities.add('Storage quota near limit');
}
} catch (e) {
this.vulnerabilities.add('Cannot check storage quota');
}

resolve(Array.from(this.vulnerabilities));
};
});
}

containsSensitiveData(data) {
const sensitivePatterns = [
/password/i,
/token/i,
/credit.?card/i,
/ssn/i,
/social.?security/i,
/\b[\w-]+@[\w-]+\.\w{2,4}\b/, // email
/\b\d{16}\b/, // credit card
/\b\d{3}-\d{2}-\d{4}\b/ // SSN pattern
];

const dataString = JSON.stringify(data).toLowerCase();
return sensitivePatterns.some(pattern => pattern.test(dataString));
}

async injectMaliciousData(dbName, storeName) {
const payload = {
__proto__: {
toString: function() {
return '';
}
}
};

return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);

request.onsuccess = (event) => {
const db = event.target.result;
try {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);

store.add(payload).onsuccess = () => {
this.vulnerabilities.add('Successfully injected malicious payload');
resolve(true);
};
} catch (e) {
resolve(false);
}
};
});
}
}

Разбор

  1. Конструктор: Инициализация Set, который будет хранить все найденные уязвимости.
  2. Метод scanDatabase(dbName):
    • Открывает базу данных с помощью indexedDB.open().
    • Обрабатывает успешные и неудачные запросы на открытие базы данных. В случае ошибки добавляет сообщение об ошибке в Set.
    • Проверяет версию базы данных на предмет устаревания.
    • Анализирует все объектные хранилища, проверяя индексы на уникальность и наличие неуникальных индексов.
  3. Сканирование данных: Используется курсор для перебора всех записей в хранилище и проверки на наличие чувствительных данных с помощью метода containsSensitiveData.
  4. Проверка на превышение квоты: Определяет, не превышает ли использование хранилища 90% от установленного лимита.
  5. Метод containsSensitiveData(data): Использует регулярные выражения для выявления чувствительных данных, таких как пароли, номера кредитных карт и адреса электронной почты.
  6. Метод injectMaliciousData(dbName, storeName):
    • Демонстрирует, как можно инъектировать вредоносные данные в IndexedDB. Использование __proto__ указывает на потенциальные уязвимости, связанные с загрязнением прототипа.

7. Анализ безопасности криптографии

Код класса


JavaScript: Скопировать в буфер обмена
Код:
class CryptoSecurityAnalyzer {
constructor() {
this.issues = new Set();
}

async analyzeCryptoOperations() {
if (!window.crypto || !window.crypto.subtle) {
return ['Web Crypto API not available'];
}

// Проверка генерации случайных чисел
const randomTest = await this.testRandomGeneration();
if (!randomTest.secure) {
this.issues.add(`Weak random generation: ${randomTest.reason}`);
}

// Проверка реализации криптографических операций
await this.testCryptoOperations();

// Проверка утечек ключей
await this.testKeyLeaks();

return Array.from(this.issues);
}

async testRandomGeneration() {
const samples = new Uint32Array(1000);
crypto.getRandomValues(samples);

// Статистический тест
const counts = new Map();
for (const value of samples) {
counts.set(value, (counts.get(value) || 0) + 1);
}

const duplicates = Array.from(counts.values()).filter(count => count > 1);

return {
secure: duplicates.length < samples.length * 0.01,
reason: duplicates.length > 0 ?
`Found ${duplicates.length} duplicate values` : 'OK'
};
}

async testCryptoOperations() {
try {
// Тест AES-GCM
const key = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256
},
true,
['encrypt', 'decrypt']
);

const data = new TextEncoder().encode('test data');
const iv = crypto.getRandomValues(new Uint8Array(12));

const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv
},
key,
data
);

// Проверка на повторное использование IV
const secondEncryption = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv // Намеренное повторное использование
},
key,
data
);

if (encrypted.byteLength === secondEncryption.byteLength) {
this.issues.add('Possible IV reuse vulnerability');
}

} catch (e) {
this.issues.add(`Crypto operation failed: ${e.message}`);
}
}

async testKeyLeaks() {
try {
// Создаем ключ
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256'
},
true,
['sign', 'verify']
);

// Экспортируем приватный ключ
const exportedPrivate = await crypto.subtle.exportKey(
'jwk',
keyPair.privateKey
);

// Проверяем доступность приватного ключа
if (exportedPrivate.d) {
this.issues.add('Private key can be exported - potential security risk');
}

} catch (e) {
// Ошибка экспорта - это хорошо
console.log('Key export properly restricted');
}
}
}

Разбор

  1. Конструктор: Инициализирует Set для хранения всех найденных проблем в криптографических операциях.
  2. Метод analyzeCryptoOperations():
    • Проверяет доступность Web Crypto API. Если API недоступен, возвращает соответствующее сообщение.
    • Запускает тесты для генерации случайных чисел, криптографических операций и проверки утечек ключей.
  3. Метод testRandomGeneration():
    • Генерирует 1000 случайных чисел с помощью crypto.getRandomValues().
    • Выполняет статистический тест для проверки на дубликаты. Если количество дубликатов превышает 1% от общего числа выборок, помечает генерацию как слабую.
  4. Метод testCryptoOperations():
    • Проверяет реализацию AES-GCM, генерируя ключ и шифруя тестовые данные.
    • Оценивает возможность повторного использования вектора инициализации (IV) путем повторного шифрования с тем же IV. Если длины зашифрованных данных совпадают, это указывает на возможную уязвимость.
  5. Метод testKeyLeaks():
    • Генерирует пару ключей с использованием ECDSA и проверяет, может ли приватный ключ быть экспортирован. Если ключ может быть экспортирован, это указывает на потенциальный риск.

8. Интеграция всех сканеров

Код класса


JavaScript: Скопировать в буфер обмена
Код:
class UnifiedSecurityScanner {
constructor() {
this.scanners = {
serviceWorker: new ServiceWorkerVulnDetector(),
webRTC: new WebRTCLeakDetector(),
spectre: new SpectreDetector(),
wasm: new WasmExploitDetector(),
audio: new AudioTimingDetector(),
indexedDB: new IndexedDBSecurityScanner(),
crypto: new CryptoSecurityAnalyzer()
};
}

async performFullScan() {
const results = new Map();
const scanPromises = [];

// Запускаем все сканеры параллельно
for (const [name, scanner] of Object.entries(this.scanners)) {
const scanPromise = (async () => {
try {
let result;
switch (name) {
case 'serviceWorker':
result = await scanner.analyze();
break;
case 'webRTC':
result = await scanner.detectLeaks();
break;
case 'spectre':
result = await scanner.detectVulnerability();
break;
case 'indexedDB':
result = await scanner.scanDatabase('testDB');
break;
case 'crypto':
result = await scanner.analyzeCryptoOperations();
break;
default:
result = await scanner.analyze();
}
return [name, result];
} catch (e) {
return [name, [`Error in ${name} scanner: ${e.message}`]];
}
})();
scanPromises.push(scanPromise);
}

const results = await Promise.all(scanPromises);
return this.generateReport(Object.fromEntries(results));
}

generateReport(results) {
const vulnerabilities = {
critical: [],
high: [],
medium: [],
low: []
};

// Классификация уязвимостей
for (const [scanner, findings] of Object.entries(results)) {
if (Array.isArray(findings)) {
findings.forEach(finding => {
if (finding.includes('critical') || finding.includes('injection')) {
vulnerabilities.critical.push({ scanner, finding });
} else if (finding.includes('high') || finding.includes('leak')) {
vulnerabilities.high.push({ scanner, finding });
} else if (finding.includes('medium')) {
vulnerabilities.medium.push({ scanner, finding });
} else {
vulnerabilities.low.push({ scanner, finding });
}
});
}
}

return {
summary: {
criticalCount: vulnerabilities.critical.length,
highCount: vulnerabilities.high.length,
mediumCount: vulnerabilities.medium.length,
lowCount: vulnerabilities.low.length
},
vulnerabilities,
rawResults: results,
timestamp: new Date().toISOString()
};
}
}

Разбор

  1. Конструктор: Инициализирует все сканеры безопасности, включая ранее описанный CryptoSecurityAnalyzer и другие.
  2. Метод performFullScan():
    • Запускает все сканеры параллельно с использованием массива scanPromises.
    • Каждый сканер выполняет свои анализы, и результаты собираются в Map.
    • Обрабатывает ошибки, если они возникают в процессе анализа.
  3. Метод generateReport(results):
    • Классифицирует уязвимости на основе найденных результатов. Использует разные уровни критичности (критические, высокие, средние и низкие) для создания структуры отчетности.
    • Возвращает сводку с количеством уязвимостей по категориям, а также временную метку для отслеживания.

Использование всех инструментов

Код


JavaScript: Скопировать в буфер обмена
Код:
async function performSecurityAudit() {
const scanner = new UnifiedSecurityScanner();
const results = await scanner.performFullScan();

console.log('Security Audit Results:');
console.log('=======================');
console.log(`Critical Vulnerabilities: ${results.summary.criticalCount}`);
console.log(`High Vulnerabilities: ${results.summary.highCount}`);
console.log(`Medium Vulnerabilities: ${results.summary.mediumCount}`);
console.log(`Low Vulnerabilities: ${results.summary.lowCount}`);

if (results.summary.criticalCount > 0) {
console.log('\nCritical Vulnerabilities:');
results.vulnerabilities.critical.forEach(v => {
console.log(`[${v.scanner}] ${v.finding}`);
});
}

return results;
}

Разбор

  1. Функция performSecurityAudit():
    • Создает экземпляр UnifiedSecurityScanner.
    • Вызывает полный анализ всех сканеров и выводит результаты в консоль, классифицируя их по критичности.
Заключение

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