icarus-cmd 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { normalize } from '../core/severity.js';
4
+
5
+ export default {
6
+ id: 'dalfox',
7
+ name: 'dalfox',
8
+ category: 'web',
9
+ description: "Scanner d'injection XSS",
10
+ needs: 'url',
11
+ needsWorkdir: true,
12
+ bins: ['dalfox'],
13
+ winPaths: [],
14
+ install: 'go install github.com/hahwul/dalfox/v2@latest',
15
+
16
+ command(target, opts = {}) {
17
+ const out = join(opts.workdir, 'dalfox.json');
18
+ return { args: ['url', target, '--format', 'json', '-o', out, '--silence', '--no-spinner'] };
19
+ },
20
+
21
+ parse({ workdir, target }) {
22
+ const out = join(workdir || '', 'dalfox.json');
23
+ if (!workdir || !existsSync(out)) return [];
24
+ const raw = readFileSync(out, 'utf8').trim();
25
+ if (!raw) return [];
26
+
27
+ let items = [];
28
+ try {
29
+ items = JSON.parse(raw); // tableau de PoCs
30
+ } catch {
31
+ // certains builds écrivent du JSONL
32
+ items = raw.split(/\r?\n/).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
33
+ }
34
+ return [].concat(items).map((p) => ({
35
+ type: 'xss',
36
+ severity: normalize(p.severity) === 'unknown' ? 'high' : normalize(p.severity),
37
+ title: `XSS (${p.type || 'reflected'}) — ${p.param || ''}`.trim(),
38
+ detail: p.data || p.evidence || target,
39
+ ref: 'dalfox',
40
+ }));
41
+ },
42
+ };
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const DEFAULT_WL = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'wordlists', 'common.txt');
6
+
7
+ export default {
8
+ id: 'ffuf',
9
+ name: 'ffuf',
10
+ category: 'web',
11
+ description: 'Fuzzing web (dossiers/fichiers cachés)',
12
+ needs: 'url',
13
+ needsWorkdir: true,
14
+ bins: ['ffuf'],
15
+ winPaths: [],
16
+ install: 'go install github.com/ffuf/ffuf/v2@latest',
17
+
18
+ options: [
19
+ { key: 'wordlist', label: 'Wordlist (chemin)', type: 'text', placeholder: 'vide = wordlist intégrée' },
20
+ ],
21
+
22
+ command(target, opts = {}) {
23
+ let url = target;
24
+ if (!/FUZZ/.test(url)) url = url.replace(/\/?$/, '/') + 'FUZZ';
25
+ const wl = opts.wordlist && existsSync(opts.wordlist) ? opts.wordlist : DEFAULT_WL;
26
+ const out = join(opts.workdir, 'ffuf.json');
27
+ return {
28
+ args: ['-u', url, '-w', wl, '-of', 'json', '-o', out, '-mc', '200,204,301,302,307,401,403', '-s'],
29
+ };
30
+ },
31
+
32
+ parse({ workdir }) {
33
+ const out = join(workdir || '', 'ffuf.json');
34
+ if (!workdir || !existsSync(out)) return [];
35
+ let data;
36
+ try { data = JSON.parse(readFileSync(out, 'utf8')); } catch { return []; }
37
+ return (data.results || []).map((r) => ({
38
+ type: 'content',
39
+ severity: r.status === 403 ? 'low' : 'info',
40
+ title: `[${r.status}] ${r.url}`,
41
+ detail: `taille:${r.length} mots:${r.words}`,
42
+ ref: 'ffuf',
43
+ }));
44
+ },
45
+ };
@@ -0,0 +1,44 @@
1
+ // gobuster : brute-force de dossiers/fichiers web. Parsing de la sortie texte.
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const DEFAULT_WL = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'wordlists', 'common.txt');
8
+
9
+ export default {
10
+ id: 'gobuster',
11
+ name: 'gobuster',
12
+ category: 'web',
13
+ description: 'Brute-force de dossiers/fichiers web',
14
+ needs: 'url',
15
+ bins: ['gobuster'],
16
+ winPaths: [],
17
+ install: 'go install github.com/OJ/gobuster/v3@latest',
18
+
19
+ options: [
20
+ { key: 'wordlist', label: 'Wordlist (chemin)', type: 'text', placeholder: 'vide = wordlist intégrée' },
21
+ ],
22
+
23
+ command(target, opts = {}) {
24
+ const wl = opts.wordlist && existsSync(opts.wordlist) ? opts.wordlist : DEFAULT_WL;
25
+ return { args: ['dir', '-u', target, '-w', wl, '-q', '--no-error'] };
26
+ },
27
+
28
+ parse({ stdout }) {
29
+ const findings = [];
30
+ // Lignes type: /admin (Status: 301) [Size: 0]
31
+ const re = /^(\/\S*)\s+\(Status:\s*(\d+)\)(?:\s*\[Size:\s*(\d+)\])?/gim;
32
+ let m;
33
+ while ((m = re.exec(stdout)) !== null) {
34
+ findings.push({
35
+ type: 'content',
36
+ severity: m[2] === '403' ? 'low' : 'info',
37
+ title: `[${m[2]}] ${m[1]}`,
38
+ detail: m[3] ? `taille:${m[3]}` : '',
39
+ ref: 'gobuster',
40
+ });
41
+ }
42
+ return findings;
43
+ },
44
+ };
@@ -0,0 +1,48 @@
1
+ // Collision de nom avec la lib Python `httpx` : verify() valide le bon binaire.
2
+ import { execFileSync } from 'node:child_process';
3
+ import { RESOLVERS_FILE } from '../core/resolvers.js';
4
+
5
+ export default {
6
+ verify(bin) {
7
+ try {
8
+ execFileSync(bin, ['-version'], { stdio: 'ignore', timeout: 8000 });
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ },
14
+
15
+ id: 'httpx',
16
+ name: 'httpx',
17
+ category: 'recon',
18
+ description: 'Sonde HTTP (statut, titre, techno)',
19
+ needs: 'url',
20
+ bins: ['httpx'],
21
+ winPaths: [],
22
+ install: 'go install github.com/projectdiscovery/httpx/cmd/httpx@latest',
23
+
24
+ command(target) {
25
+ const args = ['-u', target, '-json', '-silent', '-no-color', '-title', '-tech-detect', '-status-code', '-web-server'];
26
+ if (RESOLVERS_FILE) args.push('-resolvers', RESOLVERS_FILE);
27
+ return { args };
28
+ },
29
+
30
+ parse({ stdout }) {
31
+ const findings = [];
32
+ for (const line of stdout.split(/\r?\n/)) {
33
+ const t = line.trim();
34
+ if (!t.startsWith('{')) continue;
35
+ let j;
36
+ try { j = JSON.parse(t); } catch { continue; }
37
+ const tech = [].concat(j.tech || j.technologies || []).join(', ');
38
+ findings.push({
39
+ type: 'http',
40
+ severity: 'info',
41
+ title: `${j.status_code ?? '?'} ${j.url || j.input} ${j.title ? `— ${j.title}` : ''}`.trim(),
42
+ detail: [j.webserver, tech].filter(Boolean).join(' | '),
43
+ ref: 'httpx',
44
+ });
45
+ }
46
+ return findings;
47
+ },
48
+ };
@@ -0,0 +1,45 @@
1
+ import { RESOLVERS_FILE } from '../core/resolvers.js';
2
+
3
+ export default {
4
+ id: 'katana',
5
+ name: 'katana',
6
+ category: 'recon',
7
+ description: 'Crawler web (cartographie des endpoints)',
8
+ needs: 'url',
9
+ bins: ['katana'],
10
+ winPaths: [],
11
+ install: 'go install github.com/projectdiscovery/katana/cmd/katana@latest',
12
+
13
+ options: [
14
+ { key: 'depth', label: 'Profondeur de crawl', type: 'number', default: 2, min: 1, max: 5 },
15
+ ],
16
+
17
+ command(target, opts = {}) {
18
+ const args = ['-u', target, '-jsonl', '-silent', '-nc'];
19
+ if (RESOLVERS_FILE) args.push('-resolvers', RESOLVERS_FILE);
20
+ if (opts.depth) args.push('-depth', String(opts.depth));
21
+ return { args };
22
+ },
23
+
24
+ parse({ stdout }) {
25
+ const findings = [];
26
+ const seen = new Set();
27
+ for (const line of stdout.split(/\r?\n/)) {
28
+ const t = line.trim();
29
+ if (!t.startsWith('{')) continue;
30
+ let j;
31
+ try { j = JSON.parse(t); } catch { continue; }
32
+ const url = j.request?.endpoint || j.request?.url || j.endpoint || j.url;
33
+ if (!url || seen.has(url)) continue;
34
+ seen.add(url);
35
+ findings.push({
36
+ type: 'endpoint',
37
+ severity: 'info',
38
+ title: url,
39
+ detail: j.request?.method || '',
40
+ ref: 'katana',
41
+ });
42
+ }
43
+ return findings;
44
+ },
45
+ };
@@ -0,0 +1,59 @@
1
+ // Sur Windows msfconsole est un .bat -> lancement via `cmd /c`.
2
+ export default {
3
+ id: 'metasploit',
4
+ name: 'Metasploit',
5
+ category: 'vuln',
6
+ description: 'Framework d\'exploitation (modules auxiliaires/scanners)',
7
+ needs: 'host',
8
+ bins: ['msfconsole', 'msfconsole.bat'],
9
+ winPaths: [
10
+ 'C:/metasploit-framework/bin/msfconsole.bat',
11
+ 'C:/Program Files/Metasploit/msfconsole.bat',
12
+ ],
13
+ install: 'Installeur Windows : https://windows.metasploit.com/ (≈1 Go, droits admin requis)',
14
+
15
+ options: [
16
+ { key: 'module', label: 'Module auxiliaire', type: 'select', default: 'auxiliary/scanner/portscan/tcp', choices: [
17
+ { value: 'auxiliary/scanner/portscan/tcp', label: 'Scan de ports TCP' },
18
+ { value: 'auxiliary/scanner/smb/smb_version', label: 'SMB — version' },
19
+ { value: 'auxiliary/scanner/ssh/ssh_version', label: 'SSH — version' },
20
+ { value: 'auxiliary/scanner/http/http_version', label: 'HTTP — version' },
21
+ { value: 'auxiliary/scanner/ftp/ftp_version', label: 'FTP — version' },
22
+ ] },
23
+ { key: 'ports', label: 'Ports (module portscan)', type: 'text', placeholder: 'ex: 1-1000' },
24
+ ],
25
+
26
+ command(target, opts = {}) {
27
+ const host = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
28
+ const mod = opts.module || 'auxiliary/scanner/portscan/tcp';
29
+ const lines = [`use ${mod}`, `set RHOSTS ${host}`];
30
+ if (opts.ports) lines.push(`set PORTS ${opts.ports}`);
31
+ lines.push('run', 'exit -y');
32
+ const script = lines.join('; ');
33
+
34
+ const bin = opts.bin;
35
+ if (process.platform === 'win32') {
36
+ return { cmd: 'cmd', args: ['/c', bin, '-q', '-x', script] };
37
+ }
38
+ return { cmd: bin, args: ['-q', '-x', script] };
39
+ },
40
+
41
+ parse({ stdout, target }) {
42
+ const host = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
43
+ const findings = [];
44
+ for (const line of stdout.split(/\r?\n/)) {
45
+ const t = line.trim();
46
+ // [+] = découverte positive, [*] suivi d'infos pertinentes
47
+ const m = t.match(/^\[\+\]\s*(.+)$/);
48
+ if (m) {
49
+ findings.push({ type: 'msf', severity: 'medium', title: m[1].slice(0, 160), detail: host, ref: 'metasploit' });
50
+ continue;
51
+ }
52
+ const open = t.match(/(\d{1,5})\s*-?\s*(TCP OPEN|open)/i);
53
+ if (open) {
54
+ findings.push({ type: 'port', severity: 'info', title: `Port ${open[1]} open (msf)`, detail: host, ref: 'metasploit' });
55
+ }
56
+ }
57
+ return findings;
58
+ },
59
+ };
@@ -0,0 +1,59 @@
1
+ import { existsSync } from 'node:fs';
2
+
3
+ export default {
4
+ id: 'naabu',
5
+ name: 'naabu',
6
+ category: 'recon',
7
+ description: 'Scan de ports rapide',
8
+ needs: 'host',
9
+ bins: ['naabu'],
10
+ winPaths: [],
11
+ install: 'go install github.com/projectdiscovery/naabu/v2/cmd/naabu@latest',
12
+
13
+ // Sous Windows, naabu dépend de Npcap (wpcap.dll). Sans elle, le binaire
14
+ // plante au lancement (spawn UNKNOWN). On le considère alors indisponible
15
+ // pour qu'Icarus l'ignore proprement (nmap couvre déjà le scan de ports).
16
+ verify() {
17
+ if (process.platform !== 'win32') return true;
18
+ return [
19
+ 'C:/Windows/System32/Npcap/wpcap.dll',
20
+ 'C:/Windows/SysWOW64/Npcap/wpcap.dll',
21
+ 'C:/Windows/System32/wpcap.dll',
22
+ ].some((p) => existsSync(p));
23
+ },
24
+
25
+ options: [
26
+ { key: 'topPorts', label: 'Top ports', type: 'select', default: '100', choices: [
27
+ { value: '100', label: '100' }, { value: '1000', label: '1000' }, { value: 'full', label: 'Tous (65535)' },
28
+ ] },
29
+ ],
30
+
31
+ command(target, opts = {}) {
32
+ const host = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
33
+ const args = ['-host', host, '-json', '-silent'];
34
+ if (opts.topPorts === 'full') args.push('-p', '-');
35
+ else if (opts.topPorts) args.push('-top-ports', String(opts.topPorts));
36
+ return { args };
37
+ },
38
+
39
+ parse({ stdout }) {
40
+ const findings = [];
41
+ for (const line of stdout.split(/\r?\n/)) {
42
+ const t = line.trim();
43
+ if (!t.startsWith('{')) continue;
44
+ let j;
45
+ try { j = JSON.parse(t); } catch { continue; }
46
+ const port = j.port?.Port ?? j.port;
47
+ const host = j.host || j.ip;
48
+ if (port == null) continue;
49
+ findings.push({
50
+ type: 'port',
51
+ severity: 'info',
52
+ title: `Port ${port}/tcp open`,
53
+ detail: host || '',
54
+ ref: `${host || ''}:${port}`,
55
+ });
56
+ }
57
+ return findings;
58
+ },
59
+ };
@@ -0,0 +1,34 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ export default {
5
+ id: 'nikto',
6
+ name: 'Nikto',
7
+ category: 'web',
8
+ description: 'Scanner de serveur web (misconfig, CVE)',
9
+ needs: 'url',
10
+ needsWorkdir: true,
11
+ bins: ['nikto', 'nikto.pl'],
12
+ winPaths: [],
13
+ install: 'https://github.com/sullo/nikto (nécessite Perl)',
14
+
15
+ command(target, opts = {}) {
16
+ const out = join(opts.workdir, 'nikto.json');
17
+ return { args: ['-h', target, '-Format', 'json', '-output', out, '-nointeractive', '-ask', 'no'] };
18
+ },
19
+
20
+ parse({ workdir, target }) {
21
+ const out = join(workdir || '', 'nikto.json');
22
+ if (!workdir || !existsSync(out)) return [];
23
+ let data;
24
+ try { data = JSON.parse(readFileSync(out, 'utf8')); } catch { return []; }
25
+ const vulns = data.vulnerabilities || data[0]?.vulnerabilities || [];
26
+ return vulns.map((v) => ({
27
+ type: 'web',
28
+ severity: 'medium',
29
+ title: v.msg || v.id || 'finding nikto',
30
+ detail: v.url || target,
31
+ ref: v.id ? `nikto:${v.id}` : 'nikto',
32
+ }));
33
+ },
34
+ };
@@ -0,0 +1,96 @@
1
+ const RISKY = {
2
+ 21: ['low', 'FTP exposé (souvent en clair)'],
3
+ 23: ['medium', 'Telnet exposé (trafic en clair)'],
4
+ 135: ['low', 'MSRPC exposé'],
5
+ 139: ['low', 'NetBIOS exposé'],
6
+ 445: ['medium', 'SMB exposé (surface RCE classique)'],
7
+ 1433: ['low', 'MS-SQL exposé'],
8
+ 3306: ['low', 'MySQL exposé'],
9
+ 3389: ['medium', 'RDP exposé (cible bruteforce/CVE)'],
10
+ 5432: ['low', 'PostgreSQL exposé'],
11
+ 5900: ['medium', 'VNC exposé'],
12
+ 6379: ['high', 'Redis exposé (souvent sans auth)'],
13
+ 9200: ['medium', 'Elasticsearch exposé'],
14
+ 27017: ['high', 'MongoDB exposé (souvent sans auth)'],
15
+ };
16
+
17
+ export default {
18
+ id: 'nmap',
19
+ name: 'Nmap',
20
+ category: 'recon',
21
+ description: 'Scan de ports & détection de services',
22
+ needs: 'host',
23
+ bins: ['nmap'],
24
+ winPaths: [
25
+ 'C:/Program Files (x86)/Nmap/nmap.exe',
26
+ 'C:/Program Files/Nmap/nmap.exe',
27
+ ],
28
+ install: 'winget install Insecure.Nmap (ou https://nmap.org/download.html)',
29
+
30
+ options: [
31
+ { key: 'timing', label: 'Mode (vitesse)', type: 'select', default: '4', choices: [
32
+ { value: '0', label: 'T0 — paranoïaque (furtif)' },
33
+ { value: '2', label: 'T2 — discret' },
34
+ { value: '4', label: 'T4 — agressif (défaut)' },
35
+ { value: '5', label: 'T5 — insane (rapide/bruyant)' },
36
+ ] },
37
+ { key: 'ports', label: 'Ports', type: 'text', placeholder: '80,443 ou 1-1000 (vide = top 1000)' },
38
+ { key: 'fast', label: 'Scan rapide (-F, 100 ports)', type: 'bool' },
39
+ { key: 'scripts', label: 'Scripts NSE par défaut (-sC)', type: 'bool' },
40
+ { key: 'vuln', label: 'Scripts de vulnérabilités (--script vuln)', type: 'bool' },
41
+ { key: 'os', label: "Détection d'OS (-O)", type: 'bool' },
42
+ ],
43
+
44
+ command(target, opts = {}) {
45
+ const host = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
46
+ const args = ['-sV', `-T${opts.timing ?? 4}`, '-Pn'];
47
+ if (opts.scripts || opts.vuln) args.push('-sC');
48
+ if (opts.vuln) args.push('--script', 'vuln');
49
+ if (opts.os) args.push('-O');
50
+ if (opts.ports) args.push('-p', String(opts.ports));
51
+ else if (opts.fast) args.push('-F');
52
+ args.push(host);
53
+ return { args };
54
+ },
55
+
56
+ parse({ stdout, target }) {
57
+ const host = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
58
+ const findings = [];
59
+ const portRe = /^(\d+)\/(tcp|udp)\s+(open(?:\|filtered)?)\s+(\S+)\s*(.*)$/i;
60
+
61
+ for (const raw of stdout.split(/\r?\n/)) {
62
+ const line = raw.trimEnd();
63
+ const m = portRe.exec(line.trim());
64
+ if (m) {
65
+ const [, port, proto, state, service, version] = m;
66
+ const risk = RISKY[Number(port)];
67
+ findings.push({
68
+ type: 'port',
69
+ severity: risk ? risk[0] : 'info',
70
+ title: `Port ${port}/${proto} ${state} — ${service}`,
71
+ detail: [version.trim(), risk?.[1]].filter(Boolean).join(' • ') || service,
72
+ ref: `${host}:${port}`,
73
+ });
74
+ continue;
75
+ }
76
+ const vuln = line.match(/^\|[_\s]+(VULNERABLE|CVE-\d{4}-\d+)[:\s-]*(.*)$/i);
77
+ if (vuln) {
78
+ findings.push({
79
+ type: 'vuln',
80
+ severity: 'high',
81
+ title: `NSE: ${vuln[1]} ${vuln[2]}`.trim(),
82
+ detail: host,
83
+ ref: 'nmap-nse',
84
+ });
85
+ }
86
+ }
87
+
88
+ const os = stdout.match(/OS details?:\s*(.+)/i) || stdout.match(/Running:\s*(.+)/i);
89
+ if (os) findings.push({ type: 'recon', severity: 'info', title: `OS: ${os[1].trim()}`, detail: host, ref: 'nmap' });
90
+
91
+ if (!findings.length && /0 hosts up|Host seems down/i.test(stdout)) {
92
+ findings.push({ type: 'recon', severity: 'info', title: 'Hôte injoignable', detail: host, ref: 'nmap' });
93
+ }
94
+ return findings;
95
+ },
96
+ };
@@ -0,0 +1,51 @@
1
+ import { normalize } from '../core/severity.js';
2
+ import { RESOLVERS_FILE } from '../core/resolvers.js';
3
+
4
+ export default {
5
+ id: 'nuclei',
6
+ name: 'Nuclei',
7
+ category: 'vuln',
8
+ description: 'Scanner de vulnérabilités (templates/CVE)',
9
+ needs: 'url',
10
+ bins: ['nuclei'],
11
+ winPaths: [],
12
+ install: 'go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest (ou release GitHub ProjectDiscovery)',
13
+
14
+ options: [
15
+ { key: 'severity', label: 'Sévérités', type: 'select', default: '', choices: [
16
+ { value: '', label: 'Toutes' },
17
+ { value: 'critical,high', label: 'Critical + High' },
18
+ { value: 'critical,high,medium', label: 'Critical → Medium' },
19
+ { value: 'info', label: 'Info seulement' },
20
+ ] },
21
+ { key: 'tags', label: 'Tags (filtre)', type: 'text', placeholder: 'cve,exposure,misconfig' },
22
+ ],
23
+
24
+ command(target, opts = {}) {
25
+ const args = ['-u', target, '-jsonl', '-silent', '-no-color'];
26
+ // Sous VPN, le résolveur interne de nuclei échoue -> on force des DNS valides.
27
+ if (RESOLVERS_FILE) args.push('-resolvers', RESOLVERS_FILE);
28
+ if (opts.severity) args.push('-severity', opts.severity);
29
+ if (opts.tags) args.push('-tags', opts.tags);
30
+ return { args };
31
+ },
32
+
33
+ parse({ stdout }) {
34
+ const findings = [];
35
+ for (const line of stdout.split(/\r?\n/)) {
36
+ const trimmed = line.trim();
37
+ if (!trimmed.startsWith('{')) continue;
38
+ let j;
39
+ try { j = JSON.parse(trimmed); } catch { continue; }
40
+ const info = j.info || {};
41
+ findings.push({
42
+ type: 'vuln',
43
+ severity: normalize(info.severity),
44
+ title: info.name || j['template-id'] || 'finding nuclei',
45
+ detail: j['matched-at'] || j.host || '',
46
+ ref: j['template-id'] || (info.tags ? [].concat(info.tags).join(',') : ''),
47
+ });
48
+ }
49
+ return findings;
50
+ },
51
+ };
@@ -0,0 +1,64 @@
1
+ export default {
2
+ id: 'sqlmap',
3
+ name: 'sqlmap',
4
+ category: 'web',
5
+ description: "Détection d'injections SQL",
6
+ needs: 'url', // idéalement une URL avec paramètre, ex: http://site/p?id=1
7
+ bins: ['sqlmap', 'sqlmap.exe'],
8
+ winPaths: [],
9
+ install: 'pip install sqlmap (ou https://sqlmap.org)',
10
+
11
+ options: [
12
+ { key: 'level', label: 'Level (1-5, profondeur des tests)', type: 'number', default: 1, min: 1, max: 5 },
13
+ { key: 'risk', label: 'Risk (1-3, agressivité)', type: 'number', default: 1, min: 1, max: 3 },
14
+ { key: 'crawl', label: 'Crawl (profondeur)', type: 'number', placeholder: 'ex: 2' },
15
+ { key: 'forms', label: 'Tester les formulaires (--forms)', type: 'bool' },
16
+ { key: 'dbs', label: 'Énumérer les bases (--dbs)', type: 'bool' },
17
+ ],
18
+
19
+ command(target, opts = {}) {
20
+ const args = ['-u', target, '--batch', '--disable-coloring'];
21
+ args.push('--level', String(opts.level || 1));
22
+ args.push('--risk', String(opts.risk || 1));
23
+ if (opts.crawl) args.push('--crawl', String(opts.crawl));
24
+ if (opts.forms) args.push('--forms');
25
+ if (opts.dbs) args.push('--dbs');
26
+ return { args };
27
+ },
28
+
29
+ parse({ stdout, target }) {
30
+ const findings = [];
31
+ const params = new Set();
32
+ const re = /Parameter:\s*([^\s(]+)\s*\(([^)]+)\)/gi;
33
+ let m;
34
+ while ((m = re.exec(stdout)) !== null) params.add(`${m[1]} (${m[2]})`);
35
+
36
+ const vulnerable = /is vulnerable|sqlmap identified the following injection point/i.test(stdout);
37
+
38
+ if (vulnerable || params.size) {
39
+ if (params.size) {
40
+ for (const p of params) {
41
+ findings.push({
42
+ type: 'sqli',
43
+ severity: 'high',
44
+ title: `Injection SQL — paramètre ${p}`,
45
+ detail: target,
46
+ ref: 'sqlmap',
47
+ });
48
+ }
49
+ } else {
50
+ findings.push({ type: 'sqli', severity: 'high', title: 'Injection SQL détectée', detail: target, ref: 'sqlmap' });
51
+ }
52
+ } else if (/all tested parameters do not appear to be injectable|might not be injectable/i.test(stdout)) {
53
+ findings.push({ type: 'sqli', severity: 'info', title: 'Aucune injection SQL trouvée', detail: target, ref: 'sqlmap' });
54
+ }
55
+
56
+ // Bases de données dumpées avec --dbs
57
+ const dbBlock = stdout.match(/available databases \[\d+\]:([\s\S]*?)(?:\n\n|\n\[)/i);
58
+ if (dbBlock) {
59
+ const dbs = dbBlock[1].split(/\r?\n/).map((l) => l.replace(/^\s*\[\*\]\s*/, '').trim()).filter(Boolean);
60
+ if (dbs.length) findings.push({ type: 'info', severity: 'medium', title: `Bases accessibles: ${dbs.join(', ')}`, detail: target, ref: 'sqlmap' });
61
+ }
62
+ return findings;
63
+ },
64
+ };
@@ -0,0 +1,27 @@
1
+ import { RESOLVERS_FILE } from '../core/resolvers.js';
2
+
3
+ export default {
4
+ id: 'subfinder',
5
+ name: 'subfinder',
6
+ category: 'recon',
7
+ description: 'Énumération de sous-domaines',
8
+ needs: 'host',
9
+ bins: ['subfinder'],
10
+ winPaths: [],
11
+ install: 'go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest',
12
+
13
+ command(target) {
14
+ const domain = String(target).replace(/^https?:\/\//, '').replace(/\/.*$/, '');
15
+ const args = ['-d', domain, '-silent'];
16
+ if (RESOLVERS_FILE) args.push('-rL', RESOLVERS_FILE);
17
+ return { args };
18
+ },
19
+
20
+ parse({ stdout }) {
21
+ return stdout
22
+ .split(/\r?\n/)
23
+ .map((l) => l.trim())
24
+ .filter((l) => l && /\./.test(l) && !/\s/.test(l))
25
+ .map((sub) => ({ type: 'subdomain', severity: 'info', title: sub, detail: '', ref: 'subfinder' }));
26
+ },
27
+ };
@@ -0,0 +1,33 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ export default {
5
+ id: 'wafw00f',
6
+ name: 'wafw00f',
7
+ category: 'recon',
8
+ description: 'Détection de WAF (pare-feu applicatif)',
9
+ needs: 'url',
10
+ needsWorkdir: true,
11
+ bins: ['wafw00f'],
12
+ winPaths: [],
13
+ install: 'pip install wafw00f',
14
+
15
+ command(target, opts = {}) {
16
+ const out = join(opts.workdir, 'wafw00f.json');
17
+ return { args: [target, '-f', 'json', '-o', out] };
18
+ },
19
+
20
+ parse({ workdir, target }) {
21
+ const out = join(workdir || '', 'wafw00f.json');
22
+ if (!workdir || !existsSync(out)) return [];
23
+ let arr;
24
+ try { arr = JSON.parse(readFileSync(out, 'utf8')); } catch { return []; }
25
+ return [].concat(arr).map((r) => ({
26
+ type: 'waf',
27
+ severity: 'info',
28
+ title: r.detected ? `WAF détecté: ${r.firewall || '?'}` : 'Aucun WAF détecté',
29
+ detail: [r.manufacturer, r.url || target].filter(Boolean).join(' — '),
30
+ ref: 'wafw00f',
31
+ }));
32
+ },
33
+ };