omen-sec-cli 1.0.19 → 1.0.21
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.
- package/.omen/context.json +565 -0
- package/README.md +2 -2
- package/bin/index.js +1 -1
- package/core/discover/stack-detector.js +56 -7
- package/core/engine-v2.js +38 -9
- package/core/remote-scanner.js +115 -85
- package/core/reporters/fix-plan-reporter.js +1 -1
- package/core/ui-server.js +12 -8
- package/omen-reports/omen-fix-plan.md +139 -0
- package/omen-reports/omen-report.json +557 -22
- package/omen-reports/omen-report.md +129 -0
- package/omen-reports/omen-report.txt +53 -7
- package/package.json +1 -1
- package/ui/banner.js +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import * as cheerio from 'cheerio';
|
|
4
5
|
|
|
5
6
|
export async function runDiscovery(projectPath = process.cwd()) {
|
|
6
7
|
const discovery = {
|
|
@@ -9,18 +10,27 @@ export async function runDiscovery(projectPath = process.cwd()) {
|
|
|
9
10
|
entrypoints: [],
|
|
10
11
|
boot_strategy: null,
|
|
11
12
|
critical_files: [],
|
|
12
|
-
dependencies: {}
|
|
13
|
+
dependencies: {},
|
|
14
|
+
is_remote: false
|
|
13
15
|
};
|
|
14
16
|
|
|
17
|
+
// Check if it's a URL
|
|
18
|
+
if (projectPath.startsWith('http')) {
|
|
19
|
+
discovery.is_remote = true;
|
|
20
|
+
return await runRemoteDiscovery(projectPath, discovery);
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
// 1. Load package.json if exists
|
|
16
24
|
try {
|
|
17
25
|
const pkgPath = path.join(projectPath, 'package.json');
|
|
18
|
-
const
|
|
26
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
27
|
+
const pkgData = JSON.parse(pkgContent);
|
|
19
28
|
discovery.dependencies = { ...pkgData.dependencies, ...pkgData.devDependencies };
|
|
20
29
|
|
|
21
30
|
if (discovery.dependencies['next']) discovery.stack = 'Next.js';
|
|
22
31
|
else if (discovery.dependencies['express']) discovery.stack = 'Node/Express';
|
|
23
32
|
else if (discovery.dependencies['react']) discovery.stack = 'React SPA';
|
|
33
|
+
else discovery.stack = 'Node.js';
|
|
24
34
|
|
|
25
35
|
// Boot strategy for Node
|
|
26
36
|
if (pkgData.scripts) {
|
|
@@ -33,7 +43,7 @@ export async function runDiscovery(projectPath = process.cwd()) {
|
|
|
33
43
|
try {
|
|
34
44
|
const requirementsPath = path.join(projectPath, 'requirements.txt');
|
|
35
45
|
await fs.access(requirementsPath);
|
|
36
|
-
|
|
46
|
+
if (discovery.stack === 'Unknown') discovery.stack = 'Python';
|
|
37
47
|
discovery.critical_files.push('requirements.txt');
|
|
38
48
|
} catch (e) {}
|
|
39
49
|
|
|
@@ -46,7 +56,7 @@ export async function runDiscovery(projectPath = process.cwd()) {
|
|
|
46
56
|
} catch (e) {}
|
|
47
57
|
|
|
48
58
|
// 4. Find entrypoints
|
|
49
|
-
const commonEntrypoints = ['server.js', 'app.js', 'index.js', 'src/index.js', 'main.py', 'app.py'];
|
|
59
|
+
const commonEntrypoints = ['server.js', 'app.js', 'index.js', 'src/index.js', 'main.py', 'app.py', 'index.php'];
|
|
50
60
|
for (const entry of commonEntrypoints) {
|
|
51
61
|
try {
|
|
52
62
|
await fs.access(path.join(projectPath, entry));
|
|
@@ -55,13 +65,52 @@ export async function runDiscovery(projectPath = process.cwd()) {
|
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
// 5. Critical Security Files
|
|
58
|
-
const securityFiles = ['.env', '.env.local', '.env.production', '.git/config', 'docker-compose.yml', 'Dockerfile'];
|
|
68
|
+
const securityFiles = ['.env', '.env.local', '.env.production', '.git/config', 'docker-compose.yml', 'Dockerfile', 'package.json', 'composer.json', 'requirements.txt'];
|
|
59
69
|
for (const file of securityFiles) {
|
|
60
70
|
try {
|
|
61
71
|
await fs.access(path.join(projectPath, file));
|
|
62
|
-
discovery.critical_files.push(file);
|
|
72
|
+
if (!discovery.critical_files.includes(file)) discovery.critical_files.push(file);
|
|
63
73
|
} catch (e) {}
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
return discovery;
|
|
67
77
|
}
|
|
78
|
+
|
|
79
|
+
async function runRemoteDiscovery(url, discovery) {
|
|
80
|
+
try {
|
|
81
|
+
const response = await axios.get(url, {
|
|
82
|
+
timeout: 10000,
|
|
83
|
+
validateStatus: () => true,
|
|
84
|
+
headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.20 (Discovery)' }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const headers = response.headers;
|
|
88
|
+
const html = response.data || '';
|
|
89
|
+
|
|
90
|
+
// Header-based detection
|
|
91
|
+
if (headers['x-powered-by']) discovery.stack = headers['x-powered-by'];
|
|
92
|
+
if (headers['server']) discovery.stack = headers['server'];
|
|
93
|
+
|
|
94
|
+
// HTML-based detection
|
|
95
|
+
if (html.includes('next-head') || html.includes('_next/static')) discovery.stack = 'Next.js';
|
|
96
|
+
else if (html.includes('react-root') || html.includes('data-react')) discovery.stack = 'React';
|
|
97
|
+
else if (html.includes('wp-content')) discovery.stack = 'WordPress';
|
|
98
|
+
else if (html.includes('nuxt')) discovery.stack = 'Nuxt.js';
|
|
99
|
+
|
|
100
|
+
// Entrypoints as routes
|
|
101
|
+
const $ = cheerio.load(html);
|
|
102
|
+
$('a').each((i, el) => {
|
|
103
|
+
const href = $(el).attr('href');
|
|
104
|
+
if (href && !href.startsWith('#') && !href.startsWith('http') && discovery.entrypoints.length < 5) {
|
|
105
|
+
discovery.entrypoints.push(href);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
discovery.entrypoints = [...new Set(discovery.entrypoints)];
|
|
110
|
+
discovery.boot_strategy = 'Remote URL';
|
|
111
|
+
|
|
112
|
+
} catch (err) {
|
|
113
|
+
discovery.stack = 'Unknown (Remote unreachable)';
|
|
114
|
+
}
|
|
115
|
+
return discovery;
|
|
116
|
+
}
|
package/core/engine-v2.js
CHANGED
|
@@ -23,33 +23,49 @@ export async function plan() {
|
|
|
23
23
|
const state = await loadState();
|
|
24
24
|
const discovery = state.discovery;
|
|
25
25
|
|
|
26
|
-
if (!discovery || !discovery.stack) {
|
|
26
|
+
if (!discovery || !discovery.stack || discovery.stack === 'Unknown') {
|
|
27
27
|
console.log(chalk.red('No discovery data found. Run "omen discover" first.'));
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const plan = {
|
|
32
32
|
steps: [
|
|
33
|
-
{ id: 'static', action: 'Static Analysis', status: 'pending' }
|
|
34
|
-
{ id: 'dependencies', action: 'Dependency Audit', status: 'pending' }
|
|
33
|
+
{ id: 'static', action: 'Static Analysis', status: 'pending' }
|
|
35
34
|
]
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
// If local, check for dependencies
|
|
38
|
+
if (!discovery.is_remote) {
|
|
39
|
+
plan.steps.push({ id: 'dependencies', action: 'Dependency Audit', status: 'pending' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (discovery.boot_strategy && discovery.boot_strategy !== 'Remote URL') {
|
|
39
43
|
plan.steps.push({ id: 'boot', action: `Boot App (${discovery.boot_strategy})`, status: 'pending' });
|
|
40
44
|
plan.steps.push({ id: 'dynamic', action: 'Dynamic Analysis (Localhost)', status: 'pending' });
|
|
45
|
+
} else if (discovery.is_remote) {
|
|
46
|
+
plan.steps.push({ id: 'dynamic', action: 'Dynamic Analysis (Remote)', status: 'pending' });
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
console.log(chalk.white(`Plan generated with ${plan.steps.length} steps.`));
|
|
50
|
+
// IMPORTANTE: Retornar o plano e salvar no estado
|
|
44
51
|
await saveState({ plan });
|
|
45
52
|
return plan;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
export async function execute() {
|
|
49
56
|
console.log(chalk.bold.cyan('\n--- Phase 3: Execution ---'));
|
|
50
|
-
|
|
57
|
+
|
|
58
|
+
// Recarregar o estado para garantir que temos o plano
|
|
59
|
+
let state = await loadState();
|
|
60
|
+
|
|
61
|
+
if (!state.plan || !state.plan.steps || state.plan.steps.length === 0) {
|
|
62
|
+
console.log(chalk.yellow('No plan found in state. Attempting to generate one...'));
|
|
63
|
+
await plan();
|
|
64
|
+
state = await loadState(); // Recarregar após plan()
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
if (!state.plan || !state.plan.steps) {
|
|
52
|
-
console.log(chalk.red('
|
|
68
|
+
console.log(chalk.red('Failed to generate or load plan. Run "omen discover" first.'));
|
|
53
69
|
return;
|
|
54
70
|
}
|
|
55
71
|
|
|
@@ -59,12 +75,18 @@ export async function execute() {
|
|
|
59
75
|
for (const step of state.plan.steps) {
|
|
60
76
|
console.log(chalk.yellow(`\nExecuting: ${step.action}...`));
|
|
61
77
|
|
|
62
|
-
if (step.id === 'static') {
|
|
78
|
+
if (step.id === 'static' && !state.discovery.is_remote) {
|
|
63
79
|
const localResult = await scanLocalProject();
|
|
64
80
|
vulnerabilities.push(...localResult.vulnerabilities);
|
|
65
81
|
executionLogs.push(`Static analysis scanned ${localResult.localFilesScanned} files.`);
|
|
66
82
|
}
|
|
67
83
|
|
|
84
|
+
if (step.id === 'dynamic' && state.discovery.is_remote) {
|
|
85
|
+
const dynamicResult = await scanRemoteTarget(state.discovery.path);
|
|
86
|
+
vulnerabilities.push(...dynamicResult.vulnerabilities);
|
|
87
|
+
executionLogs.push(`Dynamic analysis performed on ${state.discovery.path}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
if (step.id === 'boot') {
|
|
69
91
|
const bootResult = await bootLocalApp(state.discovery.boot_strategy);
|
|
70
92
|
if (!bootResult.success) {
|
|
@@ -106,11 +128,18 @@ export async function execute() {
|
|
|
106
128
|
const resultData = {
|
|
107
129
|
execution,
|
|
108
130
|
score,
|
|
109
|
-
vulnerabilities,
|
|
131
|
+
vulnerabilities: vulnerabilities || [],
|
|
110
132
|
riskLevel: score < 40 ? 'Critical' : score < 70 ? 'High' : score < 90 ? 'Medium' : 'Low',
|
|
111
133
|
timestamp: new Date().toISOString(),
|
|
112
134
|
target: state.discovery?.path || process.cwd(),
|
|
113
|
-
scan_id: `OMEN-${Date.now()}
|
|
135
|
+
scan_id: `OMEN-${Date.now()}`,
|
|
136
|
+
discovery: state.discovery || {},
|
|
137
|
+
plan: state.plan || {},
|
|
138
|
+
attack_surface: {
|
|
139
|
+
endpoints: state.discovery?.entrypoints || [],
|
|
140
|
+
tech_stack: state.discovery?.stack ? [state.discovery.stack] : [],
|
|
141
|
+
critical_files: state.discovery?.critical_files || []
|
|
142
|
+
}
|
|
114
143
|
};
|
|
115
144
|
|
|
116
145
|
await saveState(resultData);
|
package/core/remote-scanner.js
CHANGED
|
@@ -15,12 +15,18 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
15
15
|
const response = await axios.get(targetUrl, {
|
|
16
16
|
timeout: 15000,
|
|
17
17
|
validateStatus: () => true,
|
|
18
|
-
headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.
|
|
18
|
+
headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.21 (Security Audit)' }
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
serverStatus = response.status;
|
|
22
22
|
const headers = response.headers;
|
|
23
|
-
const html = response.data;
|
|
23
|
+
const html = response.data || '';
|
|
24
|
+
|
|
25
|
+
// --- WAF / CHALLENGE DETECTION ---
|
|
26
|
+
const isVercelChallenge = headers['x-vercel-mitigated'] === 'challenge' || (typeof html === 'string' && html.includes('Vercel Security Checkpoint'));
|
|
27
|
+
if (isVercelChallenge) {
|
|
28
|
+
techStack.push('Vercel WAF (Challenge Active)');
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
// --- TECHNOLOGY FINGERPRINTING ---
|
|
26
32
|
if (headers['x-powered-by']) techStack.push(headers['x-powered-by']);
|
|
@@ -32,98 +38,100 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
32
38
|
if (html.includes('nuxt')) techStack.push('Nuxt.js');
|
|
33
39
|
|
|
34
40
|
// --- Header Analysis (Existing - Refined Descriptions) ---
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
if (!isVercelChallenge) {
|
|
42
|
+
if (!headers['strict-transport-security']) {
|
|
43
|
+
headers_analysis["Strict-Transport-Security"] = "Missing";
|
|
44
|
+
vulnerabilities.push({
|
|
45
|
+
id: `REM-VULN-${Date.now()}-1`,
|
|
46
|
+
kind: 'header',
|
|
47
|
+
category: 'hardening',
|
|
48
|
+
confidence: 'high',
|
|
49
|
+
severity: 'medium',
|
|
50
|
+
title: 'HSTS Header Missing',
|
|
51
|
+
description: `HTTP Strict-Transport-Security (HSTS) header is missing. This prevents the browser from enforcing HTTPS-only connections for future visits.`,
|
|
52
|
+
cwe: 'CWE-319',
|
|
53
|
+
evidence: {
|
|
54
|
+
request: { headers: { ...response.request.headers } },
|
|
55
|
+
response: { status: response.status, headers: response.headers },
|
|
56
|
+
reason: 'Security header "Strict-Transport-Security" not found in server response.'
|
|
57
|
+
},
|
|
58
|
+
remediation: 'Implement the Strict-Transport-Security header with a long max-age (e.g., 31536000) and the includeSubDomains directive.'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
vulnerabilities.push({
|
|
58
|
-
id: `REM-VULN-${Date.now()}-2`,
|
|
59
|
-
kind: 'header',
|
|
60
|
-
category: 'confirmed',
|
|
61
|
-
confidence: 'high',
|
|
62
|
-
severity: 'high',
|
|
63
|
-
title: 'Content-Security-Policy Missing',
|
|
64
|
-
description: `CSP header is missing. Without a strict Content-Security-Policy, the application is highly vulnerable to Cross-Site Scripting (XSS) and data injection attacks.`,
|
|
65
|
-
cwe: 'CWE-1022',
|
|
66
|
-
evidence: {
|
|
67
|
-
request: { headers: { ...response.request.headers } },
|
|
68
|
-
response: { status: response.status, headers: response.headers },
|
|
69
|
-
reason: 'Security header "Content-Security-Policy" not found in server response.'
|
|
70
|
-
},
|
|
71
|
-
remediation: 'Define a strict Content-Security-Policy to restrict source domains for scripts, styles, and other resources.'
|
|
72
|
-
});
|
|
73
|
-
} else {
|
|
74
|
-
headers_analysis["Content-Security-Policy"] = headers['content-security-policy'];
|
|
75
|
-
if (headers['content-security-policy'].includes("unsafe-inline")) {
|
|
62
|
+
if (!headers['content-security-policy']) {
|
|
63
|
+
headers_analysis["Content-Security-Policy"] = "Missing";
|
|
76
64
|
vulnerabilities.push({
|
|
77
|
-
id: `REM-VULN-${Date.now()}-
|
|
65
|
+
id: `REM-VULN-${Date.now()}-2`,
|
|
78
66
|
kind: 'header',
|
|
79
67
|
category: 'confirmed',
|
|
80
68
|
confidence: 'high',
|
|
81
69
|
severity: 'high',
|
|
82
|
-
title: '
|
|
83
|
-
description: `
|
|
84
|
-
cwe: 'CWE-
|
|
85
|
-
evidence: {
|
|
86
|
-
|
|
70
|
+
title: 'Content-Security-Policy Missing',
|
|
71
|
+
description: `CSP header is missing. Without a strict Content-Security-Policy, the application is highly vulnerable to Cross-Site Scripting (XSS) and data injection attacks.`,
|
|
72
|
+
cwe: 'CWE-1022',
|
|
73
|
+
evidence: {
|
|
74
|
+
request: { headers: { ...response.request.headers } },
|
|
75
|
+
response: { status: response.status, headers: response.headers },
|
|
76
|
+
reason: 'Security header "Content-Security-Policy" not found in server response.'
|
|
77
|
+
},
|
|
78
|
+
remediation: 'Define a strict Content-Security-Policy to restrict source domains for scripts, styles, and other resources.'
|
|
87
79
|
});
|
|
80
|
+
} else {
|
|
81
|
+
headers_analysis["Content-Security-Policy"] = headers['content-security-policy'];
|
|
82
|
+
if (headers['content-security-policy'].includes("unsafe-inline")) {
|
|
83
|
+
vulnerabilities.push({
|
|
84
|
+
id: `REM-VULN-${Date.now()}-3`,
|
|
85
|
+
kind: 'header',
|
|
86
|
+
category: 'confirmed',
|
|
87
|
+
confidence: 'high',
|
|
88
|
+
severity: 'high',
|
|
89
|
+
title: 'Insecure CSP (unsafe-inline)',
|
|
90
|
+
description: `The Content-Security-Policy allows 'unsafe-inline' for scripts or styles, significantly weakening protection against XSS.`,
|
|
91
|
+
cwe: 'CWE-16',
|
|
92
|
+
evidence: { finding: `policy: ${headers['content-security-policy']}` },
|
|
93
|
+
remediation: 'Refactor the application to avoid inline scripts and styles, then remove "unsafe-inline" from the CSP.'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
88
96
|
}
|
|
89
|
-
}
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
// 3. Analisar X-Frame-Options
|
|
99
|
+
if (!headers['x-frame-options']) {
|
|
100
|
+
headers_analysis["X-Frame-Options"] = "Missing";
|
|
101
|
+
vulnerabilities.push({
|
|
102
|
+
id: `REM-VULN-${Date.now()}-4`,
|
|
103
|
+
kind: 'header',
|
|
104
|
+
category: 'hardening',
|
|
105
|
+
confidence: 'high',
|
|
106
|
+
severity: 'low',
|
|
107
|
+
title: 'X-Frame-Options Missing',
|
|
108
|
+
description: `Missing X-Frame-Options header. This allows the application to be embedded in an iframe on other domains, increasing Clickjacking risk.`,
|
|
109
|
+
cwe: 'CWE-1021',
|
|
110
|
+
evidence: {
|
|
111
|
+
request: { headers: { ...response.request.headers } },
|
|
112
|
+
response: { status: response.status, headers: response.headers },
|
|
113
|
+
reason: 'Security header "X-Frame-Options" not found. This allows the site to be embedded in iframes on third-party domains.'
|
|
114
|
+
},
|
|
115
|
+
remediation: 'Set the X-Frame-Options header to DENY or SAMEORIGIN.'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
111
118
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// 3.1 Analisar X-Content-Type-Options
|
|
120
|
+
if (!headers['x-content-type-options']) {
|
|
121
|
+
headers_analysis["X-Content-Type-Options"] = "Missing";
|
|
122
|
+
vulnerabilities.push({
|
|
123
|
+
id: `REM-VULN-${Date.now()}-6`,
|
|
124
|
+
kind: 'header',
|
|
125
|
+
category: 'hardening',
|
|
126
|
+
confidence: 'high',
|
|
127
|
+
severity: 'low',
|
|
128
|
+
title: 'X-Content-Type-Options Missing',
|
|
129
|
+
description: `The X-Content-Type-Options: nosniff header is missing. This could allow the browser to "sniff" the content type, potentially leading to MIME-type sniffing attacks.`,
|
|
130
|
+
cwe: 'CWE-116',
|
|
131
|
+
evidence: { response: { headers: response.headers } },
|
|
132
|
+
remediation: 'Add the "X-Content-Type-Options: nosniff" header to all responses.'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
// 4. Server Header Leak
|
|
@@ -297,6 +305,8 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
297
305
|
'/api/auth/session', '/api/graphql', '/actuator/health', '/.ssh/id_rsa'
|
|
298
306
|
];
|
|
299
307
|
|
|
308
|
+
const forbiddenPaths = [];
|
|
309
|
+
|
|
300
310
|
for (const path of aggressivePaths) {
|
|
301
311
|
try {
|
|
302
312
|
const fuzzUrl = new URL(path, targetUrl).href;
|
|
@@ -305,6 +315,11 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
305
315
|
validateStatus: (status) => status >= 200 && status < 500
|
|
306
316
|
});
|
|
307
317
|
|
|
318
|
+
if (fuzzRes.status === 403) {
|
|
319
|
+
forbiddenPaths.push(path);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
308
323
|
const finding = await validateFuzzerFinding(path, fuzzRes, fuzzUrl);
|
|
309
324
|
if (finding) {
|
|
310
325
|
vulnerabilities.push(finding);
|
|
@@ -314,6 +329,21 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
314
329
|
}
|
|
315
330
|
}
|
|
316
331
|
|
|
332
|
+
if (forbiddenPaths.length > 0) {
|
|
333
|
+
vulnerabilities.push({
|
|
334
|
+
id: `REM-ENUM-FORBIDDEN-${Date.now()}`,
|
|
335
|
+
kind: 'path',
|
|
336
|
+
category: 'informational',
|
|
337
|
+
confidence: 'high',
|
|
338
|
+
severity: 'info',
|
|
339
|
+
title: 'Path Enumeration: Protected Resources',
|
|
340
|
+
description: `Multiple paths (${forbiddenPaths.length}) returned 403 Forbidden, confirming their existence but restricted access.`,
|
|
341
|
+
cwe: 'CWE-204',
|
|
342
|
+
evidence: { forbidden_paths: forbiddenPaths },
|
|
343
|
+
remediation: 'Ensure that 403 responses do not leak internal structure and that access controls are correctly configured.'
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
317
347
|
// --- OFFENSIVE PARAMETER FUZZING ---
|
|
318
348
|
const injectionPayloads = [
|
|
319
349
|
{ type: 'SQLi', param: "' OR '1'='1", severity: 'Critical', cwe: 'CWE-89' },
|
|
@@ -338,7 +368,7 @@ export async function scanRemoteTarget(targetUrl) {
|
|
|
338
368
|
const res = await axios.get(testUrl.href, {
|
|
339
369
|
timeout: 5000,
|
|
340
370
|
validateStatus: () => true,
|
|
341
|
-
headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.
|
|
371
|
+
headers: { 'User-Agent': 'OMEN-SEC-CLI/1.0.21 (Security Audit)' }
|
|
342
372
|
});
|
|
343
373
|
|
|
344
374
|
const evidence = {
|
|
@@ -41,6 +41,6 @@ export function generateFixPlan(scanData) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
md += `\n*Gerado automaticamente pelo OMEN SEC-CLI v1.0.
|
|
44
|
+
md += `\n*Gerado automaticamente pelo OMEN SEC-CLI v1.0.21 - Protocolo Zero-Copy AI Ativo*\n`;
|
|
45
45
|
return md;
|
|
46
46
|
}
|
package/core/ui-server.js
CHANGED
|
@@ -44,6 +44,10 @@ export async function startUIServer() {
|
|
|
44
44
|
<title>OMEN SEC-CLI Evidence Center</title>
|
|
45
45
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
46
46
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
|
|
47
|
+
<script>
|
|
48
|
+
// Ensure data safety for JS
|
|
49
|
+
const report = ${JSON.stringify(report)};
|
|
50
|
+
</script>
|
|
47
51
|
<style>
|
|
48
52
|
body { background-color: #0a0a0c; color: #e0e0e0; font-family: 'Inter', sans-serif; }
|
|
49
53
|
.mono { font-family: 'JetBrains Mono', monospace; }
|
|
@@ -124,10 +128,10 @@ export async function startUIServer() {
|
|
|
124
128
|
<span class="w-2 h-2 bg-blue-500 rounded-full mr-2"></span> Phase Intelligence
|
|
125
129
|
</h2>
|
|
126
130
|
<div class="space-y-4 text-sm">
|
|
127
|
-
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">
|
|
128
|
-
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">
|
|
129
|
-
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">
|
|
130
|
-
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">
|
|
131
|
+
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Target URL</span> <span class="mono text-gray-300">${(report && report.target) || 'N/A'}</span> </div>
|
|
132
|
+
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Tech Stack</span> <span class="text-gray-300">${(report && report.attack_surface && report.attack_surface.tech_stack && report.attack_surface.tech_stack.join(', ')) || 'N/A'}</span> </div>
|
|
133
|
+
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Endpoints Discovered</span> <span class="text-gray-300">${(report && report.attack_surface && report.attack_surface.endpoints && report.attack_surface.endpoints.length) || 0}</span> </div>
|
|
134
|
+
<div class="flex justify-between border-b border-gray-800 pb-2"> <span class="text-gray-500">Critical Files</span> <span class="text-gray-300">${(report && report.attack_surface && report.attack_surface.critical_files && report.attack_surface.critical_files.length) || 0}</span> </div>
|
|
131
135
|
</div>
|
|
132
136
|
</div>
|
|
133
137
|
</div>
|
|
@@ -222,18 +226,18 @@ export async function startUIServer() {
|
|
|
222
226
|
<div class="lg:col-span-2 space-y-8">
|
|
223
227
|
<div class="card p-6 rounded-xl">
|
|
224
228
|
<h3 class="text-xl font-bold mb-4">Discovered Assets</h3>
|
|
225
|
-
<pre class="mono max-h-[600px]">${(report.discovery
|
|
229
|
+
<pre class="mono max-h-[600px]">${((report && report.discovery && report.discovery.entrypoints) || []).concat((report && report.discovery && report.discovery.critical_files) || []).join('\n') || 'No assets discovered.'}</pre>
|
|
226
230
|
</div>
|
|
227
231
|
</div>
|
|
228
232
|
<div class="space-y-8">
|
|
229
233
|
<div class="card p-6 rounded-xl">
|
|
230
234
|
<h3 class="text-xl font-bold mb-4">Phase Log</h3>
|
|
231
|
-
<pre class="mono text-[10px]">${(report.execution
|
|
235
|
+
<pre class="mono text-[10px]">${(report && report.execution && report.execution.logs && report.execution.logs.join('\n')) || 'No execution logs.'}</pre>
|
|
232
236
|
</div>
|
|
233
237
|
<div class="card p-6 rounded-xl">
|
|
234
238
|
<h3 class="text-xl font-bold mb-4">Tech Fingerprint</h3>
|
|
235
239
|
<div class="flex flex-wrap gap-2">
|
|
236
|
-
${(report.
|
|
240
|
+
${(report && report.discovery && report.discovery.stack ? [report.discovery.stack] : []).map(t => `<span class="px-3 py-1 bg-gray-800 rounded-full text-xs font-bold text-blue-400 border border-gray-700">${t}</span>`).join('') || '<span class="text-gray-500 italic">No stack identified.</span>'}
|
|
237
241
|
</div>
|
|
238
242
|
</div>
|
|
239
243
|
</div>
|
|
@@ -262,7 +266,7 @@ export async function startUIServer() {
|
|
|
262
266
|
</div>
|
|
263
267
|
|
|
264
268
|
<footer class="text-center text-gray-600 mt-16 border-t border-gray-900 pt-8 mb-10">
|
|
265
|
-
<p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.
|
|
269
|
+
<p class="text-xs uppercase tracking-widest font-bold mb-2">OMEN Security Framework - v1.0.21</p>
|
|
266
270
|
<p class="text-[10px] text-gray-700 italic">"The eye that never sleeps, the code that never fails."</p>
|
|
267
271
|
</footer>
|
|
268
272
|
</div>
|