@kdinisv/sql-scanner
Version:
Smart SQL injection scanner with crawler and optional Playwright capture.
487 lines (390 loc) • 18.5 kB
Markdown
# @kdinisv/sql-scanner
Лёгкий SDK для поиска SQL-инъекций в Node.js. Умеет точечно сканировать URL и выполнять «умное» сканирование с краулингом. Работает в ESM и CommonJS, типы включены. Поддерживает отчёты JSON/Markdown/CSV/JUnit и приоритизирует payload’ы (в т.ч. time/union PoC) по отпечатку СУБД.
— Node.js >= 18.17
— Типы: TypeScript
– Опционально: Playwright для JS-страниц (захват сетевых запросов SPA)
## Установка
```bash
npm i @kdinisv/sql-scanner
# (опционально) для SPA-страниц
npm i -D playwright
```
## Быстрый старт (ESM)
```ts
import { SqlScanner } from "@kdinisv/sql-scanner";
const scanner = new SqlScanner();
const result = await scanner.scan({
target: "https://example.com/search?q=test",
method: "GET",
enable: { query: true, error: true, boolean: true, time: false },
});
console.log(result.vulnerable, result.details);
```
## Быстрый старт (CommonJS)
```js
const { SqlScanner } = require("@kdinisv/sql-scanner");
(async () => {
const scanner = new SqlScanner();
const result = await scanner.scan({ target: "https://example.com?id=1" });
console.log(result);
})();
```
## «Умное» сканирование (краулер)
```ts
const smart = await scanner.smartScan({
baseUrl: "https://example.com",
maxDepth: 2,
maxPages: 50,
usePlaywright: true,
});
console.log(smart.crawledPages, smart.candidates.length, smart.sqli.length);
```
## Краткое API
- new SqlScanner(options?)
- requestTimeoutMs?: number (по умолчанию 10000)
- timeThresholdMs?: number для time-based (по умолчанию 2500)
- headers?: Record<string,string>, cookies?: Record<string,string>
- scan(input)
- target: string, method?: "GET"|"POST"
- jsonBody?: Record<string,unknown>
- enable?: { query|path|form|json|header|cookie|error|boolean|time|union?: boolean }
- payloads?: { error?: string[]; boolean?: { true:string; false:string; label?:string }[]; time?: { p:string; label?:string }[] }
- onProgress?: (p: ScanProgress) => void
- Возвращает: { vulnerable: boolean; details: Detail[] }
- smartScan(options)
- baseUrl: string, maxDepth?: number, maxPages?: number
- sameOriginOnly?: boolean, usePlaywright?: boolean
- crawlConcurrency?: number — параллельность HTML-краулинга (по умолчанию 4)
- playwrightConcurrency?: number — число одновременных страниц Playwright (по умолчанию 2)
- playwrightWaitMs?: number — ожидание XHR/fetch после загрузки, мс (по умолчанию 1000)
- scanParallel?: number — параллельное сканирование кандидатов (по умолчанию 2)
- techniques?: { error?: boolean; boolean?: boolean; time?: boolean }
- onProgress?: (p: SmartScanProgress) => void
- Возвращает: { crawledPages: number; candidates: DiscoveredTarget[]; sqli: ResultShape[] }
## Возвращаемые данные
### scan(input) → ResultShape
- vulnerable: boolean — есть ли подтвержденные уязвимости среди проверок
- details: Detail[] — записи по каждому проверенному «поинту/технике»
- point: { kind: "query"|"path"|"form"|"json"|"header"|"cookie"; name: string; meta?: object }
- technique: "error" | "boolean_truefalse" | "time"
- payload: string — инъекция, использованная в проверке
- vulnerable: boolean — результат конкретной проверки
- responseMeta?: { status: number; elapsedMs?: number; len?: number; location?: string }
- evidence?: string — краткая улика (фрагмент ошибки/метрика)
- confirmations?: string[] — ярлыки подтверждений (например, "error_signature", а при error-based может добавляться отпечаток СУБД: "mysql"|"postgres"|"mssql"|"oracle"|"sqlite")
- reproduce?: { curl: string[]; note?: string } — готовые примеры для воспроизведения (curl)
- remediation?: string[] — краткие рекомендации по исправлению
Пример:
```json
{
"vulnerable": true,
"details": [
{
"point": { "kind": "query", "name": "q" },
"technique": "error",
"payload": "' OR '1'='1",
"vulnerable": true,
"responseMeta": { "status": 200, "len": 12345, "elapsedMs": 120 },
"evidence": "You have an error in your SQL syntax",
"confirmations": ["error_signature"],
"reproduce": {
"curl": ["curl \"https://example.com/search?q=' OR '1'='1\""]
},
"remediation": [
"Используйте параметризованные запросы/Prepared Statements",
"Не конкатенируйте пользовательский ввод в SQL"
]
}
]
}
```
Совет: фильтруйте `details.filter(d => d.vulnerable)` для списка подтвержденных находок.
### smartScan(options) → SmartScanResult
- crawledPages: number — сколько страниц обработано краулером
- candidates: DiscoveredTarget[] — найденные цели для сканирования
- { kind: "url-with-query"; url: string }
- { kind: "form"; action: string; method: "GET"|"POST"; enctype?: string; fields: { name: string; value: string }[] }
- { kind: "json-endpoint"; url: string; method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE"; body?: any; headers?: Record<string,string> }
- sqli: ResultShape[] — результаты сканирования для каждой из целей
Пример:
```json
{
"crawledPages": 12,
"candidates": [
{ "kind": "url-with-query", "url": "https://site/search?q=" },
{
"kind": "form",
"action": "https://site/login",
"method": "POST",
"fields": [{ "name": "email", "value": "" }]
}
],
"sqli": [
{
"vulnerable": false,
"details": [
{
"point": { "kind": "query", "name": "q" },
"technique": "boolean_truefalse",
"payload": "...",
"vulnerable": false
}
]
}
]
}
```
## Тонкая настройка
- Кастомные payload’ы для scan
```ts
await scanner.scan({
target: "http://127.0.0.1:3000/rest/products/search?q=",
enable: { query: true, error: true, boolean: true, time: false },
payloads: {
error: ["'", "' OR 1=1--", "' UNION SELECT 1--"],
boolean: [{ true: "' OR 1=1--", false: "' OR 1=2--", label: "or_comment" }],
},
});
```
– Предварительная авторизация (форма/JSON)
```ts
await scanner.scan({
target: "https://site.local/products?id=1",
method: "GET",
auth: {
url: "https://site.local/api/login",
method: "POST",
type: "form-urlencoded", // или "json"
usernameField: "username",
passwordField: "password",
username: "admin",
password: "secret",
additionalFields: { remember: "1" },
verifyUrl: "https://site.local/",
success: { notContainsText: "Sign in" },
},
enable: { query: true, error: true, boolean: true },
});
```
Аналогично в smartScan:
```ts
await scanner.smartScan({
baseUrl: "https://site.local",
auth: {
url: "https://site.local/api/login",
method: "POST",
type: "json",
usernameField: "email",
passwordField: "password",
username: "admin@site.local",
password: "secret",
verifyUrl: "https://site.local/",
success: { notContainsText: "Sign in" },
},
});
```
- Управление техниками в smartScan
```ts
await scanner.smartScan({
baseUrl: "https://example.com",
maxDepth: 2,
maxPages: 50,
techniques: { error: true, boolean: true, time: false },
});
```
## Прогресс и ETA
- Прогресс для точечного сканирования
```ts
await scanner.scan({
target: "https://example.com/search?q=1",
enable: { query: true, error: true, boolean: true, time: false },
onProgress: (p) => {
if (p.phase === "discover") {
console.log(`points=${p.points}`);
} else if (p.phase === "scan") {
console.log(
`checks ${p.processedChecks}/${p.plannedChecks}, eta=${p.etaMs}ms`
);
}
},
});
```
- Прогресс для умного сканирования (краулинг + скан)
```ts
await scanner.smartScan({
baseUrl: "https://example.com",
onProgress: (p) => {
if (p.phase === "crawl") {
console.log(`crawled ${p.crawledPages}/${p.maxPages}`);
} else if (p.phase === "scan") {
console.log(
`scanned ${p.scanProcessed}/${p.scanTotal}, eta=${p.etaMs}ms`
);
}
},
});
```
## CLI (опционально)
Запуск без установки:
```bash
npx --package @kdinisv/sql-scanner sql-scan https://example.com
```
Глобально:
```bash
npm i -g @kdinisv/sql-scanner
sql-scan https://example.com
# отключить захват JS/SPA (без Playwright)
sql-scan https://example.com --no-js
# сохранить отчёт (Markdown/JSON/CSV/JUnit)
sql-scan https://example.com --report md --out report.md
sql-scan https://example.com --report json --out report.json
sql-scan https://example.com --report csv --out report.csv
sql-scan https://example.com --report junit --out report.xml
```
CLI показывает индикатор прогресса и оценку ETA в процессе.
### .env-конфиг
CLI поддерживает конфигурацию через .env-файл (dotenv). Создайте `.env` в корне проекта (см. `.env.example`). Основные переменные:
- SQL_SCANNER_REQUEST_TIMEOUT_MS, SQL_SCANNER_TIME_THRESHOLD_MS
- SQL_SCANNER_PARALLEL, SQL_SCANNER_MAX_REQUESTS
- SQL_SCANNER_USE_JS, SQL_SCANNER_MAX_DEPTH, SQL_SCANNER_MAX_PAGES, SQL_SCANNER_SAME_ORIGIN_ONLY
- SQL_SCANNER_PLAYWRIGHT_HEADLESS (true|false). В CLI есть флаг `--headed` (делает headless=false)
- SQL_SCANNER_TECHNIQUES=error,boolean,time
- SQL_SCANNER_HEADERS / SQL_SCANNER_HEADERS_JSON (JSON строка)
- SQL_SCANNER_COOKIES / SQL_SCANNER_COOKIES_JSON (JSON строка)
- SQL_SCANNER_AUTH_JSON (JSON строка с параметрами авторизации)
Переменные окружения прокси HTTP_PROXY/HTTPS_PROXY/NO_PROXY также учитываются.
## Тестовые эмуляторы СУБД (для разработки)
В репозитории есть лёгкие локальные HTTP-эмуляторы баз данных, которые имитируют поведение MySQL/PostgreSQL/MSSQL/Oracle/SQLite для техник:
- error-based — возвращают характерные сигнатуры ошибок СУБД;
- boolean-based — различающиеся ответы для «true/false» инъекций;
- time-based — искусственная задержка при SLEEP/pg_sleep/WAITFOR/DBMS_LOCK.SLEEP;
- union-based PoC — различия в ответах для ORDER BY ok/bad и UNION SELECT.
Они используются в автотестах и не требуют внешних контейнеров/стендов:
- Код эмуляторов: `tests/servers/dbEmulators.ts`
- Тест: `tests/db-emulator.test.ts`
Запуск тестов:
```bash
npm test -s
```
## Сеть и прокси
- Поддерживаются переменные окружения прокси:
- HTTP_PROXY / HTTPS_PROXY — адрес прокси, например: `http://127.0.0.1:3128` или с авторизацией `http://user:pass@proxy.local:8080`.
- NO_PROXY — список исключений через запятую (если задан системно, будет учтён на стороне среды).
- Для транзиентных ошибок на GET (502/503/504, сетевые таймауты) вшиты короткие ретраи с экспоненциальным backoff.
— Приоритет пейлоадов: если error-based детект дал отпечаток СУБД (MySQL/Postgres/MSSQL/Oracle/SQLite), time-based подбор сначала пробует соответствующие пейлоады (например, pg_sleep/WAITFOR/DBMS_LOCK.SLEEP), что ускоряет и повышает точность.
— Union-based PoC: реализованы безопасные пробы ORDER BY (сравнение ответов на валидный/заведомо «лишний» индекс) и базовые UNION SELECT пейлоады. При наличии отпечатка СУБД выбираются наиболее подходящие варианты.
## Дополнительные примеры
### 1) Сканирование JSON API (POST)
```ts
const result = await scanner.scan({
target: "https://api.site.local/search",
method: "POST",
jsonBody: { q: "test", page: 1 },
enable: { json: true, error: true, boolean: true, time: false },
});
```
### 2) Сканирование формы
```ts
// target указывает на страницу с формой; сканер сам получит HTML и извлечёт поля
const res = await scanner.scan({
target: "https://site.local/login",
enable: { form: true, error: true, boolean: true, time: false, query: false },
});
```
### 3) Заголовки и куки (и инъекции в них)
```ts
const res = await scanner.scan({
target: "https://site.local/profile?id=1",
headers: { "X-Trace": "abc" },
cookies: { session: "token" },
enable: { header: true, cookie: true, error: true, boolean: true },
});
```
### 4) Time-based проверки
```ts
const res = await scanner.scan({
target: "https://site.local/search?q=1",
enable: { query: true, time: true },
// Поднимите порог, если бэкенд медленный
timeThresholdMs: 3000,
});
```
— Встроено статистическое подтверждение (p-value) для time-based: несколько парных замеров baseline/injected, расчёт p (односторонний тест). В evidence попадают p, z, средние времена. Для шумных систем можно повысить timeThresholdMs.
### 5) smartScan с/без JS
```ts
// Без JS (быстрее, только HTML)
await scanner.smartScan({
baseUrl: "https://site.local",
usePlaywright: false,
crawlConcurrency: 6, // быстрее HTML-краулинг
});
// С JS (если установлен Playwright): захватывает запросы SPA
await scanner.smartScan({
baseUrl: "https://site.local",
usePlaywright: true,
playwrightMaxPages: 4, // 0 или отрицательное — без ограничения
playwrightConcurrency: 3, // несколько страниц параллельно
playwrightWaitMs: 1200, // чуть дольше ждём XHR
scanParallel: 3, // параллельно сканируем кандидатов
});
```
### 6) Постобработка результатов
```ts
const onlyVuln = result.details.filter((d) => d.vulnerable);
const byTechnique = onlyVuln.reduce(
(acc, d) => {
acc[d.technique] = (acc[d.technique] || 0) + 1;
return acc;
},
/** @type {Record<string, number>} */ {}
);
```
— Готовые репорты: JSON/Markdown/CSV/JUnit
```ts
import {
toJsonReport,
toMarkdownReport,
toCsvReport,
toJUnitReport,
} from "@kdinisv/sql-scanner";
const json = toJsonReport(result);
const md = toMarkdownReport(result);
const csv = toCsvReport(result);
const junitXml = toJUnitReport(result);
// Сохраните в файл или отправьте в CI-артефакты
```
В отчётах теперь присутствуют примеры воспроизведения и советы по исправлению:
- Markdown: внутри каждого найденного пункта добавляется раздел `reproduce` с `curl ...`, а также `remediation` — список рекомендаций.
- CSV: дополнительные колонки `reproduce_curl` и `remediation`.
- JUnit: в `<failure>` попадает тело с блоками `curl:` и `fix:`.
### 7) Управление нагрузкой
```ts
const scanner = new SqlScanner({ parallel: 4, maxRequests: 500 });
const res = await scanner.scan({ target: "https://site.local/?q=1" });
// Для smartScan скорость можно настраивать:
await scanner.smartScan({
baseUrl: "https://site.local",
crawlConcurrency: 4,
playwrightConcurrency: 2,
scanParallel: 2,
});
```
### 8) UNION/ORDER BY (PoC)
```ts
// Экспериментальная техника: сначала ORDER BY (ok vs bad), затем UNION
const res = await scanner.scan({
target: "http://127.0.0.1:3000/search?q=1",
enable: {
query: true,
union: true,
error: false,
boolean: false,
time: false,
},
});
const unionFindings = res.details.filter((d) => d.technique === "union");
// evidence включает orderby-sim=... и/или sim(base,union)=...
```
Примечание: это PoC с консервативными, безопасными пейлоадами. В бою комбинируйте с error/boolean/time для повышения уверенности.
## Важно
Используйте сканер только на ресурсах, для которых у вас есть разрешение.