Skip to content
Назад к блогу
Руководства

HTML-сущности: именованные, числовые и когда экранировать

Практическое руководство по HTML-сущностям: именованные, десятичные и шестнадцатеричные ссылки, пять символов для экранирования и правила контекста против XSS.

8 мин чтения

HTML-сущность — это способ записать символ так, чтобы браузер показал его как текст, а не воспринял как разметку. Поставьте в содержимом обычный < — и браузер начнёт читать тег; напишите вместо него &lt; — и на странице отрендерится буквальный <. На этой подмене и держится всё кодирование HTML-сущностей.

Пять символов несут в HTML особое значение и экранируются чаще всего: <, >, &, " и '. Экранируют их по двум причинам. Первая — отображение: нужно показать код или разметку как текст. Вторая, и более важная, — безопасность: экранирование недоверенного ввода лежит в основе защиты от межсайтового скриптинга (XSS).

Любую сущность можно записать тремя взаимозаменяемыми способами — именованным (&lt;), десятичным (&#60;) и шестнадцатеричным (&#x3C;), — и все они дают один и тот же символ. Более тонкий вопрос — когда экранировать и чем, ведь правильный ответ зависит от того, куда попадает значение: в текст HTML, в атрибут, в скрипт или в URL. Это руководство разбирает нотации, зарезервированный набор, матрицу решений по контексту и подводные камни, которые подводят чаще всего.

Что такое HTML-сущность? (анатомия)

HTML-сущность, она же ссылка на символ (character reference), — это короткий код, заменяющий один символ. Каждая сущность начинается с амперсанда & и заканчивается точкой с запятой ;. То, что стоит между ними, и определяет, какой символ вы получите.

Существуют три формы:

  • &name;именованная ссылка, например &lt; или &copy;.
  • &#decimal;десятичная числовая ссылка, например &#60;.
  • &#xhex;шестнадцатеричная числовая ссылка, например &#x3C;.

Браузер читает ссылку, находит символ, на который она указывает, и рендерит этот единственный символ. Видимый результат при этом не меняется — &lt; и обычный < отображаются одинаково. Разница лишь в том, что сущность трактуется как текст, а не как начало тега.

Три нотации: именованная, десятичная, шестнадцатеричная

Все три нотации ссылаются на одну и ту же кодовую точку Unicode; различаются они только написанием. Именованная сущность — это читаемая форма, но она существует лишь для символов, у которых есть определённое имя. Десятичная сущность записывает кодовую точку в основании 10. Шестнадцатеричная сущность записывает ту же кодовую точку в основании 16, что напрямую соответствует нотации U+XXXX, которую вы видите в стандарте Unicode.

СимволИменованнаяДесятичнаяHex
<&lt;&#60;&#x3C;
&&amp;&#38;&#x26;
©&copy;&#169;&#xA9;
é&eacute;&#233;&#xE9;

Поскольку hex напрямую отражает U+XXXXé это U+00E9, отсюда &#xE9;, — многие разработчики выбирают его, когда документируют конкретную кодовую точку или рассуждают о ней. Для повседневной разметки лучше всего читаются именованные сущности.

Пять зарезервированных символов, которые нужно экранировать

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

СимволИменованнаяДесятичнаяHexЧто сломается без экранирования
<&lt;&#60;&#x3C;Начинает тег — браузер читает следующий текст как разметку
>&gt;&#62;&#x3E;Преждевременно закрывает тег
&&amp;&#38;&#x26;Начинает сущность — остаток может быть прочитан как ссылка
"&quot;&#34;&#x22;Рано завершает значение атрибута в двойных кавычках
'&#x27;&#39;&#x27;Рано завершает значение атрибута в одинарных кавычках

HTML-сущность амперсанда — корень всей системы. Символ & начинает каждую сущность, поэтому экранировать его нужно первым: экранируйте угловые скобки раньше амперсанда — и вы повторно экранируете & в только что созданных сущностях. Подробнее об этой ловушке ниже.

Когда экранировать действительно нужно? (с учётом контекста)

Именно здесь живёт большинство багов и большинство уязвимостей. Главный принцип краток: экранируйте на этапе вывода, согласованно с контекстом, куда попадает значение. То, что безопасно в одном месте, опасно в другом, поэтому применяемое кодирование обязано соответствовать месту назначения.

Содержимое HTML-элемента

Когда вы помещаете значение между тегами — внутрь <p>, <div>, <td> — экранируйте <, > и &. Экранировать здесь кавычки безвредно, но не нужно. Если требуется показать текст <strong> как буквальные символы, а не сделать следующее слово жирным, закодируйте его в &lt;strong&gt; — и браузер напечатает тег, а не применит его.

Значения атрибутов HTML

Внутри атрибута критическими становятся символы кавычек. Если значение стоит в title="…" и содержит неэкранированную ", оно рано завершит атрибут и позволит атакующему дописать новые атрибуты — классический вектор XSS. Экранируйте " (и желательно ') в контексте атрибута. Значение вроде He said "hi" должно стать He said &quot;hi&quot;, чтобы остаться внутри атрибута.

Внутри <script> или встроенного JavaScript

HTML-сущности здесь не помогут. Строке, встраиваемой в блок <script> или во встроенный обработчик события, нужно экранирование строк JavaScript или JSON, а не ссылки на символы. Запись &quot; внутри строкового литерала JS даёт буквальные шесть символов, а не кавычку. Для этого контекста используйте инструмент Экранирование JSON и прочитайте полное руководство по экранированию строк JSON про правила \uXXXX, которые действительно применимы внутри скрипта.

Внутри URL

У URL своя схема экранирования: процентное кодирование. HTML-сущности не сделают значение безопасным для URL. Строка a&b c должна попасть в запрос как a%26b%20c, а не a&amp;b c — пробел всё равно сломает URL, а & всё так же разделит параметры. Используйте URL декодер и кодировщик для этого и руководство по кодированию и декодированию URL — для полных правил о зарезервированных и незарезервированных символах.

Матрица решений

КонтекстЧем экранироватьПримерНеверный выбор, который не сработает
Содержимое HTML-элементаHTML-сущности (< > &)<strong>&lt;strong&gt;Оставленный сырой < внедряет тег
Значение атрибута HTMLHTML-сущности (" ' критичны)"hi"&quot;hi&quot;Неэкранированная " вырывается наружу
<script> / встроенный JSЭкранирование строк JS / JSON"\"HTML-сущности в JS бездействуют
URL / строка запросаПроцентное кодированиепробел → %20&amp; и сущности всё равно ломают URL

Именованные или числовые: что выбрать?

Именованные сущности читаемы и являются правильным выбором по умолчанию для распространённых зарезервированных символов и хорошо известных знаков — &lt;, &amp;, &copy;, &mdash;. Однако существуют они лишь для символов, у которых есть определённое имя. Числовые сущности, десятичные или hex, могут закодировать любую кодовую точку, в том числе безымянную, что делает их универсальным запасным вариантом. Когда нельзя гарантировать, что принимающая система поддерживает конкретную именованную сущность, числовая форма — безопасный выбор.

Почему апостроф это &#x27;, а не &apos;

Именованная сущность &apos; появилась только в HTML5 и XML. В HTML4 она не определена, поэтому горстка старых парсеров и почтовых клиентов рендерит её как буквальный текст &apos; вместо апострофа. Числовая ссылка &#x27; — и её десятичный близнец &#39; — указывает на тот же самый символ, U+0027, и понятна любому соответствующему стандарту парсеру за всю историю. Хорошо протестированные библиотеки экранирования вроде he выдают &#x27; для одинарной кавычки именно по этой причине, и хороший кодировщик следует этому соглашению, чтобы вывод можно было безопасно вставить в любой контекст HTML, XML или атрибута.

Кодировка против сущностей: когда кодировать не-ASCII

Кодировка символов, например UTF-8, определяет, как символы хранятся в виде байтов. Сущность — это способ записать символ, используя только чистый ASCII (&, #, ;, буквы, цифры). Это разные слои, и их смешение приводит к ненужному кодированию.

На странице в UTF-8 — то есть почти на любой современной странице, которая объявляет <meta charset="utf-8">, — буквы с диакритикой, тире и эмодзи являются допустимыми сырыми символами. Оставьте é, и 😀 ровно как есть. Кодирование всего подряд в сущности имеет смысл лишь тогда, когда текст должен пережить устаревшую однобайтовую кодировку или систему, которая искажает сырой UTF-8; для таких случаев существует режим «кодировать всё не-ASCII». Если вы не уверены, как связаны байты, кодовые точки и символы, руководство по кодированию UTF-8, UTF-16 и Unicode раскладывает эту модель по полочкам.

Частые ошибки с HTML-сущностями

Экранирование & в последнюю очередь приводит к двойному экранированию

Порядок имеет значение. Если заменить < и > раньше &, у только что созданных сущностей (&lt;, &gt;) тоже экранируется ведущий &, так что < превращается в &amp;lt; и рендерится как буквальный текст &lt;. Всегда экранируйте & первым, а затем остальное. Одно это правило снимает самый частый баг кодирования.

Двойное кодирование уже экранированного текста

Прогон уже экранированного текста через кодировщик ещё раз кодирует его повторно. &amp; становится &amp;amp;, и посетитель видит на странице &amp; вместо &. Экранируйте ровно один раз, на этапе вывода. Если значение проходит через несколько слоёв, убедитесь, что экранирует только один из них.

Mojibake при декодировании

У обратного направления своя ловушка. Декодируйте не той кодировкой или декодируйте дважды — и вы получите искажённый вывод, тот самый mojibake. Если страница показывает буквальный &amp;lt; там, где вы ожидали <, вставьте его в HTML-декодировщик сущностей, чтобы точно увидеть, во что разворачиваются сущности; он обрабатывает именованные, десятичные, hex и даже устаревшие незавершённые ссылки вроде &copy без завершающей точки с запятой.

Вера в экранирование как в полное лекарство от XSS

Экранирование — первая линия обороны, а не единственная. Поскольку у HTML есть несколько контекстов с разными правилами, экранирование под не тот контекст оставляет дыру — кавычки в атрибутах, экранирование JS в скрипте, процентное кодирование в URL. Сочетайте корректное экранирование с учётом контекста с политикой безопасности контента (Content Security Policy) и автоэкранированием вашего фреймворка. Считайте кодирование сущностей фундаментом, поверх которого наслаиваются CSP и настройки фреймворка по умолчанию.

Как кодировать и декодировать сущности на практике

Когда вы собираете HTML вручную, экранируете его вы сами. Вот корректная escapeHtml(), которая соблюдает порядок «& первым», плюс более правильный подход для реального кода приложения.

// The five reserved characters and their safe entities:
//   <  →  &lt;     >  →  &gt;     &  →  &amp;     "  →  &quot;     '  →  &#x27;

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')   // & FIRST, so later entities are not double-escaped
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;'); // numeric form — safe in HTML4, HTML5 and XML
}

const userInput = `<a href="x">Tom & Jerry's</a>`;
const safe = escapeHtml(userInput);
// → &lt;a href=&quot;x&quot;&gt;Tom &amp; Jerry&#x27;s&lt;/a&gt;

// Better in app code: let the platform escape for you.
//   el.textContent = userInput;   // the browser escapes; no manual replace
//   React / Vue / Angular escape interpolated text by default
//   Server templates (Jinja, ERB, Blade) auto-escape unless you opt out

Самописная функция полезна, чтобы понять происходящее, и для разовых преобразований, но в продакшене предпочтительнее встроенный путь. Установка element.textContent даёт браузеру экранировать за вас, а современные фреймворки автоматически экранируют интерполируемые значения. Оставьте ручное экранирование для случаев, которые платформа не покрывает.

Для разовых задач HTML-кодировщик сущностей экранирует зарезервированный набор (именованно, десятично или hex), а HTML-декодировщик сущностей выполняет обратное. Эти двое — точные инверсии друг друга для зарезервированных символов, так что можно прогнать текст через оба туда и обратно без потерь.

Часто задаваемые вопросы

Что такое HTML-сущность?

HTML-сущность — это короткий код, начинающийся с & и заканчивающийся ;, который представляет один символ. Браузер рендерит символ, на который указывает сущность, вместо того чтобы воспринимать её как разметку. Например, &lt; отображает буквальный <, а &amp; — буквальный &.

Какие символы нужно экранировать в HTML?

Пять зарезервированных HTML-символов со специальным значением: <, >, &, " и '. В содержимом элемента в основном нужны <, > и &; в значениях атрибутов критичными становятся ещё и кавычки " и '. Экранируйте амперсанд & первым, чтобы остальные сущности не были экранированы дважды.

Использовать именованные или числовые (десятичные/hex) сущности?

Используйте именованные сущности (&lt;, &copy;) ради читаемости с распространёнными символами, поскольку их легко узнать. Используйте числовые сущности (десятичную &#60; или hex &#x3C;), когда нужно закодировать символ без определённого имени или когда нельзя гарантировать, что потребитель поддерживает данную именованную сущность. Обе формы ссылаются на одну и ту же кодовую точку.

Защищают ли HTML-сущности от XSS?

Они являются фундаментом, если применять их правильно. Экранирование пяти зарезервированных символов перед помещением недоверенного ввода в содержимое HTML-элемента или атрибута останавливает внедрение тегов и скриптов. Но экранирование зависит от контекста: блокам скриптов нужно экранирование JavaScript, а URL — процентное кодирование. Сочетайте корректное экранирование с учётом контекста с CSP и автоэкранированием фреймворка.

Почему моя страница показывает &amp;lt; вместо <?

Это двойное экранирование. Текст был закодирован дважды, либо & был экранирован после угловых скобок, так что & в &lt; превратился в &amp;. Посетитель тогда видит &lt; как буквальный текст. Экранируйте ровно один раз и всегда экранируйте & первым. Инструмент-декодировщик может подтвердить, во что разворачиваются сущности.

Нужно ли экранировать символы вроде é, — или эмодзи?

Обычно нет. На странице, которая объявляет <meta charset="utf-8">, буквы с диакритикой, тире и эмодзи являются допустимыми сырыми символами и не требуют кодирования — оставьте их как есть. Кодируйте не-ASCII лишь тогда, когда текст должен пройти через устаревшую однобайтовую кодировку или систему, которая портит сырой UTF-8.

Теги: HTML Encoding Security Web

Похожие статьи

Все статьи