\n# Результат: JavaScript выполнится в браузере!\n```\n\n#### Типы XSS\n\n**1. Reflected XSS (отражённая):**\n```\nАттакующий → Вредоносный URL с JS кодом\n ↓\n Веб-сервер (уязвимый)\n ↓\nПользователь ← Ответ содержит JS\n ↓\n JS выполняется в браузере\n (кража куки, токенов, данных)\n```\n\n**Пример:**\n```html\n\n\n

Ошибка: Некорректная сумма:

\n```\n\n**2. Stored XSS (сохранённая, самая опасная):**\n```\nАттакующий → Вредоносный комментарий с JS\n ↓\n Веб-сервер (сохраняет в БД!)\n ↓\n Любой пользователь → JS выполняется\n```\n\n**Пример:**\n```python\n@app.route('/post/new', methods=['POST'])\ndef create_post():\n post_text = request.form.get('text')\n \n # УЯЗВИМО: сохраняем пользовательский ввод БЕЗ санитизации\n db.posts.insert_one({\n 'author': current_user,\n 'content': post_text # Может быть: \n })\n \n return redirect('/posts')\n\n@app.route('/posts')\ndef list_posts():\n posts = db.posts.find()\n # Выводим посты прямо в HTML (опасно!)\n return render_template('posts.html', posts=posts)\n```\n\n**3. DOM-based XSS:**\n```javascript\n// JavaScript на клиенте (уязвимо)\nconst comment = new URLSearchParams(window.location.search).get('comment');\ndocument.getElementById('comments').innerHTML = comment; // ОПАСНО!\n\n// URL: ?comment=\n// innerHTML позволяет выполнить JS!\n```\n\n### 2. CSRF (Cross-Site Request Forgery) атака\n\n**Определение:** Выполнение несанкционированных действий от имени пользователя на другом сайте.\n\n**Цель:** Перевод денег, смена пароля, удаление данных, без ведома пользователя.\n\n#### Как работает CSRF:\n\n```\n1. Пользователь логинится на bank.com\n → Сервер выдаёт куки с SESSION_ID\n → Куки хранятся в браузере\n\n2. Пользователь переходит на evil.com (вредоносный сайт)\n\n3. evil.com содержит:\n \n\n4. Браузер АВТОМАТИЧЕСКИ отправляет запрос\n (куки прилагаются!)\n → Банк думает, что запрос от авторизованного пользователя\n → Деньги переводятся!\n```\n\n**Визуально:**\n```\n┌─────────────┐\n│ Пользователь │\n└──────┬──────┘\n │\n ┌───▼─────────────┐\n │ Браузер │\n │ SESSION_ID=abc │\n └───┬─────────────┘\n │\n ┌───┴──────┐\n │ │\n┌──▼────────┐ │ ┌──────────────┐\n│ bank.com │ │ │ evil.com │\n│ (доверять) │ │ │ (вредонос) │\n└───────────┘ │ └──────────────┘\n │\n Браузер отправляет\n куки в ОБОИХ запросах!\n```\n\n### 3. Защита от XSS\n\n#### Способ 1: Санитизация (escaping) вывода\n\n```python\nfrom flask import Flask, escape\nfrom markupsafe import escape as markupsafe_escape\n\n@app.route('/comment')\ndef show_comment():\n user_comment = request.args.get('comment', '')\n \n # Экранируем специальные символы\n safe_comment = escape(user_comment)\n # ✗\n# После:

Комментарий: <script>alert('XSS')</script>

✓\n```\n\n**В Jinja2 это делается автоматически:**\n\n```html\n\n

Комментарий: {{ user_comment }}

