При разработке веб-приложений очень опасно отображать данные, введенные пользователем, на HTML-странице без каких-либо изменений. Это открывает двери для XSS (межсайтовых сценариев). Если злоумышленник подаст данные с тегом <script>, и эти данные будут отображены в браузере другого пользователя, это может привести к краже сессионных cookies или выполнению вредоносного кода.

Django предоставляет мощную коллекцию инструментов под названием django.utils.html, которая изначально блокирует такие угрозы безопасности и безопасно обрабатывает HTML. 🛡️


1. Основной метод защиты от XSS: escape()



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

  • < становится &lt;
  • > становится &gt;
  • ' (одинарная кавычка) становится &#39;
  • " (двойная кавычка) становится &quot;
  • & становится &amp;

Пример:

from django.utils.html import escape

# Подаваемые злонамеренные данные
malicious_input = "<script>alert('XSS Attack!');</script>"

# Обработка escape
safe_output = escape(malicious_input)

print(safe_output)
# Результат:
# &lt;script&gt;alert(&#39;XSS Attack!&#39;);&lt;/script&gt;

Таким образом преобразованная строка не будет выполнена как скрипт в браузере, а будет показана на экране как текст <script>alert('XSS Attack!');</script>.

[Важно] Автоэкранирование (Autoescaping) в шаблонах Django

К счастью, движок шаблонов Django по умолчанию автоматически обрабатывает все переменные с помощью escape.

{{ user_input }}

Таким образом, функция escape() в основном используется, когда требуется вручную обрабатывать HTML вне шаблона (например, в логике представлений или при создании API-ответов).


2. Удаление всех HTML-тегов: strip_tags()

Иногда нужно не просто экранировать HTML, но и полностью удалить все теги и извлечь только чистый текст. Например, когда нужно удалить все HTML-теги из текста блога и использовать его в качестве резюме для поисковых результатов.

strip_tags() выполняет именно эту функцию.

Пример:

from django.utils.html import strip_tags

html_content = "<p>Это <strong>очень важное</strong> <em>извещение</em>.</p>"

plain_text = strip_tags(html_content)

print(plain_text)
# Результат:
# Это очень важное извещение.
# (Пробелы между тегами также аккуратно очищаются)

3. Безопасное создание HTML: format_html()



Одна из самых мощных и важных функций.

Иногда необходимо динамически создавать HTML в Python-коде (например, в views.py или models.py). Например, может потребоваться, чтобы метод модели возвращал ссылку в определенном формате на странице администратора.

В этом случае, если с помощью f-строки или оператора + собрать строку, это может открыть уязвимости к XSS.

format_html(format_string, *args, **kwargs) автоматически обрабатывает все аргументы (args, kwargs) с помощью escape(), кроме format_string, вставляя их в строку. Затем окончательный результат помечается как "этот HTML безопасен" (mark_safe), чтобы он был отрендерен без экранирования в шаблоне.

Пример: (создание ссылок на странице администратора из метода модели)

from django.db import models
from django.utils.html import format_html
from django.utils.text import slugify

class Post(models.Model):
    title = models.CharField(max_length=100)

    def get_edit_link(self):
        # [Плохой пример] f-строка: если в self.title есть <script>, это вызовет XSS
        # return f'<a href="/admin/blog/post/{self.id}/change/">{self.title}</a>'

        # [Хороший пример] использование format_html
        # self.id и self.title будут автоматически обработаны через escape.
        url = f"/admin/blog/post/{self.id}/change/"
        return format_html(
            '<a href="{}">{} (редактировать)</a>',
            url,
            self.title  # даже если title "My<script>...", оно превратится в "&lt;script&gt;"
        )

4. Утилиты для форматирования текста: linebreaks и urlize

Эти функции являются исходными функциями шаблонных фильтров (|linebreaks, |urlize) и полезны для преобразования чистого текста в HTML-формат.

  • linebreaks(text) : преобразует символы новой строки (\n) в теги HTML <p> или <br>. Полезно, когда нужно показать текст, введенный пользователем в textarea, с сохранением форматирования.
  • urlize(text) : находит URL-паттерны, такие как http://..., https://..., www... в тексте и автоматически оборачивает их в теги <a>.

Пример:

from django.utils.html import linebreaks, urlize

raw_text = """Здравствуйте.
Тестирую djang.utils.html.

Посетите сайт: https://www.djangoproject.com
"""

# 1. Применение переноса строк
html_with_breaks = linebreaks(raw_text)
# Результат (примерно):
# <p>Здравствуйте.<br>Тестирую djang.utils.html.</p>
# <p>Посетите сайт: https://www.djangoproject.com</p>

# 2. Применение ссылок URL
html_with_links = urlize(html_with_breaks)
# Результат (примерно):
# ...
# <p>Посетите сайт: <a href="https://www.djangoproject.com" rel="nofollow">https://www.djangoproject.com</a></p>

