burterm/internal/scanner/finding.go
2026-09-14 10:55:07 +03:00

111 lines
5.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package scanner реализует активные и пассивные проверки на типовые
// классы уязвимостей (аналог Scanner в Burp): SQLi, XSS, SSTI, LFI,
// RCE (командная инъекция), SSRF, XXE, CORS-мисконфигурация, JWT.
//
// Активные проверки (Check) переиспользуют ту же инфраструктуру
// маркеров §...§ и engine.BuildRequest, что и Intruder — по сути,
// Scanner это Intruder с фиксированным набором payload'ов на класс
// уязвимости и анализом ответа вместо простого прогона.
//
// Честная оговорка про качество детекции, важная для трактовки
// результатов:
// - SQLi/LFI/SSTI/XXE — детект по сигнатурам в теле ответа плюс
// time-based эвристика для слепых случаев. Ложноположительные
// срабатывания возможны, особенно на time-based (просто медленный
// сервер даст такой же эффект) — результаты Scanner это наводки
// для ручной проверки, не готовый вердикт.
// - SSRF — без out-of-band листенера (как Burp Collaborator) реальный
// SSRF в общем случае не подтвердить; здесь только best-effort
// проверка через облачные метадата-эндпоинты и file://, которые
// иногда отражаются прямо в ответе.
// - CORS/JWT — не задействуют Probes()/Analyze(), это отдельные
// одноразовые проверки (CheckCORS, DecodeJWT) с другой формой
// входных данных, поэтому не реализуют интерфейс Check.
package scanner
import (
"net/http"
"time"
)
// Severity — грубая оценка серьёзности находки. Не заменяет CVSS и
// подобное — это просто сортировка "на что смотреть в первую очередь".
type Severity int
const (
SeverityInfo Severity = iota
SeverityLow
SeverityMedium
SeverityHigh
)
func (s Severity) String() string {
switch s {
case SeverityInfo:
return "INFO"
case SeverityLow:
return "LOW"
case SeverityMedium:
return "MEDIUM"
case SeverityHigh:
return "HIGH"
default:
return "?"
}
}
// Finding — одна находка проверки.
type Finding struct {
Check string
Severity Severity
Detail string
Payload string // что было отправлено (пусто для CORS/JWT — там нет единого "payload")
Evidence string // фрагмент ответа/заголовка, подтверждающий находку
}
// ProbeResult — минимальный набор данных об ответе, нужный проверкам.
// Отделён от engine.Result по тому же принципу, что sitemap.Record
// отделён от proxy.Entry — scanner не должен тянуть за собой знание
// о внутреннем устройстве Intruder-движка сверх того, что реально нужно.
type ProbeResult struct {
StatusCode int
Body string
Headers http.Header
Duration time.Duration
Err error
}
// Check — активная проверка одного класса уязвимости через подстановку
// payload'ов в одну точку вставки (маркер).
type Check interface {
// Name — человекочитаемое имя, показывается в TUI и в Finding.Check.
Name() string
// Probes — payload'ы, которые по очереди подставляются в маркер.
Probes() []string
// Analyze решает, есть ли находка, сравнивая ответ на probe с
// baseline (ответ на нейтральное/исходное значение той же точки).
// Возвращает nil, если находки нет.
Analyze(probe string, resp, baseline ProbeResult) *Finding
}
// EventKind различает два вида сообщений в потоке Scanner.Run: тик
// прогресса (ещё один пробник отправлен) и собственно находку.
type EventKind int
const (
EventProgress EventKind = iota
EventFinding
)
// Event — одно сообщение из канала Scanner.Run. Kind говорит, какое из
// полей заполнено: Finding (при EventFinding) или Done/Total (при
// EventProgress). Объединение прогресса и находок в один канал/тип —
// осознанное решение: TUI всё равно должен читать их последовательно
// из одного потока, заводить два отдельных канала ради этого не давало
// бы ничего, кроме сложности синхронизации между ними.
type Event struct {
Kind EventKind
Finding Finding // валидно при Kind == EventFinding
Done int // валидно при Kind == EventProgress — сколько пробников уже отправлено
Total int // общее число пробников в этом прогоне (включая baseline)
}