\n```\n\n#### Способ 2: Content Security Policy (CSP)\n\n```python\nfrom flask import Flask\n\n@app.route('/')\ndef index():\n response = make_response(render_template('index.html'))\n \n # Запрещаем inline скрипты, разрешаем только с конкретных источников\n response.headers['Content-Security-Policy'] = (\n \"default-src 'self'; \"\n \"script-src 'self' https://cdn.example.com; \"\n \"style-src 'self' 'unsafe-inline'; \"\n \"img-src 'self' data: https:;\"\n )\n return response\n\n# Результат:\n# - → блокируется браузером\n# - → разрешается\n```\n\n#### Способ 3: Использование безопасных библиотек\n\n```python\nfrom bleach import clean\nimport html\n\ndef sanitize_html(user_input: str) -> str:\n \"\"\"Удаляем вредоносный HTML, оставляя безопасные теги.\"\"\"\n allowed_tags = ['p', 'br', 'strong', 'em', 'a']\n allowed_attributes = {'a': ['href']}\n \n return clean(\n user_input,\n tags=allowed_tags,\n attributes=allowed_attributes,\n strip=True # Удаляем недопустимые теги вместо экранирования\n )\n\nuser_comment = \"

Привет!

\"\nprint(sanitize_html(user_comment))\n# Вывод:

Привет!

