111 lines
5.6 KiB
Go
111 lines
5.6 KiB
Go
// 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)
|
||
}
|