Что такое CSRF и XSS атаки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
CSRF и XSS атаки: безопасность веб-приложений
Это две основные уязвимости на веб-приложения, включённые в OWASP Top 10. Давайте разберёмся в механизмах, различиях и защите.
1. XSS (Cross-Site Scripting) атака
Определение: Внедрение вредоносного JavaScript кода в страницу для выполнения в браузере пользователя.
Цель: Кража куки, токенов, данных, перенаправление на фишинговый сайт.
Пример уязвимого кода (Flask):
from flask import Flask, request
app = Flask(__name__)
@app.route('/comment', methods=['GET'])
def show_comment():
# УЯЗВИМО: прямой вывод пользовательского ввода!
user_comment = request.args.get('comment', '')
return f"<h1>Комментарий: {user_comment}</h1>"
# URL: /comment?comment=<script>alert('XSS')</script>
# Результат: JavaScript выполнится в браузере!
Типы XSS
1. Reflected XSS (отражённая):
Аттакующий → Вредоносный URL с JS кодом
↓
Веб-сервер (уязвимый)
↓
Пользователь ← Ответ содержит JS
↓
JS выполняется в браузере
(кража куки, токенов, данных)
Пример:
<!-- URL: https://bank.com/transfer?amount=<script>steal_money()</script> -->
<!-- Сервер выводит: -->
<p>Ошибка: Некорректная сумма: <script>steal_money()</script></p>
2. Stored XSS (сохранённая, самая опасная):
Аттакующий → Вредоносный комментарий с JS
↓
Веб-сервер (сохраняет в БД!)
↓
Любой пользователь → JS выполняется
Пример:
@app.route('/post/new', methods=['POST'])
def create_post():
post_text = request.form.get('text')
# УЯЗВИМО: сохраняем пользовательский ввод БЕЗ санитизации
db.posts.insert_one({
'author': current_user,
'content': post_text # Может быть: <img src=x onerror="steal()">
})
return redirect('/posts')
@app.route('/posts')
def list_posts():
posts = db.posts.find()
# Выводим посты прямо в HTML (опасно!)
return render_template('posts.html', posts=posts)
3. DOM-based XSS:
// JavaScript на клиенте (уязвимо)
const comment = new URLSearchParams(window.location.search).get('comment');
document.getElementById('comments').innerHTML = comment; // ОПАСНО!
// URL: ?comment=<script>alert('XSS')</script>
// innerHTML позволяет выполнить JS!
2. CSRF (Cross-Site Request Forgery) атака
Определение: Выполнение несанкционированных действий от имени пользователя на другом сайте.
Цель: Перевод денег, смена пароля, удаление данных, без ведома пользователя.
Как работает CSRF:
1. Пользователь логинится на bank.com
→ Сервер выдаёт куки с SESSION_ID
→ Куки хранятся в браузере
2. Пользователь переходит на evil.com (вредоносный сайт)
3. evil.com содержит:
<img src="https://bank.com/transfer?to=attacker&amount=1000">
4. Браузер АВТОМАТИЧЕСКИ отправляет запрос
(куки прилагаются!)
→ Банк думает, что запрос от авторизованного пользователя
→ Деньги переводятся!
Визуально:
┌─────────────┐
│ Пользователь │
└──────┬──────┘
│
┌───▼─────────────┐
│ Браузер │
│ SESSION_ID=abc │
└───┬─────────────┘
│
┌───┴──────┐
│ │
┌──▼────────┐ │ ┌──────────────┐
│ bank.com │ │ │ evil.com │
│ (доверять) │ │ │ (вредонос) │
└───────────┘ │ └──────────────┘
│
Браузер отправляет
куки в ОБОИХ запросах!
3. Защита от XSS
Способ 1: Санитизация (escaping) вывода
from flask import Flask, escape
from markupsafe import escape as markupsafe_escape
@app.route('/comment')
def show_comment():
user_comment = request.args.get('comment', '')
# Экранируем специальные символы
safe_comment = escape(user_comment)
# <script> становится <script>
return f"<h1>Комментарий: {safe_comment}</h1>"
# До: <h1>Комментарий: <script>alert('XSS')</script></h1> ✗
# После: <h1>Комментарий: <script>alert('XSS')</script></h1> ✓
В Jinja2 это делается автоматически:
<!-- template.html -->
<h1>Комментарий: {{ user_comment }}</h1> <!-- Автоматически экранируется -->
Способ 2: Content Security Policy (CSP)
from flask import Flask
@app.route('/')
def index():
response = make_response(render_template('index.html'))
# Запрещаем inline скрипты, разрешаем только с конкретных источников
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' https://cdn.example.com; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:;"
)
return response
# Результат:
# - <script>alert('XSS')</script> → блокируется браузером
# - <script src="https://trusted.com/lib.js"></script> → разрешается
Способ 3: Использование безопасных библиотек
from bleach import clean
import html
def sanitize_html(user_input: str) -> str:
"""Удаляем вредоносный HTML, оставляя безопасные теги."""
allowed_tags = ['p', 'br', 'strong', 'em', 'a']
allowed_attributes = {'a': ['href']}
return clean(
user_input,
tags=allowed_tags,
attributes=allowed_attributes,
strip=True # Удаляем недопустимые теги вместо экранирования
)
user_comment = "<p>Привет!</p><script>alert('XSS')</script>"
print(sanitize_html(user_comment))
# Вывод: <p>Привет!</p>
Способ 4: HttpOnly куки
from flask import Flask, session
from flask_session import Session
app = Flask(__name__)
app.config['SESSION_COOKIE_HTTPONLY'] = True # Куки недоступны для JavaScript
app.config['SESSION_COOKIE_SECURE'] = True # Только HTTPS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF защита
Session(app)
# Теперь JavaScript не может получить доступ к куки:
# document.cookie в консоли будет пуста
4. Защита от CSRF
Способ 1: CSRF токены
from flask import Flask, render_template, session
from wtforms import Form, StringField, SubmitField
from wtforms.csrf.core import CSRF_FIELD_NAME
from flask_wtf.csrf import generate_csrf, validate_csrf
app = Flask(__name__)
app.secret_key = 'super-secret-key'
@app.route('/transfer', methods=['GET', 'POST'])
def transfer():
if request.method == 'POST':
# Валидируем CSRF токен
try:
validate_csrf(request.form.get('csrf_token'))
except:
return "CSRF validation failed", 403
# Выполняем перевод
amount = request.form.get('amount')
to_account = request.form.get('to_account')
# Логика перевода
return "Transfer successful"
# Генерируем токен для формы
csrf_token = generate_csrf()
return render_template('transfer.html', csrf_token=csrf_token)
<!-- transfer.html -->
<form method="POST" action="/transfer">
<!-- Токен ДОЛЖЕН быть в каждой форме! -->
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="text" name="to_account" placeholder="Счёт получателя">
<input type="number" name="amount" placeholder="Сумма">
<button type="submit">Перевести</button>
</form>
Почему это работает:
1. Пользователь посещает bank.com
→ Сервер генерирует уникальный токен (CSRF_TOKEN_abc123)
→ Токен вставляется в форму HTML
→ Браузер хранит его в памяти страницы
2. Зловред на evil.com пытается
<form action="https://bank.com/transfer">
<!-- Зловред НЕ ЗНАЕТ токена! -->
<input name="to_account" value="attacker">
</form>
3. Сервер получает запрос БЕЗ корректного токена
→ Запрос отклоняется
Способ 2: SameSite атрибут куки
from flask import Flask, make_response
@app.after_request
def set_secure_cookies(response):
"""Устанавливаем безопасные флаги для кук."""
response.set_cookie(
'session_id',
value='abc123',
httponly=True, # JS не может получить
secure=True, # Только HTTPS
samesite='Strict' # Только requests с одного сайта
# Strict = никогда не отправляем cross-site
# Lax = отправляем при top-level navigation
)
return response
# Результат:
# SameSite=Strict:
# ✓ bank.com → bank.com/transfer (работает)
# ✗ evil.com → bank.com/transfer (куки НЕ отправляются)
Способ 3: Двойная проверка (Double Submit Cookies)
import secrets
@app.route('/api/transfer', methods=['POST'])
def api_transfer():
# Получаем токен из куки и заголовка
csrf_token_cookie = request.cookies.get('X-CSRF-Token')
csrf_token_header = request.headers.get('X-CSRF-Token')
# CSRF атакующий не может прочитать куку (HttpOnly)
# и не может установить заголовок (Same-Origin Policy)
if csrf_token_cookie != csrf_token_header:
return "CSRF validation failed", 403
# Выполняем операцию
return {"status": "success"}
5. Сравнение XSS и CSRF
| Параметр | XSS | CSRF |
|---|---|---|
| Тип | Внедрение кода | Подделка запроса |
| Выполнение | На сервере → браузер | Браузер → сервер |
| Куки отправляются | Да | Да |
| Требует взаимодействия | Нет (автоматично) | Нет (автоматично) |
| Защита | Санитизация, CSP | CSRF токены, SameSite |
| OWASP ранг | #1 (Injection) | #4 |
6. Полный пример защиты (FastAPI + Pydantic)
from fastapi import FastAPI, Form, HTTPException, Depends
from fastapi.responses import HTMLResponse
from starlette.middleware.csrf import CSRFMiddleware
from pydantic import BaseModel, validator
import bleach
app = FastAPI()
app.add_middleware(CSRFMiddleware, secret_key="your-secret-key")
class CommentCreate(BaseModel):
text: str
@validator('text')
def sanitize_text(cls, v):
# Убираем опасный HTML
allowed_tags = ['p', 'br', 'strong', 'em']
return bleach.clean(v, tags=allowed_tags, strip=True)
@app.post("/comments")
async def create_comment(comment: CommentCreate):
# Текст уже санитизирован через validator
# CSRF токен автоматически проверяется middleware
# Сохраняем в БД
# ...
return {"status": "success", "text": comment.text}
@app.get("/comments")
async def list_comments():
# Возвращаем комментарии
# Браузер отобразит их как текст (не как JS)
comments = db.comments.find()
return {"comments": list(comments)}
7. Чеклист безопасности
# ✓ Всегда проверяйте пользовательский ввод
from pydantic import BaseModel, validator
class UserInput(BaseModel):
name: str
@validator('name')
def validate_name(cls, v):
if len(v) > 100:
raise ValueError('Name too long')
return v.strip()
# ✓ Используйте Jinja2 с автоматическим экранированием
# {{ comment }} # Автоматически экранируется
# ✓ Устанавливайте безопасные флаги куки
response.set_cookie('session', value, httponly=True, secure=True, samesite='Strict')
# ✓ Используйте CSRF токены
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
</form>
# ✓ Установите Content-Security-Policy
response.headers['Content-Security-Policy'] = "default-src 'self'"
# ✓ Используйте HTTPS везде
# ✓ Регулярно обновляйте зависимости
# ✓ Используйте библиотеки для санитизации (bleach, markupsafe)
Вывод
XSS и CSRF — две основные уязвимости современного веба. Защита требует комплексного подхода:
- XSS: санитизация входа, экранирование вывода, CSP
- CSRF: CSRF токены, SameSite куки, двойная проверка
Всегда предполагайте худшее и применяйте defence in depth (множество слоёв защиты).