redgun-security 1.0.0 → 1.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/README.md +72 -20
- package/bin/redgun.js +1 -1
- package/package.json +7 -1
- package/scan.js +22 -0
- package/src/core/reporter/console.js +1 -1
- package/src/core/reporter/html.js +1 -1
- package/src/local/access-control.js +64 -0
- package/src/local/ato.js +72 -0
- package/src/local/business-logic.js +67 -0
- package/src/local/cicd.js +69 -0
- package/src/local/cloud.js +73 -0
- package/src/local/csrf.js +75 -0
- package/src/local/index.js +32 -8
- package/src/local/ldap.js +67 -0
- package/src/local/mobile.js +71 -0
- package/src/local/oauth.js +68 -0
- package/src/local/saml.js +72 -0
- package/src/local/web3.js +73 -0
- package/src/local/xxe.js +74 -0
- package/src/remote/advanced.js +238 -0
- package/src/remote/crawler.js +234 -0
- package/src/remote/portswigger.js +235 -0
- package/src/remote/probe.js +217 -0
package/README.md
CHANGED
|
@@ -10,25 +10,24 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<strong>Black-box & white-box security auditor for web applications —
|
|
13
|
+
<strong>Black-box & white-box security auditor for web applications — Enhanced.</strong>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
17
|
<a href="https://github.com/aloc999/redgun/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
|
|
18
18
|
<img src="https://img.shields.io/badge/node-%3E%3D18-green" alt="Node">
|
|
19
|
-
<img src="https://img.shields.io/badge/modules-
|
|
20
|
-
<img src="https://img.shields.io/badge/HackTricks-Enhanced-critical" alt="HackTricks">
|
|
19
|
+
<img src="https://img.shields.io/badge/modules-51-ff4444" alt="Modules">
|
|
21
20
|
</p>
|
|
22
21
|
|
|
23
22
|
<br>
|
|
24
23
|
|
|
25
24
|
## What is RedGun?
|
|
26
25
|
|
|
27
|
-
RedGun is a security auditing CLI tool that finds vulnerabilities in your web applications. It includes **
|
|
26
|
+
RedGun is a security auditing CLI tool that finds vulnerabilities in your web applications. It includes **51 security modules** covering techniques from [HackTricks](https://book.hacktricks.wiki), [PortSwigger Web Security Academy](https://portswigger.net/web-security), [Katana](https://github.com/projectdiscovery/katana), and [httpx](https://github.com/projectdiscovery/httpx). Two modes:
|
|
28
27
|
|
|
29
|
-
**Remote scan** (black-box): Give it a URL. It
|
|
28
|
+
**Remote scan** (black-box): Give it a URL. It crawls with Katana-style JS parsing, fingerprints with httpx-style probing, then tests — XSS, SQLi, SSRF, CORS, XXE, OAuth, IDOR, cache deception, DOM-based, HTTP smuggling, CRLF, parameter pollution, file upload, and more.
|
|
30
29
|
|
|
31
|
-
**Local audit** (white-box): Point it at your project directory. It reads your source code checking for secrets, SSTI, insecure deserialization, prototype pollution, JWT
|
|
30
|
+
**Local audit** (white-box): Point it at your project directory. It reads your source code checking for secrets, SSTI, XXE, insecure deserialization, prototype pollution, JWT attacks, OAuth flaws, IDOR, business logic, command injection, weak crypto, and more.
|
|
32
31
|
|
|
33
32
|
<br>
|
|
34
33
|
|
|
@@ -51,10 +50,12 @@ redgun modules # List all modules
|
|
|
51
50
|
|
|
52
51
|
<br>
|
|
53
52
|
|
|
54
|
-
## Remote Scan Modules (
|
|
53
|
+
## Remote Scan Modules (33 — Black-box)
|
|
55
54
|
|
|
56
55
|
| Module | What it tests | Source |
|
|
57
56
|
|---|---|---|
|
|
57
|
+
| **Probe & Fingerprint** | Status code, title, technologies (40+), CDN/WAF detection, favicon hash, response time, virtual host discovery | httpx |
|
|
58
|
+
| **Crawl & Extract** | JS file parsing, endpoint extraction, form discovery, parameter mining, email harvesting, secret detection in bundles | Katana |
|
|
58
59
|
| **HTTP Headers** | Missing CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, CORP, COEP | OWASP |
|
|
59
60
|
| **Exposed Files** | `.env`, `.git/config`, `package.json`, `.DS_Store`, source maps, actuator, swagger, phpinfo, Docker files, backups | HackTricks |
|
|
60
61
|
| **Secrets Detection** | API keys (AWS, Stripe, Firebase, Supabase, OpenAI, Anthropic), tokens, passwords in page source | HackTricks |
|
|
@@ -80,10 +81,18 @@ redgun modules # List all modules
|
|
|
80
81
|
| **WebSocket Security** | Origin validation, authentication checks | HackTricks |
|
|
81
82
|
| **Cache Poisoning** | Unkeyed headers (X-Forwarded-Host, X-Forwarded-Scheme, X-Original-URL) | HackTricks |
|
|
82
83
|
| **Race Conditions** | Detection guidance for concurrent request attacks | HackTricks |
|
|
84
|
+
| **XXE Injection** | XML entity injection at upload/import/SOAP endpoints | PortSwigger |
|
|
85
|
+
| **OAuth Misconfiguration** | redirect_uri validation, OIDC config exposure, implicit flow detection | PortSwigger |
|
|
86
|
+
| **Access Control Bypass** | Admin panel exposure, 403 bypass via X-Original-URL/X-Forwarded-For, robots.txt disclosure | PortSwigger |
|
|
87
|
+
| **Web Cache Deception** | Static extension cache deception, path normalization inconsistency | PortSwigger |
|
|
88
|
+
| **Parameter Pollution** | HTTP Parameter Pollution, null byte truncation, duplicate params | PortSwigger |
|
|
89
|
+
| **File Upload Testing** | Upload endpoint discovery, OPTIONS probing | PortSwigger |
|
|
90
|
+
| **DOM-Based Vulnerabilities** | DOM sinks (document.write, innerHTML, eval, postMessage), source-to-sink flow | PortSwigger |
|
|
91
|
+
| **HTTP/2 Attacks** | H2.CL/H2.TE smuggling indicators, HPACK injection surface | PortSwigger |
|
|
83
92
|
|
|
84
93
|
<br>
|
|
85
94
|
|
|
86
|
-
## Local Audit Modules (
|
|
95
|
+
## Local Audit Modules (18 — White-box)
|
|
87
96
|
|
|
88
97
|
| Module | What it checks | Source |
|
|
89
98
|
|---|---|---|
|
|
@@ -101,6 +110,10 @@ redgun modules # List all modules
|
|
|
101
110
|
| **Path Traversal / LFI** | User input in file paths, readFile, sendFile, include/require | HackTricks |
|
|
102
111
|
| **Command Injection** | exec, spawn, child_process, system, subprocess with user input, shell interpolation | HackTricks |
|
|
103
112
|
| **Weak Cryptography** | MD5, SHA1, DES, RC4, ECB mode, Math.random, hardcoded keys/IVs | HackTricks |
|
|
113
|
+
| **XXE Detection** | XML parsers without entity disabled, DOMParser, lxml, simplexml with user input | PortSwigger |
|
|
114
|
+
| **Access Control / IDOR** | Direct object reference, role from user input, admin headers, ownership checks | PortSwigger |
|
|
115
|
+
| **OAuth / OIDC Flaws** | redirect_uri manipulation, missing state, client_secret exposure, token storage | PortSwigger |
|
|
116
|
+
| **Business Logic** | Price manipulation, negative quantity, workflow step skipping, race conditions, referral abuse | PortSwigger |
|
|
104
117
|
|
|
105
118
|
<br>
|
|
106
119
|
|
|
@@ -209,9 +222,9 @@ Create a `.redgunignore` file to exclude files from local audit:
|
|
|
209
222
|
|
|
210
223
|
<br>
|
|
211
224
|
|
|
212
|
-
##
|
|
225
|
+
## Techniques & Sources
|
|
213
226
|
|
|
214
|
-
|
|
227
|
+
### HackTricks Techniques
|
|
215
228
|
|
|
216
229
|
- **SSRF** — AWS/GCP metadata, internal IP bypass, DNS rebinding indicators
|
|
217
230
|
- **SSTI** — Template engine detection and exploitation patterns
|
|
@@ -231,6 +244,37 @@ RedGun integrates techniques documented in [HackTricks](https://book.hacktricks.
|
|
|
231
244
|
- **Subdomain Takeover** — Dangling CNAME detection
|
|
232
245
|
- **Weak Cryptography** — Deprecated algorithms and hardcoded key detection
|
|
233
246
|
|
|
247
|
+
### PortSwigger Web Security Academy Techniques
|
|
248
|
+
|
|
249
|
+
- **XXE (XML External Entity)** — Entity injection, DTD-based file read, blind OOB XXE, parameter entities
|
|
250
|
+
- **Access Control** — Horizontal/vertical privilege escalation, IDOR, 403 bypass via headers (X-Original-URL, X-Rewrite-URL), referer-based control
|
|
251
|
+
- **OAuth 2.0 Vulnerabilities** — redirect_uri manipulation, state CSRF, implicit flow token theft, client_secret exposure, PKCE bypass
|
|
252
|
+
- **Business Logic** — Price manipulation, negative quantity, workflow step skipping, race condition exploitation, referral abuse, trial abuse
|
|
253
|
+
- **Web Cache Deception** — Static extension deception, path normalization inconsistency, cache key manipulation
|
|
254
|
+
- **DOM-Based Vulnerabilities** — Source-to-sink analysis, document.write, innerHTML, eval, postMessage hijacking
|
|
255
|
+
- **HTTP Parameter Pollution** — Duplicate parameters, server-side truncation, null byte injection
|
|
256
|
+
- **File Upload** — Unrestricted upload, extension bypass, content-type manipulation, polyglot files
|
|
257
|
+
- **HTTP/2 Attacks** — H2.CL smuggling, H2.TE smuggling, HPACK header injection, request tunneling
|
|
258
|
+
|
|
259
|
+
### Katana (ProjectDiscovery) Techniques
|
|
260
|
+
|
|
261
|
+
- **JavaScript Crawling** — Parse all JS bundles including lazy-loaded chunks for endpoints
|
|
262
|
+
- **Endpoint Extraction** — API routes, fetch/axios calls, URL patterns from source
|
|
263
|
+
- **Form Discovery** — Automatic form detection with CSRF and sensitive field analysis
|
|
264
|
+
- **Parameter Mining** — Extract all parameters from URLs, forms, and JS source
|
|
265
|
+
- **Secret Extraction** — API keys, tokens, and credentials from JS bundles
|
|
266
|
+
- **Email Harvesting** — Email addresses from page source for social engineering
|
|
267
|
+
|
|
268
|
+
### httpx (ProjectDiscovery) Techniques
|
|
269
|
+
|
|
270
|
+
- **Technology Detection** — 40+ technologies fingerprinted (frameworks, CMS, servers, BaaS, analytics)
|
|
271
|
+
- **CDN/WAF Detection** — Cloudflare, AWS CloudFront, Fastly, Akamai, Imperva, Sucuri, Azure, Vercel, Netlify
|
|
272
|
+
- **WAF Fingerprinting** — ModSecurity, Wordfence, F5 BIG-IP, Cloudflare WAF, Incapsula
|
|
273
|
+
- **Favicon Hashing** — MD5 hash for Shodan-style fingerprinting
|
|
274
|
+
- **Virtual Host Discovery** — Host header manipulation to find hidden vhosts
|
|
275
|
+
- **Response Analysis** — Status codes, content-length, response time, title extraction
|
|
276
|
+
- **TLS/Certificate Info** — Protocol detection, certificate validation
|
|
277
|
+
|
|
234
278
|
<br>
|
|
235
279
|
|
|
236
280
|
## Project Structure
|
|
@@ -247,7 +291,7 @@ redgun/
|
|
|
247
291
|
│ │ ├── console.js # Terminal output
|
|
248
292
|
│ │ ├── json.js # JSON + SARIF export
|
|
249
293
|
│ │ └── html.js # HTML report
|
|
250
|
-
│ ├── local/ # White-box modules (
|
|
294
|
+
│ ├── local/ # White-box modules (18)
|
|
251
295
|
│ │ ├── index.js # Module orchestrator
|
|
252
296
|
│ │ ├── secrets.js # Source code secrets
|
|
253
297
|
│ │ ├── env.js # .env audit
|
|
@@ -255,18 +299,26 @@ redgun/
|
|
|
255
299
|
│ │ ├── code-vulnerabilities.js # SQLi, XSS, eval, ReDoS
|
|
256
300
|
│ │ ├── auth.js # Auth & middleware
|
|
257
301
|
│ │ ├── headers-config.js # CSP/HSTS config
|
|
258
|
-
│ │ ├── ssrf.js # SSRF
|
|
259
|
-
│ │ ├── ssti.js # SSTI
|
|
260
|
-
│ │ ├── deserialization.js # Insecure deserialization
|
|
261
|
-
│ │ ├── prototype-pollution.js # Prototype pollution
|
|
262
|
-
│ │ ├── jwt.js # JWT vulnerabilities
|
|
263
|
-
│ │ ├── path-traversal.js # LFI/path traversal
|
|
264
|
-
│ │ ├── command-injection.js # OS command injection
|
|
265
|
-
│ │
|
|
302
|
+
│ │ ├── ssrf.js # SSRF (HackTricks)
|
|
303
|
+
│ │ ├── ssti.js # SSTI (HackTricks)
|
|
304
|
+
│ │ ├── deserialization.js # Insecure deserialization (HackTricks)
|
|
305
|
+
│ │ ├── prototype-pollution.js # Prototype pollution (HackTricks)
|
|
306
|
+
│ │ ├── jwt.js # JWT vulnerabilities (HackTricks)
|
|
307
|
+
│ │ ├── path-traversal.js # LFI/path traversal (HackTricks)
|
|
308
|
+
│ │ ├── command-injection.js # OS command injection (HackTricks)
|
|
309
|
+
│ │ ├── crypto.js # Weak cryptography (HackTricks)
|
|
310
|
+
│ │ ├── xxe.js # XXE detection (PortSwigger)
|
|
311
|
+
│ │ ├── access-control.js # IDOR/access control (PortSwigger)
|
|
312
|
+
│ │ ├── oauth.js # OAuth/OIDC flaws (PortSwigger)
|
|
313
|
+
│ │ └── business-logic.js # Business logic (PortSwigger)
|
|
314
|
+
│ ├── remote/ # Black-box enhanced modules
|
|
315
|
+
│ │ ├── crawler.js # Katana-style JS crawler
|
|
316
|
+
│ │ ├── probe.js # httpx-style fingerprinting
|
|
317
|
+
│ │ └── portswigger.js # PortSwigger remote tests
|
|
266
318
|
│ └── utils/
|
|
267
319
|
│ ├── fetch.js # HTTP with timeout
|
|
268
320
|
│ └── patterns.js # Shared regex patterns
|
|
269
|
-
├── scan.js # Remote scan engine (
|
|
321
|
+
├── scan.js # Remote scan engine (33 modules)
|
|
270
322
|
├── action.yml # GitHub Action definition
|
|
271
323
|
├── .github/workflows/security.yml
|
|
272
324
|
└── package.json
|
package/bin/redgun.js
CHANGED
|
@@ -19,7 +19,7 @@ const program = new Command();
|
|
|
19
19
|
|
|
20
20
|
program
|
|
21
21
|
.name('redgun')
|
|
22
|
-
.description('Black-box & white-box security auditor for web applications (
|
|
22
|
+
.description('Black-box & white-box security auditor for web applications (Enhanced)')
|
|
23
23
|
.version('1.0.0');
|
|
24
24
|
|
|
25
25
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redgun-security",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Black-box & white-box security auditor for web applications with HackTricks techniques",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "scan.js",
|
|
@@ -20,10 +20,16 @@
|
|
|
20
20
|
"vulnerability",
|
|
21
21
|
"audit",
|
|
22
22
|
"hacktricks",
|
|
23
|
+
"portswigger",
|
|
24
|
+
"katana",
|
|
25
|
+
"httpx",
|
|
23
26
|
"xss",
|
|
24
27
|
"sqli",
|
|
25
28
|
"ssrf",
|
|
26
29
|
"ssti",
|
|
30
|
+
"xxe",
|
|
31
|
+
"idor",
|
|
32
|
+
"oauth",
|
|
27
33
|
"web-security"
|
|
28
34
|
],
|
|
29
35
|
"author": "aloc999",
|
package/scan.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { addFinding } from './src/core/findings.js';
|
|
2
2
|
import { fetchText, checkUrl } from './src/utils/fetch.js';
|
|
3
3
|
import { EXPOSED_FILES, HEADER_CHECKS, COMMON_SUBDOMAINS, COMMON_PORTS, SECRET_PATTERNS } from './src/utils/patterns.js';
|
|
4
|
+
import { runCrawler } from './src/remote/crawler.js';
|
|
5
|
+
import { runProbe } from './src/remote/probe.js';
|
|
6
|
+
import { scanXxeRemote, scanOauthRemote, scanAccessControlRemote, scanWebCacheDeception, scanParameterPollution, scanFileUpload, scanDomBased, scanHttp2 } from './src/remote/portswigger.js';
|
|
7
|
+
import { scanSamlRemote, scanLdapRemote, scanMfaBypass, scanWebsocketReplay, scanPasswordReset, scanCsrfRemote, scanDanglingDns, scanCloudRemote } from './src/remote/advanced.js';
|
|
4
8
|
|
|
5
9
|
export async function runRemoteScan(url, spinner, modules = null) {
|
|
6
10
|
const target = new URL(url);
|
|
@@ -8,6 +12,8 @@ export async function runRemoteScan(url, spinner, modules = null) {
|
|
|
8
12
|
const origin = target.origin;
|
|
9
13
|
|
|
10
14
|
const allModules = [
|
|
15
|
+
{ name: 'Probe & Fingerprint (httpx)', value: 'probe', fn: () => runProbe(origin, spinner) },
|
|
16
|
+
{ name: 'Crawl & Extract (Katana)', value: 'crawl', fn: () => runCrawler(origin, spinner) },
|
|
11
17
|
{ name: 'HTTP Headers', value: 'headers', fn: () => scanHeaders(origin, spinner) },
|
|
12
18
|
{ name: 'Exposed Files & Paths', value: 'files', fn: () => scanExposedFiles(origin, spinner) },
|
|
13
19
|
{ name: 'Secrets in JS Bundles', value: 'secrets', fn: () => scanSecrets(origin, spinner) },
|
|
@@ -33,6 +39,22 @@ export async function runRemoteScan(url, spinner, modules = null) {
|
|
|
33
39
|
{ name: 'WebSocket Security (HackTricks)', value: 'websocket', fn: () => scanWebsocket(origin, spinner) },
|
|
34
40
|
{ name: 'Cache Poisoning (HackTricks)', value: 'cache', fn: () => scanCachePoisoning(origin, spinner) },
|
|
35
41
|
{ name: 'Race Condition Detection (HackTricks)', value: 'race', fn: () => scanRaceCondition(origin, spinner) },
|
|
42
|
+
{ name: 'XXE Injection (PortSwigger)', value: 'xxe', fn: () => scanXxeRemote(origin, spinner) },
|
|
43
|
+
{ name: 'OAuth Misconfiguration (PortSwigger)', value: 'oauth', fn: () => scanOauthRemote(origin, spinner) },
|
|
44
|
+
{ name: 'Access Control Bypass (PortSwigger)', value: 'acl', fn: () => scanAccessControlRemote(origin, spinner) },
|
|
45
|
+
{ name: 'Web Cache Deception (PortSwigger)', value: 'wcd', fn: () => scanWebCacheDeception(origin, spinner) },
|
|
46
|
+
{ name: 'Parameter Pollution (PortSwigger)', value: 'hpp', fn: () => scanParameterPollution(origin, spinner) },
|
|
47
|
+
{ name: 'File Upload Testing (PortSwigger)', value: 'upload', fn: () => scanFileUpload(origin, spinner) },
|
|
48
|
+
{ name: 'DOM-Based Vulnerabilities (PortSwigger)', value: 'dom', fn: () => scanDomBased(origin, spinner) },
|
|
49
|
+
{ name: 'HTTP/2 Attacks (PortSwigger)', value: 'h2', fn: () => scanHttp2(origin, spinner) },
|
|
50
|
+
{ name: 'SAML/SSO Attacks', value: 'saml', fn: () => scanSamlRemote(origin, spinner) },
|
|
51
|
+
{ name: 'LDAP Injection', value: 'ldap', fn: () => scanLdapRemote(origin, spinner) },
|
|
52
|
+
{ name: 'MFA Bypass Testing', value: 'mfa', fn: () => scanMfaBypass(origin, spinner) },
|
|
53
|
+
{ name: 'WebSocket Replay/CSWSH', value: 'wshijack', fn: () => scanWebsocketReplay(origin, spinner) },
|
|
54
|
+
{ name: 'Password Reset Security', value: 'pwdreset', fn: () => scanPasswordReset(origin, spinner) },
|
|
55
|
+
{ name: 'CSRF Token Analysis (remote)', value: 'csrf', fn: () => scanCsrfRemote(origin, spinner) },
|
|
56
|
+
{ name: 'Subdomain Takeover (Dangling DNS)', value: 'takeover', fn: () => scanDanglingDns(hostname, spinner) },
|
|
57
|
+
{ name: 'Cloud Metadata SSRF', value: 'cloudmeta', fn: () => scanCloudRemote(origin, spinner) },
|
|
36
58
|
];
|
|
37
59
|
|
|
38
60
|
const toRun = modules ? allModules.filter((m) => modules.includes(m.value)) : allModules;
|
|
@@ -19,7 +19,7 @@ export function printBanner() {
|
|
|
19
19
|
██║ ██║███████╗██████╔╝╚██████╔╝╚██████╔╝██║ ╚████║
|
|
20
20
|
╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
21
21
|
`));
|
|
22
|
-
console.log(chalk.gray(' Black-box & white-box security auditor |
|
|
22
|
+
console.log(chalk.gray(' Black-box & white-box security auditor | Enhanced\n'));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function printResults() {
|
|
@@ -89,7 +89,7 @@ export function exportHtml(outputDir = './scans') {
|
|
|
89
89
|
<h2>Findings (${findings.length} total)</h2>
|
|
90
90
|
${findingsHtml}
|
|
91
91
|
<div class="footer">
|
|
92
|
-
<p>RedGun Security Scanner v1.
|
|
92
|
+
<p>RedGun Security Scanner v1.1.0 | Enhanced</p>
|
|
93
93
|
</div>
|
|
94
94
|
</div>
|
|
95
95
|
</body>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { addFinding } from '../core/findings.js';
|
|
4
|
+
|
|
5
|
+
const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java'];
|
|
6
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
|
|
7
|
+
|
|
8
|
+
export async function auditAccessControl(projectPath, spinner) {
|
|
9
|
+
spinner.text = 'Scanning for access control vulnerabilities (PortSwigger)...';
|
|
10
|
+
const files = getFiles(projectPath);
|
|
11
|
+
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(file, 'utf-8');
|
|
15
|
+
const relativePath = file.replace(projectPath, '.');
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
|
|
18
|
+
const patterns = [
|
|
19
|
+
{ pattern: /(?:req|request)\.(?:params|query|body)\.\s*(?:user_?id|userId|uid|owner_?id|account_?id)/gi, name: 'IDOR - user ID from request parameters', severity: 'HIGH' },
|
|
20
|
+
{ pattern: /(?:isAdmin|is_admin|role)\s*[:=]\s*(?:req|request|params|query|body)/gi, name: 'Role/privilege from user input', severity: 'CRITICAL' },
|
|
21
|
+
{ pattern: /(?:if|when)\s*\(\s*(?:req|request)\.(?:headers|query|params)\[?\s*['"]?(?:x-admin|admin|role|is[-_]?admin)/gi, name: 'Admin check via client-controlled header/param', severity: 'CRITICAL' },
|
|
22
|
+
{ pattern: /\.findById\s*\(\s*(?:req|params|query|body)\./gi, name: 'Direct object reference without ownership check', severity: 'HIGH' },
|
|
23
|
+
{ pattern: /\.findOne\s*\(\s*\{\s*(?:_id|id)\s*:\s*(?:req|params|query|body)\./gi, name: 'Database lookup by user-supplied ID (potential IDOR)', severity: 'HIGH' },
|
|
24
|
+
{ pattern: /\.delete\s*\(\s*['"`]\/[^'"`]*:id/gi, name: 'DELETE route with :id param (check authorization)', severity: 'MEDIUM' },
|
|
25
|
+
{ pattern: /\.put\s*\(\s*['"`]\/[^'"`]*:id/gi, name: 'PUT route with :id param (check authorization)', severity: 'MEDIUM' },
|
|
26
|
+
{ pattern: /(?:admin|dashboard|manage|internal).*(?:app\.get|router\.get|@app\.route)/gi, name: 'Admin route - verify middleware protection', severity: 'MEDIUM' },
|
|
27
|
+
{ pattern: /res\.json\s*\(\s*(?:users|accounts|orders|payments|transactions)/gi, name: 'Bulk data exposure without filtering', severity: 'MEDIUM' },
|
|
28
|
+
{ pattern: /X-Original-URL|X-Rewrite-URL/gi, name: 'URL override headers (access control bypass)', severity: 'HIGH' },
|
|
29
|
+
{ pattern: /referer\s*(?:===?|!==?|includes|match)/gi, name: 'Referer-based access control (easily spoofed)', severity: 'HIGH' },
|
|
30
|
+
{ pattern: /(?:hidden|type=['"]hidden['"])\s*.*(?:role|admin|privilege|permission)/gi, name: 'Hidden form field for access control', severity: 'HIGH' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const { pattern, name, severity } of patterns) {
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
36
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
37
|
+
addFinding(
|
|
38
|
+
severity,
|
|
39
|
+
'Access Control (PortSwigger)',
|
|
40
|
+
name,
|
|
41
|
+
`File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
|
|
42
|
+
'Implement server-side authorization checks. Verify object ownership on every request. Use middleware for role-based access control. Never trust client-supplied IDs without verifying the requesting user owns that resource.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getFiles(dir, files = []) {
|
|
51
|
+
try {
|
|
52
|
+
const entries = readdirSync(dir);
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
|
|
55
|
+
const fullPath = join(dir, entry);
|
|
56
|
+
try {
|
|
57
|
+
const stat = statSync(fullPath);
|
|
58
|
+
if (stat.isDirectory()) getFiles(fullPath, files);
|
|
59
|
+
else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
package/src/local/ato.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { addFinding } from '../core/findings.js';
|
|
4
|
+
|
|
5
|
+
const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb', '.php', '.go', '.java'];
|
|
6
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
|
|
7
|
+
|
|
8
|
+
export async function auditAto(projectPath, spinner) {
|
|
9
|
+
spinner.text = 'Scanning for Account Takeover patterns...';
|
|
10
|
+
const files = getFiles(projectPath);
|
|
11
|
+
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(file, 'utf-8');
|
|
15
|
+
const relativePath = file.replace(projectPath, '.');
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
|
|
18
|
+
const patterns = [
|
|
19
|
+
{ pattern: /(?:password|pwd).*reset.*token\s*=\s*(?:Math\.random|Date\.now|uuid|random)/gi, name: 'Password reset token from predictable source', severity: 'CRITICAL' },
|
|
20
|
+
{ pattern: /reset.*token\s*=\s*(?:hash|md5|sha1)\s*\(/gi, name: 'Reset token hashed with weak algorithm', severity: 'MEDIUM' },
|
|
21
|
+
{ pattern: /reset.*token\s*.*(?:url|link|href).*\$\{.*token/gi, name: 'Reset token exposed in URL (referer leakage)', severity: 'HIGH' },
|
|
22
|
+
{ pattern: /(?:email|phone)\s*.*\s*change.*\s*\((?!.*confirm|verify|reauth|password)/gi, name: 'Email/phone change without re-authentication', severity: 'HIGH' },
|
|
23
|
+
{ pattern: /logout\s*\([^)]*\)\s*\{[^}]*(?:session\.destroy|clearCookie|cookie.*clear)/gi, name: 'Logout implementation (check server-side invalidation)', severity: 'INFO' },
|
|
24
|
+
{ pattern: /(?:session|token|jwt)\s*.*\s*(?:invalidation|revoke|blacklist|expire|expiry)/gi, name: 'Session/token revocation', severity: 'INFO' },
|
|
25
|
+
{ pattern: /otp\s*.*generate.*\s*=\s*(?:Math\.floor|Math\.random|random)/gi, name: 'OTP from predictable source', severity: 'CRITICAL' },
|
|
26
|
+
{ pattern: /otp.*length\s*[<=]?\s*[0-5]/gi, name: 'Short OTP length (< 6 digits)', severity: 'HIGH' },
|
|
27
|
+
{ pattern: /otp.*(?:expir|ttl|validity|valid.*for).*[>=]?\s*(?:60\d|7\d|8\d|9\d)\d*/gi, name: 'Long OTP validity (> 10 min)', severity: 'MEDIUM' },
|
|
28
|
+
{ pattern: /(?:login|auth|signin)\s*.*\s*attempt.*\s*=\s*0/gi, name: 'Rate limit on login (good)', severity: 'INFO' },
|
|
29
|
+
{ pattern: /(?:password|credential|account|login).*(?:lock|throttle|attempt|brute|restrict|limit)/gi, name: 'Account lockout/throttling detected', severity: 'INFO' },
|
|
30
|
+
{ pattern: /(?:social|oauth|google|github|facebook).*(?:link|connect|attach).*(?!.*confirm|verify|reauth)/gi, name: 'Social account linking without re-auth', severity: 'HIGH' },
|
|
31
|
+
{ pattern: /(?:MFA|2FA|two.?factor|multi.?factor).*(?:skip|disabled?|bypass|false)/gi, name: 'MFA skip/bypass condition', severity: 'CRITICAL' },
|
|
32
|
+
{ pattern: /(?:MFA|2FA).*\..*secret.*\s*=.*['"][a-zA-Z0-9]{10,}['"]/gi, name: 'MFA secret hardcoded', severity: 'CRITICAL' },
|
|
33
|
+
{ pattern: /(?:register|signup|create).*(?:duplicate|exist|unique).*email|username/gi, name: 'User enumeration via register endpoint', severity: 'MEDIUM' },
|
|
34
|
+
{ pattern: /(?:login|signin).*(?:incorrect|wrong|invalid|not found).*(?:password|email)/gi, name: 'User enumeration via verbose login errors', severity: 'MEDIUM' },
|
|
35
|
+
{ pattern: /res\.sendStatus\s*\(\s*401|res\.json.*invalid.*credentials/gi, name: 'Generic error handling (good for preventing enumeration)', severity: 'INFO' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (const { pattern, name, severity } of patterns) {
|
|
39
|
+
let match;
|
|
40
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
41
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
42
|
+
const line = lines[lineNum - 1]?.trim() || '';
|
|
43
|
+
if (line.startsWith('//') || line.startsWith('#') || line.startsWith('*')) continue;
|
|
44
|
+
|
|
45
|
+
addFinding(
|
|
46
|
+
severity,
|
|
47
|
+
'Account Takeover (ATO)',
|
|
48
|
+
name,
|
|
49
|
+
`File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
|
|
50
|
+
'Use crypto.randomBytes for reset tokens. Expire tokens quickly (< 15 min). Require current password to change email/password or link social accounts. Use TOTP-based MFA (not SMS). Use generic error messages. Implement proper rate limiting and account lockout (not just IP-based).'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getFiles(dir, files = []) {
|
|
59
|
+
try {
|
|
60
|
+
const entries = readdirSync(dir);
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
|
|
63
|
+
const fullPath = join(dir, entry);
|
|
64
|
+
try {
|
|
65
|
+
const stat = statSync(fullPath);
|
|
66
|
+
if (stat.isDirectory()) getFiles(fullPath, files);
|
|
67
|
+
else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { addFinding } from '../core/findings.js';
|
|
4
|
+
|
|
5
|
+
const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java'];
|
|
6
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
|
|
7
|
+
|
|
8
|
+
export async function auditBusinessLogic(projectPath, spinner) {
|
|
9
|
+
spinner.text = 'Scanning for business logic flaws (PortSwigger)...';
|
|
10
|
+
const files = getFiles(projectPath);
|
|
11
|
+
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(file, 'utf-8');
|
|
15
|
+
const relativePath = file.replace(projectPath, '.');
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
|
|
18
|
+
const patterns = [
|
|
19
|
+
{ pattern: /(?:price|amount|total|cost|quantity|qty)\s*[:=]\s*(?:req|params|query|body|request)\./gi, name: 'Price/amount from client input (price manipulation)', severity: 'CRITICAL' },
|
|
20
|
+
{ pattern: /(?:discount|coupon|promo|voucher).*(?:apply|use|redeem)/gi, name: 'Discount/coupon logic (test for race conditions & reuse)', severity: 'MEDIUM' },
|
|
21
|
+
{ pattern: /(?:quantity|qty)\s*[:=]\s*(?:parseInt|Number)\s*\(\s*(?:req|body)/gi, name: 'Quantity from user input (negative quantity attack)', severity: 'HIGH' },
|
|
22
|
+
{ pattern: /(?:transfer|withdraw|send)\s*.*(?:amount|value)\s*[:=]/gi, name: 'Financial transfer logic (check for negative amounts, overflow)', severity: 'HIGH' },
|
|
23
|
+
{ pattern: /(?:limit|max|threshold)\s*.*(?:parseInt|Number|parseFloat)\s*\(\s*(?:req|body|query)/gi, name: 'Limit/threshold from user input', severity: 'MEDIUM' },
|
|
24
|
+
{ pattern: /(?:step|stage|phase|state)\s*[:=]\s*(?:req|params|query|body)/gi, name: 'Workflow step from user input (step skipping)', severity: 'HIGH' },
|
|
25
|
+
{ pattern: /(?:if|when).*(?:balance|credits?|points?)\s*(?:>=?|<=?|>|<)\s*(?:0|amount|price)/gi, name: 'Balance check (test for race conditions)', severity: 'MEDIUM' },
|
|
26
|
+
{ pattern: /(?:verify|validate|check).*(?:email|phone|identity)\s*.*(?:skip|bypass|false)/gi, name: 'Verification bypass flag', severity: 'HIGH' },
|
|
27
|
+
{ pattern: /(?:free[-_]?trial|trial[-_]?period).*(?:extend|reset|create)/gi, name: 'Trial logic (test for unlimited trial abuse)', severity: 'MEDIUM' },
|
|
28
|
+
{ pattern: /(?:referral|invite|bonus).*(?:credit|reward|points)/gi, name: 'Referral/bonus logic (test for self-referral abuse)', severity: 'MEDIUM' },
|
|
29
|
+
{ pattern: /parseInt\s*\(\s*(?:req|body|query).*10\s*\)|Number\s*\(\s*(?:req|body|query)/gi, name: 'Numeric input parsing (test integer overflow, NaN, Infinity)', severity: 'LOW' },
|
|
30
|
+
{ pattern: /(?:vote|like|upvote|rate|review).*(?:req|body|params)/gi, name: 'Voting/rating logic (test for multiple votes)', severity: 'MEDIUM' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const { pattern, name, severity } of patterns) {
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
36
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
37
|
+
const line = lines[lineNum - 1]?.trim() || '';
|
|
38
|
+
if (line.startsWith('//') || line.startsWith('#')) continue;
|
|
39
|
+
|
|
40
|
+
addFinding(
|
|
41
|
+
severity,
|
|
42
|
+
'Business Logic (PortSwigger)',
|
|
43
|
+
name,
|
|
44
|
+
`File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
|
|
45
|
+
'Never trust client-supplied values for prices, quantities, or workflow states. Validate all business rules server-side. Use database transactions with proper isolation levels for financial operations. Implement idempotency keys to prevent double-processing.'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getFiles(dir, files = []) {
|
|
54
|
+
try {
|
|
55
|
+
const entries = readdirSync(dir);
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
|
|
58
|
+
const fullPath = join(dir, entry);
|
|
59
|
+
try {
|
|
60
|
+
const stat = statSync(fullPath);
|
|
61
|
+
if (stat.isDirectory()) getFiles(fullPath, files);
|
|
62
|
+
else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { addFinding } from '../core/findings.js';
|
|
4
|
+
|
|
5
|
+
const SCAN_EXTENSIONS = ['.yml', '.yaml', '.js', '.ts', '.sh', '.bash'];
|
|
6
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
|
|
7
|
+
|
|
8
|
+
export async function auditCicd(projectPath, spinner) {
|
|
9
|
+
spinner.text = 'Scanning for CI/CD pipeline vulnerabilities...';
|
|
10
|
+
const files = getFiles(projectPath);
|
|
11
|
+
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(file, 'utf-8');
|
|
15
|
+
const relativePath = file.replace(projectPath, '.');
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
|
|
18
|
+
const patterns = [
|
|
19
|
+
{ pattern: /pull_request_target/gi, name: 'pull_request_target event (injection risk)', severity: 'CRITICAL' },
|
|
20
|
+
{ pattern: /pull_request_target.*(?:checkout|run:|script:|exec|execute)/gi, name: 'pull_request_target with code execution', severity: 'CRITICAL' },
|
|
21
|
+
{ pattern: /on:\s*pull_request_target.*workflow_run/gi, name: 'workflow_run from fork PR (injection)', severity: 'CRITICAL' },
|
|
22
|
+
{ pattern: /\$\{\{\s*github\.event\.pull_request\.(?:title|body|head\.ref)\s*\}\}/gi, name: 'GitHub Actions - PR body/title used unsafely', severity: 'CRITICAL' },
|
|
23
|
+
{ pattern: /run:.*\${{.*github\.event\.|run:.*\${{.*inputs\./gi, name: 'GitHub Actions - user input in run step', severity: 'CRITICAL' },
|
|
24
|
+
{ pattern: /secrets\.\w+\s*:\s*\$\{\{\s*inputs\./gi, name: 'Secrets exposed via workflow inputs', severity: 'HIGH' },
|
|
25
|
+
{ pattern: /(?:ACCESS_TOKEN|API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIALS)\s*:\s*\$\{\{\s*secrets\./gi, name: 'Secrets in workflow environment', severity: 'INFO' },
|
|
26
|
+
{ pattern: /actions\/checkout@v\d\s*$|[^/]checkout\s*:/gi, name: 'Repository checkout', severity: 'INFO' },
|
|
27
|
+
{ pattern: /jenkins.*pipeline|Jenkinsfile/gi, name: 'Jenkins pipeline', severity: 'INFO' },
|
|
28
|
+
{ pattern: /stage\s*\(\s*['"]Deploy|publish|release/gi, name: 'CI deployment stage', severity: 'INFO' },
|
|
29
|
+
{ pattern: /docker.*build.*push|docker.*image|container.*registry/gi, name: 'Container build and push', severity: 'INFO' },
|
|
30
|
+
{ pattern: /gitlab-ci\.yml|\.gitlab-ci/gi, name: 'GitLab CI configuration', severity: 'INFO' },
|
|
31
|
+
{ pattern: /(?:npm|pip|maven|gradle|docker|helm)\s*(?:publish|push|deploy)/gi, name: 'Package/docker publish step', severity: 'INFO' },
|
|
32
|
+
{ pattern: /needs:\s*\[.*build.*\]\s*$|if:\s*github\.ref\s*==\s*'refs\/heads\/(?:main|master)'/gi, name: 'CI gate on main branch', severity: 'INFO' },
|
|
33
|
+
{ pattern: /write\s*:\s*\[\s*['"]id-token['"]\s*\]|id-token\s*:\s*write/gi, name: 'OIDC token write permission', severity: 'MEDIUM' },
|
|
34
|
+
{ pattern: /actions\s*:\s*read.*contents:\s*write|actions: write/gi, name: 'Elevated pipeline permissions', severity: 'HIGH' },
|
|
35
|
+
{ pattern: /(?:fetch-depth|filter)\s*:\s*0/gi, name: 'Full git history fetched', severity: 'LOW' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (const { pattern, name, severity } of patterns) {
|
|
39
|
+
let match;
|
|
40
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
41
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
42
|
+
addFinding(
|
|
43
|
+
severity,
|
|
44
|
+
'CI/CD Pipeline',
|
|
45
|
+
name,
|
|
46
|
+
`File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
|
|
47
|
+
'Never use pull_request_target with untrusted code execution. Sanitize all github.event fields used in scripts. Use OIDC instead of long-lived secrets. Apply least-privilege GitHub token permissions. Use environment protection rules for deployments.'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getFiles(dir, files = []) {
|
|
56
|
+
try {
|
|
57
|
+
const entries = readdirSync(dir);
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
|
|
60
|
+
const fullPath = join(dir, entry);
|
|
61
|
+
try {
|
|
62
|
+
const stat = statSync(fullPath);
|
|
63
|
+
if (stat.isDirectory()) getFiles(fullPath, files);
|
|
64
|
+
else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 256 * 1024) files.push(fullPath);
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
} catch {}
|
|
68
|
+
return files;
|
|
69
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { addFinding } from '../core/findings.js';
|
|
4
|
+
|
|
5
|
+
const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb', '.tf', '.hcl', '.yml', '.yaml', '.json'];
|
|
6
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
|
|
7
|
+
|
|
8
|
+
export async function auditCloud(projectPath, spinner) {
|
|
9
|
+
spinner.text = 'Scanning for cloud misconfigurations...';
|
|
10
|
+
const files = getFiles(projectPath);
|
|
11
|
+
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(file, 'utf-8');
|
|
15
|
+
const relativePath = file.replace(projectPath, '.');
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
|
|
18
|
+
const patterns = [
|
|
19
|
+
{ pattern: /"Effect"\s*:\s*"Allow"\s*,\s*"Principal"\s*:\s*"\*"/gi, name: 'S3 bucket policy - public access (Principal: *)', severity: 'CRITICAL' },
|
|
20
|
+
{ pattern: /"Effect"\s*:\s*"Allow"\s*,\s*"Principal"\s*:\s*{?\s*"AWS"\s*:\s*"\*"/gi, name: 'AWS IAM policy - public access', severity: 'CRITICAL' },
|
|
21
|
+
{ pattern: /s3:GetObject.*Principal.*\*/gi, name: 'S3 GetObject public access', severity: 'CRITICAL' },
|
|
22
|
+
{ pattern: /s3:PutObject.*Principal.*\*/gi, name: 'S3 PutObject public access (critical!)', severity: 'CRITICAL' },
|
|
23
|
+
{ pattern: /block_public_acls\s*=\s*false|blockPublicPolicy\s*:\s*false/gi, name: 'S3 public access block disabled', severity: 'CRITICAL' },
|
|
24
|
+
{ pattern: /access_key\s*=\s*['"][A-Za-z0-9+\/=]{20}['"]/gi, name: 'AWS access key in config', severity: 'CRITICAL' },
|
|
25
|
+
{ pattern: /secret_key\s*=\s*['"][A-Za-z0-9+\/=]{40}['"]/gi, name: 'AWS secret key in config', severity: 'CRITICAL' },
|
|
26
|
+
{ pattern: /primary_access_key|primary_secret_key/gi, name: 'Cloud access keys in source', severity: 'CRITICAL' },
|
|
27
|
+
{ pattern: /metadata.*169\.254|instance.?metadata/gi, name: 'Cloud metadata endpoint reference', severity: 'INFO' },
|
|
28
|
+
{ pattern: /"Action"\s*:\s*"\*:"\s*,\s*"Resource"\s*:\s*"\*"/gi, name: 'IAM policy - wildcard action + resource', severity: 'CRITICAL' },
|
|
29
|
+
{ pattern: /"NotAction"|"NotResource"/gi, name: 'IAM NotAction/NotResource (check for over-permissiveness)', severity: 'MEDIUM' },
|
|
30
|
+
{ pattern: /GCP_CREDENTIALS|GOOGLE_APPLICATION_CREDENTIALS|gcp\.json|service\.account/gi, name: 'GCP service account credentials', severity: 'CRITICAL' },
|
|
31
|
+
{ pattern: /AZURE_STORAGE_CONNECTION_STRING|AZURE_CLIENT_SECRET|ARM_CLIENT_SECRET/gi, name: 'Azure credentials in source', severity: 'CRITICAL' },
|
|
32
|
+
{ pattern: /terraform\.tfstate|backend\.tf/gi, name: 'Terraform state reference', severity: 'HIGH' },
|
|
33
|
+
{ pattern: /(?:bucket|container)\s*.*acl\s*.*public-read/gi, name: 'Storage bucket with public-read ACL', severity: 'CRITICAL' },
|
|
34
|
+
{ pattern: /(?:lambda|function_url).*auth_type\s*=\s*"NONE"/gi, name: 'AWS Lambda function URL without auth', severity: 'HIGH' },
|
|
35
|
+
{ pattern: /(?:publicly_readable|publicly_writable)\s*=\s*true/gi, name: 'GCP bucket publicly accessible', severity: 'CRITICAL' },
|
|
36
|
+
{ pattern: /(?:monitoring|logging|audit.?log)\s*.*(?:disabled?|false)/gi, name: 'Cloud logging/monitoring disabled', severity: 'MEDIUM' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const { pattern, name, severity } of patterns) {
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
42
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
43
|
+
const line = lines[lineNum - 1]?.trim() || '';
|
|
44
|
+
if (line.startsWith('//') || line.startsWith('#') || line.startsWith('*')) continue;
|
|
45
|
+
|
|
46
|
+
addFinding(
|
|
47
|
+
severity,
|
|
48
|
+
'Cloud Misconfig (S3/IAM)',
|
|
49
|
+
name,
|
|
50
|
+
`File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
|
|
51
|
+
'Follow least privilege principle. Never use wildcard (*) for IAM actions/resources. Block all public S3 bucket access by default. Use IAM roles, not access keys. Enable CloudTrail logging. Store secrets in AWS Secrets Manager / GCP Secret Manager / Azure Key Vault.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getFiles(dir, files = []) {
|
|
60
|
+
try {
|
|
61
|
+
const entries = readdirSync(dir);
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
|
|
64
|
+
const fullPath = join(dir, entry);
|
|
65
|
+
try {
|
|
66
|
+
const stat = statSync(fullPath);
|
|
67
|
+
if (stat.isDirectory()) getFiles(fullPath, files);
|
|
68
|
+
else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
return files;
|
|
73
|
+
}
|