diff options
| author | Татьяна Фарбер <alexandr@asustem.ru> | 2026-10-22 00:40:04 +0400 |
|---|---|---|
| committer | Татьяна Фарбер <alexandr@asustem.ru> | 2026-10-22 00:40:04 +0400 |
| commit | 2010bd982ef630ba9f8115aa8104fceec83cc1b6 (patch) | |
| tree | 7a2f1630b62ccb5f2be3b0ad36c8468911dc2e8e | |
| parent | 9412e0deb93f3d551ea6223a310aa6973c8b370e (diff) | |
| -rw-r--r-- | README.md | 22 | ||||
| -rw-r--r-- | example2.lua | 85 | ||||
| -rw-r--r-- | index.html | 138 |
3 files changed, 245 insertions, 0 deletions
@@ -24,6 +24,8 @@ UPDATE: добавлена работа со спецификой уязвимо • example.lua — Пример реализации на фазе access_by_lua 7 уровня OSI (фаза блока http). +• example2.lua — Пример реализации с возвратом данных инцендента для заглушки. + • waf/config.lua — Единая точка конфигурации лимитов, белых списков и штрафных матриц. • waf/ban.lua — Двухуровневый кэш блокировок (Локальная L1 таблица воркера ➡️ L2 shared_dict). @@ -91,3 +93,23 @@ http { } ``` + +3. С заглушкой + +``` + error_page 403 = @waf_block_page; + + location @waf_block_page { + root /home/html; + rewrite ^(.*)$ /index.html break; + ssi on; + access_log off; + } + + server { + listen 443 ssl; + server_name test.ru; + access_by_lua_file /home/example2.lua; + } + +``` diff --git a/example2.lua b/example2.lua new file mode 100644 index 0000000..4fc41f2 --- /dev/null +++ b/example2.lua @@ -0,0 +1,85 @@ +local detector = require('waf.detector') +local analyzer = require('waf.analyzer') +local filter = require('waf.filter') +local limits = require('waf.limits') +local config = require('waf.config') +local ban = require('waf.ban') +local log = require('utils.log') + +local duration = config.duration +local valid_ips = config.ips +local ban_add = ban.add +local ban_check = ban.check +local headers +local meta +local client_ip = ngx.var.remote_addr + +local function answer(reason_module, level) + if not reason_module then return ngx.exit(444) end + local ray_id = string.sub(ngx.md5(ngx.time() .. client_ip), 1, 16) + ngx.header['X-WAF-Ray-ID'] = ray_id + ngx.header['X-WAF-Block-Module'] = reason_module or 'system' + ngx.header['X-WAF-Client-IP'] = client_ip + log.warn('🚨 WAF BLOCK: IP ' .. client_ip .. ' | RayID: ' .. ray_id .. ' | Модуль: ' .. (reason_module or 'unknown')) + return ngx.exit(403) +end + +local function valid(ip) + for k=1,#valid_ips do + if valid_ips[k] == ip then return end + end + return false +end + +log.name = 'waf' +log.level = 'trace' + +if valid(client_ip) then return true end + +if ban_check(client_ip) then return ngx.exit(444) end +if ngx.var.is_bot == '1' then return answer('bot_map') end + +if not detector then + log.error('Не найден детектор') +else + headers = ngx.req.get_headers() + meta = { + method = ngx.var.request_method, + uri = ngx.var.uri + } + local res,level = detector.check(headers,meta) + if not res then + ban_add(client_ip, duration/level) + if level==1 then return answer('detector') end + end +end + +if not limits then + log.error('Не найден лимитизатор') +else + local res,level = limits.check(meta,client_ip) + if not res then + ban_add(client_ip, duration/level) + if level==1 then return answer('rate_limiting') end + end +end + +if not analyzer then + log.error('Не найден анализатор') +else + local res,level = analyzer.check() + if not res then + ban_add(client_ip, duration/level) + if level==1 then return answer('uri_analyzer') end + end +end + +if not filter then + log.error('Не найден фильтр') +else + local res = filter.check() + if not res then + ban_add(client_ip, duration) + return answer('body_filter') + end +end diff --git a/index.html b/index.html new file mode 100644 index 0000000..20c30cc --- /dev/null +++ b/index.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<html lang="ru"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Доступ ограничен | Защита WAF</title> + <style> + :root { + --bg-color: #111217; + --card-color: #1c1d24; + --text-main: #f3f4f6; + --text-muted: #9ca3af; + --accent-color: #ff453a; + --code-bg: #0f1014; + } + * { box-sizing: border-box; margin: 0; padding: 0; } + body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-main); + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 20px; + } + .waf-container { + max-width: 650px; + width: 100%; + background-color: var(--card-color); + padding: 40px; + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + border-top: 4px solid var(--accent-color); + } + .header { display: flex; align-items: center; gap: 15px; margin-bottom: 24px; } + .icon { + width: 48px; + height: 48px; + background-color: rgba(255, 69, 58, 0.1); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent-color); + font-size: 24px; + font-weight: bold; + } + h1 { font-size: 24px; font-weight: 600; color: var(--text-main); } + p { font-size: 15px; line-height: 1.6; color: var(--text-muted); margin-bottom: 20px; } + .info-panel { + background-color: var(--code-bg); + border-left: 4px solid var(--accent-color); + padding: 20px; + border-radius: 4px; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 14px; + margin-bottom: 24px; + position: relative; + } + .info-row { display: flex; margin-bottom: 8px; } + .info-row:last-child { margin-bottom: 0; } + .info-label { width: 140px; color: var(--text-muted); font-weight: bold; } + .info-value { color: #30d158; word-break: break-all; } + .btn-copy { + display: inline-flex; + align-items: center; + gap: 8px; + background-color: transparent; + border: 1px solid #3a3b45; + color: var(--text-main); + padding: 10px 16px; + font-size: 14px; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + } + .btn-copy:hover { background-color: #252630; border-color: var(--text-muted); } + .footer { font-size: 12px; color: #525866; margin-top: 32px; border-top: 1px solid #282932; padding-top: 16px; text-align: center; } + </style> +</head> +<body> + +<div class="waf-container"> + <div class="header"> + <div class="icon">🛑</div> + <h1>Error 1020: Access Denied</h1> + </div> + + <p>Ваш запрос был автоматически заблокирован глобальным контуром безопасности Web Application Firewall (WAF). Система зафиксировала аномальное поведение, нарушающее установленные политики сетевой защиты.</p> + + <div class="info-panel" id="incident-data"> + <div class="info-row"> + <div class="info-label">Your IP:</div> + <!--# if expr="$sent_http_x_waf_client_ip" --> + <div class="info-value"><!--# echo var="sent_http_x_waf_client_ip" --></div> + <!--# else --> + <div class="info-value">Protected Context</div> + <!--# endif --> + </div> + <div class="info-row"> + <div class="info-label">WAF Ray ID:</div> + <!--# if expr="$sent_http_x_waf_ray_id" --> + <div class="info-value"><!--# echo var="sent_http_x_waf_ray_id" --></div> + <!--# else --> + <div class="info-value">N/A</div> + <!--# endif --> + </div> + <div class="info-row"> + <div class="info-label">Block Reason:</div> + <!--# if expr="$sent_http_x_waf_block_module" --> + <div class="info-value"><!--# echo var="sent_http_x_waf_block_module" --></div> + <!--# else --> + <div class="info-value">security_rule_match</div> + <!--# endif --> + </div> + </div> + + <button class="btn-copy" onclick="copyIncidentData()">📋 Скопировать данные инцидента</button> + + <p class="footer">Если вы считаете, что блокировка произошла ошибочно, пожалуйста, скопируйте данные инцидента и передайте их системному администратору вашей экосистемы для внесения в белый список.</p> +</div> + +<script> +function copyIncidentData() { + const text = document.getElementById('incident-data').innerText; + navigator.clipboard.writeText(text).then(() => { + const btn = document.querySelector('.btn-copy'); + const oldText = btn.innerHTML; + btn.innerHTML = '✅ Данные скопированы!'; + setTimeout(() => { btn.innerHTML = oldText; }, 2000); + }).catch(err => { + alert('Не удалось скопировать. Пожалуйста, выделите текст вручную.'); + }); +} +</script> +</body> +</html> |
