Перейти к содержанию

Глава 37. Web Application Firewall (WAF)

Жесты защиты веб-приложений: anti-CSRF, фильтрация загрузок по типу содержимого, лимиты тела запроса, накопительный движок <Waf> и XML-firewall. Все они доступны в слое <Proxy><Waf> — задокументированное расширение Redcoat).

37.1. Anti-CSRF (http.csrf.*) — защита веб-приложений (WAF)

Семейство http.csrf.* защищает веб-приложения за прокси (режим WAF / reverse-proxy) от CSRF-атак (Cross-Site Request Forgery). Принцип: прокси вставляет в формы приложения подписанный анти-CSRF токен (token.insert), а затем проверяет его в последующих POST-запросах (detection); запрос без валидного токена отклоняется.

Применимость: это WAF-фича (reverse-proxy перед защищаемым приложением), не SWG (forward-proxy для клиентов) — в forward-режиме прокси не владеет формами чужих сайтов.

Свойство Синтаксис Семантика Статус
http.csrf.detection (yes\|no) Проверка анти-CSRF токена в form-type POST (urlencoded/multipart); невалидный/отсутствующий → 403 (блокировка WAF)
http.csrf.token.name (<name>) Имя поля токена (по умолчанию CSRF-Token)
http.csrf.token.insert (yes\|no) Вставка скрытого токена в HTML-формы ответа
http.csrf.authentication_link (user_id\|client_ip\|user_id,client_ip) Какая часть идентичности привязывается к токену

Синтаксис:

http.csrf.detection(yes|no)
http.csrf.token.name(<name>)
http.csrf.token.insert(yes|no)
http.csrf.authentication_link(<link>)

Слои: <Proxy> (WAF-фаза). Транзакции: HTTP form-POST.

Пример:

<Proxy>
    ; защитить /app/* от CSRF: вставлять токен и валидировать POST
    url.path=/app/ http.csrf.token.insert(yes) http.csrf.detection(yes)

Соответствие BlueCoat 7.3: «Used in WAF deployments» — токен зашифрован и подписан криптографически-стойкой подписью; detection проверяет form-type POST; token.name переопределяет default CSRF-Token; authentication_link задаёт содержимое токена.

Поведение: - Токен: имеет вид base64url(payload).base64url(HMAC-SHA256(payload)), где payload = привязка:nonce:время_выдачи — подписан (не зашифрован), привязан к идентичности, ограничен по времени (срок жизни 1 час). - Проверка (detection): на form-type POST токен извлекается из тела (по token.name) и проверяется (привязка к пользователю или IP клиента); невалидный → 403. - Вставка (token.insert): после каждого <form> в HTML-ответе вставляется скрытое поле <input name={token.name} value=...>. authentication_link задаёт привязку (user_id/client_ip/оба). - Отличие от BlueCoat: токен подписан HMAC (целостность и подлинность), но не зашифрован (BlueCoat шифрует). По рекомендациям OWASP для защиты от CSRF подписи достаточно (токен не секрет — нужна защита от подделки, а не от чтения).

Наблюдаемость (WAF): заблокированный CSRF-POST попадает в тот же конвейер, что и блокировки WAF — увеличивается счётчик заблокированных запросов и фиксируется источник (IP) на дашборде WAF, пишется запись в журнал безопасности (event=csrf_blocked rule_id=csrf-token-invalid) и формируется событие блокировки WAF (которое отображается в счётчиках и таблице событий WAF). Счётчики наполняются при включённом WAF; журнал безопасности пишется всегда. Управление — целиком через CPL (отдельная страница интерфейса не нужна).

Поведение: detection (без токена → 403, с валидным → 200, с поддельным → 403), token.insert (GET вставляет токен → POST с ним → 200, без него → 403), а также token.name, authentication_link работают как описано.


37.2. Apparent Data Type — фильтрация загрузок по типу содержимого (http.request.apparent_data_type.*)

Назначение. Контроль HTTP POST-загрузок по фактическому типу данных тела запроса (Apparent Data Type, ADT). Тип определяется content-sniffing'ом — по магическим байтам первых байт тела, — а НЕ по заявленному Content-Type или расширению файла. Это делает фильтр устойчивым к подмене MIME/расширения. Главный кейс — Reverse Proxy: ограничить, какие файлы клиент может загрузить на back-end-сервер. Слой <Proxy>, только HTTP POST, без участия ICAP.

Синтаксис.

http.request.apparent_data_type.allow(<type>, ...)   ; allow-list
http.request.apparent_data_type.deny(<type>, ...)    ; deny-list

Семантика. - allow(t, …) — проходят только перечисленные типы; всё остальное, включая неопознанные content-sniffer'ом тела, — deny (консервативно: «разрешено только то, что явно разрешено»). - deny(t, …) — блокируются только перечисленные типы; всё остальное проходит (инверсия allow). - Заблокированный POST → 403 (Error::AccessDenied), с записью в security-лог (event=adt_blocked detected_type=<label> mode=allow|deny).

Распознаваемые типы (label). ~40 форматов по магическим байтам: 7ZIP ACE ARJ BMP BZ2 CAB(MSCAB/ISCAB) COMPRESS CPIO DAA EGG EML EXE FLASH GIF GZIP HTML ICC JPG LHA LZIP MACH-O MSDOC PDF PNG RAR RTF TAR TIF TNEF TTF TXT UUE XAR XML XZ ZIP. Тип можно указывать лейблом или расширением (pdfPDF); CAB — зонтичный (матчит MSCAB и ISCAB).

Особенности классификации (как в BlueCoat 7.3): - XLSX/DOCX/PPTX (OOXML) классифицируются как ZIP, не MSDOC. Чтобы политика сработала на XLSX — матчите ZIP. MSDOC = только legacy OLE2 (.doc/.xls/.ppt/.msi). - EXE покрывает и MZ, и редкий ZM; TIF — classic и BigTIFF; универсальный (fat) Mach-O детектится (коллизия с Java .class разрешена по nfat_arch). - MRAR/MZIP (многотомные) неотличимы по содержимому от RAR/ZIP — детектятся как RAR/ZIP.

Примеры.

; Reverse-proxy: разрешить загрузку только изображений и PDF
<Proxy>
  url.host=upload.example.com http.request.apparent_data_type.allow(JPG, PNG, GIF, PDF)

; Запретить загрузку исполняемых файлов и архивов (даже если переименованы)
<Proxy>
  http.request.apparent_data_type.deny(EXE, MACH-O, ZIP, 7ZIP, RAR)
В первом примере POST с телом-EXE (или с любым неопознанным телом) на upload.example.com → 403; PNG-картинка → проходит. Во втором — переименованный в photo.jpg Windows-исполняемый файл всё равно определяется как EXE по сигнатуре MZ и блокируется.

Поведение. Таблица магических байтов тщательно сверена с авторитетными источниками (это контроль безопасности: неверная сигнатура означала бы молча пропущенную загрузку). Тело POST анализируется по содержимому, и при совпадении возвращается 403. Тело буферизуется для любого POST с телом, поэтому распознаются и сырые (не form) загрузки.

Условие http.request.apparent_data_type=<type>. Помимо действий, можно матчить тип в условии правила:

<Proxy>
  http.request.apparent_data_type=PDF deny           ; запретить PDF-загрузки
  http.request.apparent_data_type=(EXE, ZIP) exception(upload_blocked)
Тип определяется тем же анализом содержимого (по первым ≤512 байтам тела POST). Поддерживаются лейбл, расширение и зонтичный тип CAB (без учёта регистра), списки и продвинутые шаблоны (подстановочные знаки, регулярные выражения, префиксы). Для запроса без тела или с нераспознанным типом совпадения нет (правило не срабатывает и не начинает реагировать на любой трафик).

Наблюдаемость (WAF): заблокированный POST попадает в тот же конвейер, что и блокировки WAF — увеличивается счётчик заблокированных запросов и фиксируется источник (IP) на дашборде WAF, пишется запись в журнал безопасности (event=adt_blocked detected_type=<label> mode=allow|deny) и формируется событие блокировки WAF (которое отображается в счётчиках и таблице событий WAF). Счётчики наполняются при включённом WAF; журнал безопасности пишется всегда. Управление — целиком через CPL (отдельная страница интерфейса не нужна, как у CSRF); блокировки видны на дашборде WAF.

Поведение: deny(PDF) → PDF-тело = 403, текст = 200; allow(PDF) → PDF = 200, текст = 403; условие =PDF deny → PDF = 403, текст = 200, GET = 200.

Известные ограничения: - Сторона ответа http.response.apparent_data_type= пока не работает: условие фактически не срабатывает (парсится, но никогда не совпадает) — нужна детекция в фазе ответа. Сторона запроса (http.request.apparent_data_type=) реализована. - Вложенный файл в multipart: детектор применяется к телу POST как есть; для multipart/form-data это распознаёт конверт, а не вложенный файл. Извлечение вложения пока не реализовано. - Синтаксические формы действий [t,..](yes|no) и .t(yes|no) пока не разбираются (только каноническая (t,…)).


37.3. Лимиты размера тела запроса для WAF (http.request.body.max_size / .inspection_size)

Два WAF-действия <Proxy> ограничивают объём тела запроса.

http.request.body.max_size(N). Если тело запроса больше N байт — запрос блокируется с 413 Payload Too Large (канонический код BlueCoat). Семантика BlueCoat: тело больше лимита не обрабатывается.

<Proxy>
  http.request.body.max_size(1048576)        ; запретить тела > 1 МБ
Например: max_size(50) → тело 120 байт = 403, 20 байт = 200.

http.request.body.inspection_size(N). Ограничивает сканирование WAF первыми N байтами тела (содержимое за этой границей не инспектируется). WAF обрезает тело по лимиту перед сканированием.

Парные условия http.request.body.inspection_size_exceeded=yes|no и http.request.body.max_size_exceeded=yes|no — истинны, если тело превысило соответствующий лимит; читают реальное состояние. Особенность: флаг выставляется уже после оценки правил текущего прохода, поэтому в фазе запроса того же прохода условие видит false — оно полезно в фазе ответа; для блокировки используйте сам max_size() (он применяет лимит напрямую).

Ограничения: действие inspection_size пока проверено не до конца — в тестовом окружении WAF не детектирует тело запроса даже при полном сканировании, поэтому наблюдать эффект обрезки тела пока невозможно; http.request.body.data_type() (переопределение типа тела для WAF) применяет безопасный отказ (нет движка детекции).


37.4. Расширение Redcoat: слой <Waf> (CRS/CF-F5 anomaly-scoring WAF)

Это расширение Redcoat, которого НЕТ в BlueCoat ProxySG 7.3. Раздел вынесен отдельно, чтобы слой <Waf> не путали с эталонной моделью BlueCoat. В BlueCoat нет ни слоя <Waf> (0 вхождений в 7.3-референсе), ни понятия «накопительного anomaly score».

Архитектурный принцип (решение 1a)

Конструкции делятся по слоям однозначно:

Что Слой Семантика Источник
WAF-жесты BlueCoathttp.csrf.*, http.request.apparent_data_type.*, http.request.body.*, http.request.detection.*, http.request.normalization.*, условия detection.result=/rule=/threat_risk= <Proxy> выигрывает первое совпадение (одно правило) BlueCoat 7.3 (канон)
CRS-движок Redcoatwaf.enable, waf.action, waf.anomaly_threshold, waf.paranoia, waf.ruleset, waf.anomaly_score+=, detection_result(), waf.log_extra <Waf> суммирование по ВСЕМ совпавшим правилам Расширение Redcoat

Правило размещения: всё, что есть в BlueCoat, пишется в <Proxy> и там же работает. Всё, что относится к CRS-WAF Redcoat, живёт в <Waf>. Слои НЕ взаимозаменяемы: перенос <Waf><Proxy> сломал бы накопительный подсчёт очков (слой BlueCoat берёт первое совпадение и останавливается).

Почему <Waf> — отдельный слой

Модель BlueCoat — движковая: define application_protection_set engine=blacklist + жест http.request.detection.<set>(block|monitor) в <Proxy>. Решение «блокировать/мониторить» принимает встроенный движок, политика лишь дёргает результат.

Модель Redcoat <Waf>CRS-накопительная (вдохновлена ModSecurity/OWASP CRS, CloudFlare, F5 ASM): каждое совпавшее правило добавляет очки (anomaly_score += N), и при превышении anomaly_threshold срабатывает action. Это требует обойти и просуммировать все совпадения, а не остановиться на первом — отсюда для слоя <Waf> используется отдельный проход с суммированием по всем совпавшим правилам, а не общий проход «выигрывает одно правило».

Surface слоя <Waf> (действия)

<Waf>
    ; включение/режим
    waf.enable(yes)
    waf.action(block)              ; block | monitor | log
    waf.anomaly_threshold(5)       ; порог суммарного score
    waf.paranoia(2)                ; уровень paranoia CRS (1..4)
    waf.ruleset("owasp-crs-4")     ; имя набора правил

    ; накопление и явный результат
    url.path=/upload          waf.anomaly_score += 3
    request.header.X-Attack=1 waf.anomaly_score += 2
    detection_result(sql_injection)   ; явно проставить вид детекта
    waf.log_extra(full)               ; детализация лога
</Waf>

Семантика накопления: если запрос совпал с тремя правилами, добавившими 3+2+… очков, и сумма ≥ anomaly_threshold, применяется waf.action. В monitor-режиме block понижается до log (счётчик logged всё равно инкрементится — см. наблюдаемость WAF stats).

Межслойный детект: условия detection.result/rule/threat_risk

Условия http.request.detection.result=, …rule=, …threat_risk= доступны в слоях <Proxy> и <Exception> и видят вердикт WAF в рамках того же запроса. Перед основным проходом по слоям выполняется проход детекции: встроенные детекторы (SQL-инъекция, XSS, обход пути, инъекция команд) и заданные политикой в <Waf> правила вычисляют вердикт, который затем доступен условиям детекта в последующих слоях.

<Proxy>
    ; заблокировать запрос, в котором WAF обнаружил SQL-инъекцию
    http.request.detection.result=sqli  deny

<Exception>
    ; отдать страницу под конкретное сработавшее правило
    exception.id=policy_denied  http.request.detection.rule=sqli-942100 \
        exception(sql_injection_page)

Значения detection.result: sqli, xss, rce, path_traversal, rfi, lfi, header_anomaly, xxe, xinclude, xml_anomaly, ssrf, ssti, deserialization, clean. Проход детекции выполняется только если политика реально использует условия detection.* — иначе он пропускается без накладных расходов на остальной трафик.

Детекторы ssrf (cloud-metadata / опасные схемы file:///gopher:///… / внутренние хосты в URL-контексте), ssti (полиглот-пробы шаблонизаторов: {{7*7}}, ${7*7}, Freemarker/Velocity/Thymeleaf) и deserialization (Java-magic / Python pickle / PHP serialized) — расширения Redcoat WAF, доступны через то же условие detection.result=. Пример: <Proxy> http.request.detection.result=ssrf deny.

Сводка для аудита

  • Слой <Waf>задокументированное расширение Redcoat, НЕ нарушение конформанса BlueCoat.
  • Все WAF-жесты BlueCoat канонично работают в <Proxy> (надмножество — они доступны там же, где в 7.3).
  • Условия детекта (detection.result/rule/threat_risk) видят вердикт WAF в рамках того же запроса — связка расширения Redcoat с условиями BlueCoat замкнута.

37.5. XML-firewall: детект XXE / XInclude / глубины вложенности (http.request.detection.xml.*)

Свойства слоя <Proxy>, инспектирующие XML-тело запроса (типичный кейс — SOAP/XML-API) и блокирующие вредоносный XML. Это BlueCoat-7.3-нативные жесты (не расширение Redcoat). Они действия (синтаксис с ()-аргументом, без =), а не условия — режим реакции задаётся аргументом.

Свойство Что детектирует
http.request.detection.xml.xxe(block\|monitor\|ignore) XML External Entity — <!ENTITY … SYSTEM/PUBLIC …> и параметрические сущности (<!ENTITY % …>); вектор чтения файлов сервера / SSRF
http.request.detection.xml.xinclude(block\|monitor\|ignore) директиву XInclude (элемент в ns http://www.w3.org/2001/XInclude); вектор включения файлов / SSRF
http.request.detection.xml.node_depth(1-99) превышение глубины вложенности XML (анти-DoS «billion laughs» / глубокая рекурсия); по умолчанию 99
http.request.detection.xml.invalid(block\|monitor\|ignore) не-well-formed (битый) XML; режим как у xxe/xinclude, по умолчанию ignore
http.request.detection.xml.cdata(yes\|no) модификатор (не самостоятельное действие): при yes инспектор разворачивает CDATA-секции и сканирует их содержимое теми же детекторами (XXE/XInclude) — ловит атаку, спрятанную в CDATA. Сам ничего не блокирует — блок даёт сработавший xxe/xinclude

Режимы: block — запретить запрос и залогировать (deny, резолвится в <Exception>); monitor — пропустить и залогировать; ignore — пропустить без лога.

Пример — блокировать XXE и XInclude, ограничить глубину 10 для SOAP-эндпоинта:

<Proxy>
    url.domain=soap.example.com \
        http.request.detection.xml.xxe(block) \
        http.request.detection.xml.xinclude(block) \
        http.request.detection.xml.node_depth(10) \
        allow

Результат (проверено сквозным тестом на работающем прокси): запрос с <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>…403 (страница отказа «XML external entity (XXE) detected»); безобидный <foo>hello</foo>200; XML с 15 уровнями вложенности при node_depth(10)403, с 5 уровнями → 200.

Как это работает (важно для перфа и точности):

  • Инспекция запускается только если тело — XML: Content-Type application/xml / text/xml / суффикс +xml; при пустом Content-Type — по magic-bytes (<?xml или ведущий <). Явный не-XML Content-Type подавляет magic-byte fallback — обычный текст с литералами <!ENTITY / xi:include НЕ триггерит детект (привязка к XML-телу, а не к подстроке).
  • Парсер потоковый (streaming): никогда не разворачивает и не резолвит внешние сущности — XXE невозможен в самом инспекторе; один проход; жёсткие лимиты (размер тела, глубина, число элементов/атрибутов/сущностей).
  • Тело инспектируется в распакованном виде; превышение лимита размера/глубины → явный отказ (deny-able), а не молчаливый пропуск.

Нюанс 1. Жесты допустимы только в <Proxy> (по 7.3).

Нюанс 2. block использует обычный deny (откладывается и резолвится в <Exception> — можно подставить кастомную страницу), а не force_deny.

Нюанс 3. cdata(yes)модификатор, а не самостоятельное действие: он лишь включает скан внутри CDATA, а блокировку даёт сработавший xxe/xinclude. cdata(yes) без них ничего не запретит.

Нюанс 4. Реализованы xxe / xinclude / node_depth / invalid / cdata. Жесты http.request.detection.xml.schema.schema_name(...) (валидация против XSD) и http.request.detection.xml.xpath_validation(...) пока не реализованы — политика с ними будет отклонена при загрузке, а не выполнится молча (безопасный отказ, а не тихий пропуск).