\n```\n\n#### Способ 4: HttpOnly куки\n\n```python\nfrom flask import Flask, session\nfrom flask_session import Session\n\napp = Flask(__name__)\napp.config['SESSION_COOKIE_HTTPONLY'] = True # Куки недоступны для JavaScript\napp.config['SESSION_COOKIE_SECURE'] = True # Только HTTPS\napp.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF защита\n\nSession(app)\n\n# Теперь JavaScript не может получить доступ к куки:\n# document.cookie в консоли будет пуста\n```\n\n### 4. Защита от CSRF\n\n#### Способ 1: CSRF токены\n\n```python\nfrom flask import Flask, render_template, session\nfrom wtforms import Form, StringField, SubmitField\nfrom wtforms.csrf.core import CSRF_FIELD_NAME\nfrom flask_wtf.csrf import generate_csrf, validate_csrf\n\napp = Flask(__name__)\napp.secret_key = 'super-secret-key'\n\n@app.route('/transfer', methods=['GET', 'POST'])\ndef transfer():\n if request.method == 'POST':\n # Валидируем CSRF токен\n try:\n validate_csrf(request.form.get('csrf_token'))\n except:\n return \"CSRF validation failed\", 403\n \n # Выполняем перевод\n amount = request.form.get('amount')\n to_account = request.form.get('to_account')\n \n # Логика перевода\n return \"Transfer successful\"\n \n # Генерируем токен для формы\n csrf_token = generate_csrf()\n return render_template('transfer.html', csrf_token=csrf_token)\n```\n\n```html\n\n
\n \n \n \n \n \n \n
\n```\n\n**Почему это работает:**\n```\n1. Пользователь посещает bank.com\n → Сервер генерирует уникальный токен (CSRF_TOKEN_abc123)\n → Токен вставляется в форму HTML\n → Браузер хранит его в памяти страницы\n\n2. Зловред на evil.com пытается\n
\n \n \n
\n\n3. Сервер получает запрос БЕЗ корректного токена\n → Запрос отклоняется\n```\n\n#### Способ 2: SameSite атрибут куки\n\n```python\nfrom flask import Flask, make_response\n\n@app.after_request\ndef set_secure_cookies(response):\n \"\"\"Устанавливаем безопасные флаги для кук.\"\"\"\n response.set_cookie(\n 'session_id',\n value='abc123',\n httponly=True, # JS не может получить\n secure=True, # Только HTTPS\n samesite='Strict' # Только requests с одного сайта\n # Strict = никогда не отправляем cross-site\n # Lax = отправляем при top-level navigation\n )\n return response\n\n# Результат:\n# SameSite=Strict:\n# ✓ bank.com → bank.com/transfer (работает)\n# ✗ evil.com → bank.com/transfer (куки НЕ отправляются)\n```\n\n#### Способ 3: Двойная проверка (Double Submit Cookies)\n\n```python\nimport secrets\n\n@app.route('/api/transfer', methods=['POST'])\ndef api_transfer():\n # Получаем токен из куки и заголовка\n csrf_token_cookie = request.cookies.get('X-CSRF-Token')\n csrf_token_header = request.headers.get('X-CSRF-Token')\n \n # CSRF атакующий не может прочитать куку (HttpOnly)\n # и не может установить заголовок (Same-Origin Policy)\n if csrf_token_cookie != csrf_token_header:\n return \"CSRF validation failed\", 403\n \n # Выполняем операцию\n return {\"status\": \"success\"}\n```\n\n### 5. Сравнение XSS и CSRF\n\n| Параметр | XSS | CSRF |\n|----------|-----|------|\n| Тип | Внедрение кода | Подделка запроса |\n| Выполнение | На сервере → браузер | Браузер → сервер |\n| Куки отправляются | Да | Да |\n| Требует взаимодействия | Нет (автоматично) | Нет (автоматично) |\n| Защита | Санитизация, CSP | CSRF токены, SameSite |\n| OWASP ранг | #1 (Injection) | #4 |\n\n### 6. Полный пример защиты (FastAPI + Pydantic)\n\n```python\nfrom fastapi import FastAPI, Form, HTTPException, Depends\nfrom fastapi.responses import HTMLResponse\nfrom starlette.middleware.csrf import CSRFMiddleware\nfrom pydantic import BaseModel, validator\nimport bleach\n\napp = FastAPI()\napp.add_middleware(CSRFMiddleware, secret_key=\"your-secret-key\")\n\nclass CommentCreate(BaseModel):\n text: str\n \n @validator('text')\n def sanitize_text(cls, v):\n # Убираем опасный HTML\n allowed_tags = ['p', 'br', 'strong', 'em']\n return bleach.clean(v, tags=allowed_tags, strip=True)\n\n@app.post(\"/comments\")\nasync def create_comment(comment: CommentCreate):\n # Текст уже санитизирован через validator\n # CSRF токен автоматически проверяется middleware\n \n # Сохраняем в БД\n # ...\n \n return {\"status\": \"success\", \"text\": comment.text}\n\n@app.get(\"/comments\")\nasync def list_comments():\n # Возвращаем комментарии\n # Браузер отобразит их как текст (не как JS)\n comments = db.comments.find()\n return {\"comments\": list(comments)}\n```\n\n### 7. Чеклист безопасности\n\n```python\n# ✓ Всегда проверяйте пользовательский ввод\nfrom pydantic import BaseModel, validator\n\nclass UserInput(BaseModel):\n name: str\n \n @validator('name')\n def validate_name(cls, v):\n if len(v) > 100:\n raise ValueError('Name too long')\n return v.strip()\n\n# ✓ Используйте Jinja2 с автоматическим экранированием\n# {{ comment }} # Автоматически экранируется\n\n# ✓ Устанавливайте безопасные флаги куки\nresponse.set_cookie('session', value, httponly=True, secure=True, samesite='Strict')\n\n# ✓ Используйте CSRF токены\n
\n \n
\n\n# ✓ Установите Content-Security-Policy\nresponse.headers['Content-Security-Policy'] = \"default-src 'self'\"\n\n# ✓ Используйте HTTPS везде\n# ✓ Регулярно обновляйте зависимости\n# ✓ Используйте библиотеки для санитизации (bleach, markupsafe)\n```\n\n### Вывод\n\n**XSS** и **CSRF** — две основные уязвимости современного веба. Защита требует комплексного подхода:\n- XSS: санитизация входа, экранирование вывода, CSP\n- CSRF: CSRF токены, SameSite куки, двойная проверка\n\nВсегда предполагайте худшее и применяйте defence in depth (множество слоёв защиты).","dateCreated":"2026-03-23T09:28:51.315511","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Что такое CSRF и XSS атаки?

2.0 Middle🔥 131 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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> становится &lt;script&gt;
    
    return f"<h1>Комментарий: {safe_comment}</h1>"

# До: <h1>Комментарий: <script>alert('XSS')</script></h1> ✗
# После: <h1>Комментарий: &lt;script&gt;alert('XSS')&lt;/script&gt;</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

ПараметрXSSCSRF
ТипВнедрение кодаПодделка запроса
ВыполнениеНа сервере → браузерБраузер → сервер
Куки отправляютсяДаДа
Требует взаимодействияНет (автоматично)Нет (автоматично)
ЗащитаСанитизация, CSPCSRF токены, 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 (множество слоёв защиты).