XSS и рендеринг данных
XSS-атаки позволяют выполнить произвольный JS-код, что ведет к краже токенов, перехвату сессий и подмене контента. Современные фреймворки экранируют данные по умолчанию. Этот защитный механизм обходить без крайней необходимости FORBIDDEN.
Правила
FORBIDDEN
❌ FORBIDDEN: Использование механизмов вставки сырого HTML (dangerouslySetInnerHTML в React) без предварительной санитизации
❌ FORBIDDEN: Присвоение недоверенных данных в innerHTML через ref без санитизации
❌ FORBIDDEN: Динамическое формирование HTML-строк с включением пользовательских данных и их последующая вставка в DOM
❌ FORBIDDEN: Самостоятельная реализация санитизации
❌ FORBIDDEN: Использование Regular Expressions для очистки HTML
MUST
✅ MUST: Если рендеринг HTML необходим (CMS, WYSIWYG, Markdown), санитизация выполняется непосредственно перед рендерингом
✅ MUST: Использовать только проверенные библиотеки санитизации:
dompurifyдля клиентского рендерингаisomorphic-dompurifyдля SSR-приложений
✅ MUST: Применять принцип whitelist (явное указание разрешенных тегов и атрибутов)
Примеры
Запрещенные практики
// ❌ FORBIDDEN: Вставка сырого HTML без санитизации
function CommentBody({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// ❌ FORBIDDEN: Использование innerHTML через ref
function CommentBody({ html }: { html: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.innerHTML = html; // Опасно!
}
}, [html]);
return <div ref={ref} />;
}
// ❌ FORBIDDEN: Динамическое формирование HTML с пользовательскими данными
function UserGreeting({ name }: { name: string }) {
const html = `<h1>Hello, ${name}!</h1>`; // Опасно!
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// ❌ FORBIDDEN: Самостоятельная санитизация через regex
function sanitizeHTML(html: string) {
return html
.replace(/<script/gi, '') // Недостаточно!
.replace(/on\w+=/gi, ''); // Обходится легко!
}
Правильный подход
// ✅ MUST: Санитизация с dompurify
import DOMPurify from 'dompurify';
function CommentBody({ html }: { html: string }) {
const sanitizedHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
ALLOWED_ATTR: ['href']
});
return <div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />;
}
// ✅ MUST: Markdown с санитизацией (react-markdown)
import ReactMarkdown from 'react-markdown';
function CommentBody({ markdown }: { markdown: string }) {
return (
<ReactMarkdown
allowedElements={['p', 'br', 'strong', 'em', 'a', 'code']}
>
{markdown}
</ReactMarkdown>
);
}
// ✅ Лучше всего: Использование React без обхода экранирования
function UserGreeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>; // React автоматически экранирует
}
Санитизация для SSR
// ✅ MUST: isomorphic-dompurify для SSR
import DOMPurify from 'isomorphic-dompurify';
export function ArticleContent({ html }: { html: string }) {
const sanitized = DOMPurify.sanitize(html, {
ALLOWED_TAGS: [
'h1', 'h2', 'h3', 'p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'code', 'pre'
],
ALLOWED_ATTR: ['href', 'target', 'rel']
});
return <article dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
Whitelist конфигурация
// Пример whitelist для разных типов контента
// Базовый текст (комментарии)
const BASIC_TEXT_CONFIG = {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: []
};
// Форматированный текст (статьи)
const RICH_TEXT_CONFIG = {
ALLOWED_TAGS: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'p', 'br', 'strong', 'em', 'u', 's',
'ul', 'ol', 'li',
'a', 'img',
'blockquote', 'code', 'pre'
],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'target', 'rel']
};
// Использование
function RichTextContent({ html }: { html: string }) {
const sanitized = DOMPurify.sanitize(html, RICH_TEXT_CONFIG);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
📌 Ключевые моменты:
- Современные фреймворки экранируют данные по умолчанию
- Обход экранирования FORBIDDEN без санитизации
- MUST использовать whitelist подход
- MUST использовать проверенные библиотеки (
dompurify)- Санитизация выполняется непосредственно перед рендерингом