redgun-security 1.0.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.
@@ -0,0 +1,75 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+ import { SSTI_PATTERNS } from '../utils/patterns.js';
5
+
6
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.py', '.rb', '.php', '.java', '.go'];
7
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
8
+
9
+ export async function auditSsti(projectPath, spinner) {
10
+ spinner.text = 'Scanning for Server-Side Template Injection (HackTricks)...';
11
+ const files = getFiles(projectPath);
12
+
13
+ for (const file of files) {
14
+ try {
15
+ const content = readFileSync(file, 'utf-8');
16
+ const relativePath = file.replace(projectPath, '.');
17
+ const lines = content.split('\n');
18
+
19
+ for (const pattern of SSTI_PATTERNS) {
20
+ const regex = new RegExp(pattern.source, pattern.flags);
21
+ let match;
22
+ while ((match = regex.exec(content)) !== null) {
23
+ const lineNum = content.substring(0, match.index).split('\n').length;
24
+ addFinding(
25
+ 'CRITICAL',
26
+ 'SSTI (HackTricks)',
27
+ 'Server-Side Template Injection - user input in template',
28
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
29
+ 'Never pass user input directly to template engines. Use template variables/context instead of string concatenation. Sandbox template execution if possible.'
30
+ );
31
+ }
32
+ }
33
+
34
+ const additionalPatterns = [
35
+ { pattern: /render\s*\(\s*['"`].*\$\{.*\}.*['"`]/gi, name: 'Template string in render()' },
36
+ { pattern: /Environment\s*\(\s*.*\)\s*.*from_string/gi, name: 'Jinja2 from_string()' },
37
+ { pattern: /Handlebars\.compile\s*\(\s*(?:req|params|query|body)/gi, name: 'Handlebars user template' },
38
+ { pattern: /ejs\.render\s*\(\s*(?:req|params|query|body)/gi, name: 'EJS user template' },
39
+ { pattern: /Velocity\.evaluate/gi, name: 'Apache Velocity evaluate' },
40
+ { pattern: /Freemarker.*\$\{/gi, name: 'Freemarker expression' },
41
+ { pattern: /Thymeleaf.*th:text.*\$\{/gi, name: 'Thymeleaf expression' },
42
+ ];
43
+
44
+ for (const { pattern, name } of additionalPatterns) {
45
+ let match;
46
+ while ((match = pattern.exec(content)) !== null) {
47
+ const lineNum = content.substring(0, match.index).split('\n').length;
48
+ addFinding(
49
+ 'HIGH',
50
+ 'SSTI (HackTricks)',
51
+ `Potential SSTI via ${name}`,
52
+ `File: ${relativePath}:${lineNum}`,
53
+ 'Validate that user input is never used as template source. Use sandboxed environments and restrict template builtins.'
54
+ );
55
+ }
56
+ }
57
+ } catch {}
58
+ }
59
+ }
60
+
61
+ function getFiles(dir, files = []) {
62
+ try {
63
+ const entries = readdirSync(dir);
64
+ for (const entry of entries) {
65
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
66
+ const fullPath = join(dir, entry);
67
+ try {
68
+ const stat = statSync(fullPath);
69
+ if (stat.isDirectory()) getFiles(fullPath, files);
70
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
71
+ } catch {}
72
+ }
73
+ } catch {}
74
+ return files;
75
+ }
@@ -0,0 +1,55 @@
1
+ import https from 'https';
2
+ import http from 'http';
3
+
4
+ export async function fetchWithTimeout(url, options = {}, timeout = 10000) {
5
+ const controller = new AbortController();
6
+ const timer = setTimeout(() => controller.abort(), timeout);
7
+
8
+ try {
9
+ const response = await fetch(url, {
10
+ ...options,
11
+ signal: controller.signal,
12
+ headers: {
13
+ 'User-Agent': 'RedGun-Security-Scanner/1.0',
14
+ ...options.headers,
15
+ },
16
+ });
17
+ clearTimeout(timer);
18
+ return response;
19
+ } catch (error) {
20
+ clearTimeout(timer);
21
+ if (error.name === 'AbortError') {
22
+ throw new Error(`Request timeout after ${timeout}ms: ${url}`);
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ export async function fetchText(url, options = {}) {
29
+ const response = await fetchWithTimeout(url, options);
30
+ return {
31
+ status: response.status,
32
+ headers: Object.fromEntries(response.headers.entries()),
33
+ body: await response.text(),
34
+ url: response.url,
35
+ };
36
+ }
37
+
38
+ export async function fetchJson(url, options = {}) {
39
+ const response = await fetchWithTimeout(url, options);
40
+ return {
41
+ status: response.status,
42
+ headers: Object.fromEntries(response.headers.entries()),
43
+ body: await response.json(),
44
+ url: response.url,
45
+ };
46
+ }
47
+
48
+ export async function checkUrl(url, timeout = 5000) {
49
+ try {
50
+ const response = await fetchWithTimeout(url, { method: 'HEAD' }, timeout);
51
+ return { accessible: true, status: response.status };
52
+ } catch {
53
+ return { accessible: false, status: null };
54
+ }
55
+ }
@@ -0,0 +1,205 @@
1
+ export const SECRET_PATTERNS = {
2
+ 'AWS Access Key': /AKIA[0-9A-Z]{16}/g,
3
+ 'AWS Secret Key': /(?:aws_secret_access_key|aws_secret)\s*[=:]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/gi,
4
+ 'GitHub Token': /gh[pousr]_[A-Za-z0-9_]{36,255}/g,
5
+ 'GitHub OAuth': /gho_[A-Za-z0-9_]{36,255}/g,
6
+ 'Google API Key': /AIza[0-9A-Za-z\-_]{35}/g,
7
+ 'Firebase Key': /AIza[0-9A-Za-z\-_]{35}/g,
8
+ 'Stripe Secret Key': /sk_live_[0-9a-zA-Z]{24,}/g,
9
+ 'Stripe Publishable Key': /pk_live_[0-9a-zA-Z]{24,}/g,
10
+ 'Supabase Service Role': /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
11
+ 'Slack Token': /xox[baprs]-[0-9a-zA-Z-]{10,}/g,
12
+ 'Slack Webhook': /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[a-zA-Z0-9]+/g,
13
+ 'Twilio API Key': /SK[0-9a-fA-F]{32}/g,
14
+ 'SendGrid Key': /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
15
+ 'Mailgun Key': /key-[0-9a-zA-Z]{32}/g,
16
+ 'Heroku API Key': /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,
17
+ 'Private Key': /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
18
+ 'JWT Token': /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/g,
19
+ 'Generic Password': /(?:password|passwd|pwd|secret)\s*[=:]\s*['"][^'"]{8,}['"]/gi,
20
+ 'Database URL': /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/gi,
21
+ 'OpenAI Key': /sk-[a-zA-Z0-9]{48}/g,
22
+ 'Anthropic Key': /sk-ant-[a-zA-Z0-9_-]{40,}/g,
23
+ 'Discord Token': /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}/g,
24
+ 'Telegram Bot Token': /[0-9]{8,10}:[a-zA-Z0-9_-]{35}/g,
25
+ 'Azure Storage Key': /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{88}/g,
26
+ 'Cloudflare API Key': /[0-9a-f]{37}/g,
27
+ 'DigitalOcean Token': /dop_v1_[a-f0-9]{64}/g,
28
+ 'npm Token': /npm_[A-Za-z0-9]{36}/g,
29
+ };
30
+
31
+ export const XSS_PATTERNS = [
32
+ /v-html\s*=/g,
33
+ /dangerouslySetInnerHTML/g,
34
+ /innerHTML\s*=/g,
35
+ /document\.write\s*\(/g,
36
+ /\.html\s*\(\s*[^)]*\+/g,
37
+ /\$\{[^}]*\}\s*(?:<!--|\<)/g,
38
+ /outerHTML\s*=/g,
39
+ /insertAdjacentHTML/g,
40
+ ];
41
+
42
+ export const SQLI_PATTERNS = [
43
+ /`[^`]*\$\{[^}]*\}[^`]*(?:SELECT|INSERT|UPDATE|DELETE|WHERE|FROM|JOIN)/gi,
44
+ /['"][^'"]*\+\s*(?:req\.|params\.|query\.|body\.)/gi,
45
+ /(?:query|execute|raw)\s*\(\s*['"`][^'"`]*\$\{/gi,
46
+ /(?:query|execute|raw)\s*\(\s*['"`][^'"`]*\+\s*(?:req|params|query|body)/gi,
47
+ /String\.format\s*\(\s*['"][^'"]*(?:SELECT|INSERT|UPDATE|DELETE)/gi,
48
+ /f['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*\{/gi,
49
+ ];
50
+
51
+ export const COMMAND_INJECTION_PATTERNS = [
52
+ /exec\s*\(\s*['"`][^'"`]*\$\{/gi,
53
+ /exec\s*\(\s*['"`][^'"`]*\+\s*(?:req|params|query|body|user)/gi,
54
+ /spawn\s*\(\s*['"`][^'"`]*\$\{/gi,
55
+ /child_process/gi,
56
+ /eval\s*\(\s*(?:req|params|query|body|user)/gi,
57
+ /Function\s*\(\s*(?:req|params|query|body|user)/gi,
58
+ /execSync\s*\(\s*['"`][^'"`]*\$\{/gi,
59
+ /system\s*\(\s*['"`][^'"`]*\$\{/gi,
60
+ ];
61
+
62
+ export const SSRF_PATTERNS = [
63
+ /fetch\s*\(\s*(?:req|params|query|body|user)/gi,
64
+ /axios\s*\.\s*(?:get|post|put|delete)\s*\(\s*(?:req|params|query|body|user)/gi,
65
+ /request\s*\(\s*(?:req|params|query|body|user)/gi,
66
+ /urllib\.request\.urlopen\s*\(\s*(?:req|params|query|body|user)/gi,
67
+ /http\.get\s*\(\s*(?:req|params|query|body|user)/gi,
68
+ /new\s+URL\s*\(\s*(?:req|params|query|body|user)/gi,
69
+ ];
70
+
71
+ export const SSTI_PATTERNS = [
72
+ /render_template_string\s*\(\s*(?:req|params|query|body|user)/gi,
73
+ /Template\s*\(\s*(?:req|params|query|body|user)/gi,
74
+ /Jinja2\s*.*\s*render\s*\(\s*(?:req|params|query|body|user)/gi,
75
+ /nunjucks\s*.*\s*renderString\s*\(\s*(?:req|params|query|body|user)/gi,
76
+ /pug\s*.*\s*render\s*\(\s*(?:req|params|query|body|user)/gi,
77
+ /ejs\s*.*\s*render\s*\(\s*['"`][^'"`]*<%/gi,
78
+ ];
79
+
80
+ export const DESERIALIZATION_PATTERNS = [
81
+ /pickle\.loads?\s*\(/gi,
82
+ /yaml\.load\s*\(\s*[^)]*Loader\s*=\s*yaml\.(?:Unsafe|Full)Loader/gi,
83
+ /yaml\.load\s*\(\s*[^)]*(?!Loader)/gi,
84
+ /unserialize\s*\(/gi,
85
+ /ObjectInputStream/gi,
86
+ /Marshal\.load/gi,
87
+ /JSON\.parse\s*\(\s*(?:req|params|query|body)/gi,
88
+ /readObject\s*\(\s*\)/gi,
89
+ /BinaryFormatter/gi,
90
+ ];
91
+
92
+ export const PROTOTYPE_POLLUTION_PATTERNS = [
93
+ /Object\.assign\s*\(\s*\{\}/gi,
94
+ /\.\.\.\s*(?:req|params|query|body)/gi,
95
+ /merge\s*\(\s*[^,]+,\s*(?:req|params|query|body)/gi,
96
+ /deepmerge|lodash\.merge|_.merge/gi,
97
+ /__proto__/g,
98
+ /constructor\s*\[\s*['"]prototype['"]\s*\]/gi,
99
+ ];
100
+
101
+ export const PATH_TRAVERSAL_PATTERNS = [
102
+ /path\.join\s*\(\s*[^,]+,\s*(?:req|params|query|body)/gi,
103
+ /readFile(?:Sync)?\s*\(\s*(?:req|params|query|body)/gi,
104
+ /createReadStream\s*\(\s*(?:req|params|query|body)/gi,
105
+ /sendFile\s*\(\s*(?:req|params|query|body)/gi,
106
+ /res\.download\s*\(\s*(?:req|params|query|body)/gi,
107
+ /open\s*\(\s*(?:req|params|query|body)/gi,
108
+ ];
109
+
110
+ export const JWT_PATTERNS = [
111
+ /algorithm\s*:\s*['"]none['"]/gi,
112
+ /verify\s*:\s*false/gi,
113
+ /algorithms\s*:\s*\[\s*['"]HS256['"]/gi,
114
+ /jwt\.decode\s*\(/gi,
115
+ /ignoreExpiration\s*:\s*true/gi,
116
+ ];
117
+
118
+ export const HEADER_CHECKS = [
119
+ 'content-security-policy',
120
+ 'strict-transport-security',
121
+ 'x-frame-options',
122
+ 'x-content-type-options',
123
+ 'referrer-policy',
124
+ 'permissions-policy',
125
+ 'x-xss-protection',
126
+ 'cross-origin-opener-policy',
127
+ 'cross-origin-resource-policy',
128
+ 'cross-origin-embedder-policy',
129
+ ];
130
+
131
+ export const EXPOSED_FILES = [
132
+ '/.env',
133
+ '/.env.local',
134
+ '/.env.production',
135
+ '/.git/config',
136
+ '/.git/HEAD',
137
+ '/.gitignore',
138
+ '/package.json',
139
+ '/package-lock.json',
140
+ '/composer.json',
141
+ '/composer.lock',
142
+ '/.DS_Store',
143
+ '/wp-config.php',
144
+ '/web.config',
145
+ '/server-status',
146
+ '/server-info',
147
+ '/.htaccess',
148
+ '/.htpasswd',
149
+ '/phpinfo.php',
150
+ '/info.php',
151
+ '/debug',
152
+ '/_debug',
153
+ '/trace',
154
+ '/actuator',
155
+ '/actuator/health',
156
+ '/actuator/env',
157
+ '/actuator/heapdump',
158
+ '/graphql',
159
+ '/graphiql',
160
+ '/.well-known/security.txt',
161
+ '/robots.txt',
162
+ '/sitemap.xml',
163
+ '/crossdomain.xml',
164
+ '/clientaccesspolicy.xml',
165
+ '/elmah.axd',
166
+ '/trace.axd',
167
+ '/swagger.json',
168
+ '/swagger-ui.html',
169
+ '/api-docs',
170
+ '/openapi.json',
171
+ '/v1/api-docs',
172
+ '/v2/api-docs',
173
+ '/v3/api-docs',
174
+ '/.dockerenv',
175
+ '/Dockerfile',
176
+ '/docker-compose.yml',
177
+ '/backup.sql',
178
+ '/database.sql',
179
+ '/dump.sql',
180
+ '/adminer.php',
181
+ '/phpmyadmin',
182
+ '/_profiler',
183
+ '/telescope',
184
+ '/horizon',
185
+ ];
186
+
187
+ export const COMMON_SUBDOMAINS = [
188
+ 'admin', 'api', 'app', 'auth', 'beta', 'blog', 'cdn', 'ci',
189
+ 'cms', 'cpanel', 'dashboard', 'db', 'debug', 'dev', 'docker',
190
+ 'docs', 'email', 'ftp', 'git', 'grafana', 'graphql', 'help',
191
+ 'internal', 'jenkins', 'jira', 'k8s', 'kibana', 'login', 'mail',
192
+ 'manage', 'monitor', 'mysql', 'ns1', 'ns2', 'old', 'panel',
193
+ 'phpmyadmin', 'portal', 'postgres', 'prometheus', 'proxy', 'queue',
194
+ 'rabbitmq', 'redis', 'registry', 'remote', 'sentry', 'sftp',
195
+ 'shop', 'smtp', 'sonar', 'sso', 'stage', 'staging', 'status',
196
+ 'storage', 'store', 'support', 'test', 'testing', 'traefik',
197
+ 'vault', 'vpn', 'webmail', 'wiki', 'www', 'ws',
198
+ ];
199
+
200
+ export const COMMON_PORTS = [
201
+ 21, 22, 23, 25, 53, 80, 110, 143, 443, 445, 993, 995,
202
+ 1433, 1521, 2049, 2375, 2376, 3000, 3306, 3389, 4443,
203
+ 5000, 5432, 5900, 6379, 6443, 8000, 8080, 8443, 8888,
204
+ 9000, 9090, 9200, 9300, 27017, 27018, 50000,
205
+ ];