5. Безопасное объединение нескольких элементов в HTML: format_html_join()

Если format_html() форматирует один элемент, то format_html_join() используется для безопасного объединения нескольких элементов (списки, кортежи и т.д.).

Используется в формате format_html_join(separator, format_string, args_list).

  • separator: HTML для разделения каждого элемента (например, '\n', <br>)
  • format_string: HTML-формат, который будет применен к каждому элементу (например, <li>{}</li>)
  • args_list: список данных, которые будут последовательно подставлены в format_string

Пример: (преобразование списка Python в <ul>)

from django.utils.html import format_html_join
from django.utils.safestring import mark_safe

options = [
    ('item1', 'элемент 1'),
    ('item2', '<strong>опасный элемент 2</strong>'),
]

# {0} обозначает первый элемент кортежа, {1} - второй.
# 'элемент 1' и '<strong>...' будут автоматически экранированы.
list_items = format_html_join(
    '\n',  # разделение каждого элемента перевода строки
    '<li><input type="radio" value="{0}">{1}</li>', # применяемый формат для каждого элемента
    options  # список данных
)

# list_items становится 'безопасным' HTML-кусочком.
final_html = format_html('<ul>\n{}\n</ul>', list_items)

# Если отрендерить final_html в шаблоне как {{ final_html }}...

Результат (HTML исходный код):

<ul>
<li><input type="radio" value="item1">элемент 1</li>
<li><input type="radio" value="item2">&lt;strong&gt;опасный элемент 2&lt;/strong&gt;</li>
</ul>

6. Безопасная передача данных в / тег: json_script()

Часто необходимо передавать данные Python в JavaScript переменные в шаблонах Django. В этом случае использование json_script(data, element_id) будет очень удобным и безопасным.

Эта функция преобразует словари или списки Python в JSON-строку и помещает её в тег <script> с типом application/json.

Пример: (передача данных в представлении)

# views.py
from django.utils.html import json_script

def my_view(request):
    user_data = {
        'id': request.user.id,
        'username': request.user.username,
        'isAdmin': request.user.is_superuser,
    }
    # Вставка user_data в тег <script id="user-data-json">
    context = {
        'user_data_json': json_script(user_data, 'user-data-json')
    }
    return render(request, 'my_template.html', context)

Шаблон (my_template.html):

{{ user_data_json }}

<script>
    const dataElement = document.getElementById('user-data-json');
    const userData = JSON.parse(dataElement.textContent);

    console.log(userData.username); // "admin"
</script>

Этот способ полностью предотвращает ошибки синтаксиса или уязвимости XSS, которые могут возникнуть, если пытаться вручную вставить данные с помощью var user = {{ user_data }}; из-за символов " или '.


7. [Продвинутое] Явное указание, что HTML безопасен: mark_safe() / html_safe

Иногда разработчик может умышленно создать HTML и быть уверенным, что он на 100% безопасен, и захочет отключить функцию автоэкранирования Django.

Функции, такие как format_html() или json_script(), автоматически выполняют эту обработку.

  • mark_safe(s): возвращает строку s с "это безопасный HTML, не экранируйте его"" - 'ярлык безопасности'. Эта функция сама по себе не экранирует. Таким образом, её нельзя использовать с ненадежными данными.

  • @html_safe (декоратор): используется для обозначения того, что строка, возвращаемая методом модели или пользовательской функцией шаблона, безопасна. Полезно при создании HTML с сложной логикой, когда использование format_html неуместно.

Пример: (применение к методу модели)

from django.db import models
from django.utils.html import format_html, html_safe

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()

    # Этот метод, использующий format_html, уже безопасен (рекомендуемый способ)
    def get_username_display(self):
        return format_html("<strong>{}</strong>", self.user.username)

    # Этот метод безопасен благодаря сложной логике и @html_safe (продвинутый способ)
    @html_safe
    def get_complex_display(self):
        # ... (логика сложного HTML-соединения, безопасная по мнению разработчика) ...
        html_string = f"<div>{self.user.username}</div><p>{self.bio}</p>"
        # Этот метод уязвим для XSS, если в bio находится <script>.
        # @html_safe нужно использовать очень осторожно.
        return html_string

Резюме

Модуль django.utils.html является незаменимым инструментом для реализации основной философии безопасности Django (Автоэкранирование) на уровне Python-кода.

  • Для предотвращения XSS используйте escape(). (автоматически в шаблонах)
  • Чтобы удалить теги, используйте strip_tags().
  • Для безопасного создания HTML в Python-коде следует использовать format_html().
  • Для объединения данных списка в HTML используйте format_html_join().
  • Чтобы передать данные Python в JavaScript, json_script() является самым безопасным и стандартным способом.
  • Использование mark_safe или @html_safe отключает автоматическое экранирование, поэтому стоит использовать format_html, если нет реальной необходимости.

Правильное понимание и использование этих инструментов поможет создать защищенное приложение Django.