rav-xss 1.0.28
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/LICENSE +21 -0
- package/README.md +449 -0
- package/package.json +41 -0
- package/payloads/Basic/basic.txt +35 -0
- package/payloads/FilterEvasion/evasion.txt +20 -0
- package/payloads/Polyglots/polyglots.txt +11 -0
- package/payloads/PureReflex/reflex.txt +222 -0
- package/payloads/WAFBypass/wafbypass.txt +12 -0
- package/src/cli/args.js +57 -0
- package/src/cli/help.js +31 -0
- package/src/cli/wizard.js +196 -0
- package/src/config/colors.js +83 -0
- package/src/config/manager.js +112 -0
- package/src/core/browser.js +430 -0
- package/src/core/scanner.js +778 -0
- package/src/index.js +113 -0
- package/src/media/ravxss.png +0 -0
- package/src/utils/box.js +266 -0
- package/src/utils/helpers.js +22 -0
- package/src/utils/logger.js +44 -0
- package/src/utils/packageInfo.js +192 -0
- package/src/utils/reporter.js +56 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const { exec } = require("child_process");
|
|
7
|
+
const { parseArgs, hasHelp, shouldConfigure, shouldOpenReports } = require("./cli/args");
|
|
8
|
+
const { showHelp } = require("./cli/help");
|
|
9
|
+
const { runWizard } = require("./cli/wizard");
|
|
10
|
+
const { loadConfig, validateConfig } = require("./config/manager");
|
|
11
|
+
const { XSSScanner } = require("./core/scanner");
|
|
12
|
+
const { colors } = require("./config/colors");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 📁 Abre a pasta de relatórios no explorador de arquivos
|
|
16
|
+
* @param {string} reportDir - Caminho do diretório de relatórios
|
|
17
|
+
*/
|
|
18
|
+
const openReportsFolder = (reportDir) => {
|
|
19
|
+
const resolvedPath = path.resolve(reportDir);
|
|
20
|
+
|
|
21
|
+
console.log(`\n${colors.text("📂 Reports folder:")} ${colors.link(resolvedPath)}\n`);
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
24
|
+
console.log(colors.warning("⚠️ Reports folder doesn't exist yet. Creating..."));
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(resolvedPath, { recursive: true });
|
|
27
|
+
console.log(colors.success("✅ Reports folder created!"));
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.log(colors.error(`❌ Could not create folder: ${err.message}`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const platform = process.platform;
|
|
35
|
+
|
|
36
|
+
if (platform === "win32") {
|
|
37
|
+
exec(`explorer "${resolvedPath}"`);
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
console.log(colors.success("✅ Reports folder opened!"));
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}, 500);
|
|
42
|
+
} else if (platform === "darwin") {
|
|
43
|
+
exec(`open "${resolvedPath}"`, (error) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
console.log(colors.error(`❌ Could not open folder: ${error.message}`));
|
|
46
|
+
console.log(colors.text(`📂 Location: ${colors.link(resolvedPath)}`));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(colors.success("✅ Reports folder opened!"));
|
|
49
|
+
}
|
|
50
|
+
process.exit(0);
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
exec(`xdg-open "${resolvedPath}"`, (error) => {
|
|
54
|
+
if (error) {
|
|
55
|
+
console.log(colors.error(`❌ Could not open folder: ${error.message}`));
|
|
56
|
+
console.log(colors.text(`📂 Location: ${colors.link(resolvedPath)}`));
|
|
57
|
+
} else {
|
|
58
|
+
console.log(colors.success("✅ Reports folder opened!"));
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 🚀 Função principal
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
const main = async () => {
|
|
70
|
+
const args = parseArgs();
|
|
71
|
+
|
|
72
|
+
if (hasHelp(args)) {
|
|
73
|
+
showHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (shouldConfigure(args)) {
|
|
78
|
+
await runWizard();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (shouldOpenReports(args)) {
|
|
83
|
+
const config = loadConfig();
|
|
84
|
+
openReportsFolder(config.scanner.report_dir);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
if (!validateConfig(config)) {
|
|
90
|
+
console.error("Invalid configuration. Run --configure to set up.");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (args.mode) {
|
|
95
|
+
config.mode = args.mode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (args.headed) {
|
|
99
|
+
if (!config.scanner) config.scanner = {};
|
|
100
|
+
config.scanner.headless = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const scanner = new XSSScanner(config, args);
|
|
104
|
+
await scanner.run();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
main().catch((err) => {
|
|
108
|
+
console.error(`\n[FATAL] ${err.message}\n`);
|
|
109
|
+
if (process.argv.includes("--verbose")) {
|
|
110
|
+
console.error(err.stack);
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
Binary file
|
package/src/utils/box.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const boxen = require("boxen");
|
|
4
|
+
const { colors, theme } = require("../config/colors");
|
|
5
|
+
const packageInfo = require("./packageInfo");
|
|
6
|
+
|
|
7
|
+
class BoxManager {
|
|
8
|
+
|
|
9
|
+
get appInfo() {
|
|
10
|
+
return packageInfo.allInfo;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createBox(content, options = {}) {
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
padding: 1,
|
|
16
|
+
margin: 1,
|
|
17
|
+
borderStyle: "round",
|
|
18
|
+
borderColor: theme.border.primary,
|
|
19
|
+
textAlignment: "left"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
return boxen(content, { ...defaultOptions, ...options });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const width = options.width || 60;
|
|
26
|
+
const border = "─".repeat(width);
|
|
27
|
+
return `\n┌${border}┐\n${content}\n└${border}┘\n`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
createWelcomeBox(config, totalPayloads, category, targetUrl) {
|
|
32
|
+
const version = packageInfo.version || "1.0.0";
|
|
33
|
+
const contentLines = [];
|
|
34
|
+
|
|
35
|
+
contentLines.push(colors.info.bold("🛡️ Bug Bounty Edition"));
|
|
36
|
+
contentLines.push(colors.dim("─".repeat(45)));
|
|
37
|
+
contentLines.push("");
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const figlet = require("figlet");
|
|
41
|
+
const bannerText = figlet.textSync(packageInfo.name.toUpperCase(), {
|
|
42
|
+
font: "ANSI Shadow",
|
|
43
|
+
horizontalLayout: "default",
|
|
44
|
+
verticalLayout: "default"
|
|
45
|
+
});
|
|
46
|
+
contentLines.push(colors.action(bannerText));
|
|
47
|
+
} catch (e) {
|
|
48
|
+
contentLines.push(colors.action.bold(`⚡ ${packageInfo.name.toUpperCase()} ⚡`));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
contentLines.push("");
|
|
52
|
+
contentLines.push(`${colors.icon.link} ${colors.link(packageInfo.site)}`);
|
|
53
|
+
contentLines.push("");
|
|
54
|
+
|
|
55
|
+
const infoItems = [
|
|
56
|
+
`${colors.icon.version} ${colors.muted("Version:")} ${colors.primary.bold("v" + version)}`,
|
|
57
|
+
`${colors.icon.payload} ${colors.muted("Payloads:")} ${colors.primary.bold(String(totalPayloads))}`,
|
|
58
|
+
`${colors.icon.category} ${colors.muted("Category:")} ${colors.highlight(category || "Not selected")}`,
|
|
59
|
+
`${colors.icon.target} ${colors.muted("Target:")} ${colors.url(this.truncateUrl(targetUrl))}`
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
contentLines.push(infoItems.join(`\n`));
|
|
63
|
+
|
|
64
|
+
const content = contentLines.join("\n");
|
|
65
|
+
|
|
66
|
+
return this.createBox(content, {
|
|
67
|
+
borderStyle: "round",
|
|
68
|
+
borderColor: theme.border.primary,
|
|
69
|
+
padding: {
|
|
70
|
+
top: 1,
|
|
71
|
+
bottom: 2,
|
|
72
|
+
left: 3,
|
|
73
|
+
right: 3
|
|
74
|
+
},
|
|
75
|
+
margin: 1,
|
|
76
|
+
textAlignment: "center"
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
createTargetBox(target, index, total) {
|
|
81
|
+
const formattedContent = [
|
|
82
|
+
`${colors.primary.bold(`Target ${index + 1} of ${total}`)} ${colors.dim("•")} ${colors.title(target.name || "CLI Target")}`,
|
|
83
|
+
colors.text(target.url)
|
|
84
|
+
].join("\n");
|
|
85
|
+
|
|
86
|
+
return this.createBox(formattedContent, {
|
|
87
|
+
borderStyle: "round",
|
|
88
|
+
borderColor: theme.border.info,
|
|
89
|
+
padding: 1,
|
|
90
|
+
margin: { top: 1, bottom: 1 }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 📊 Cria box de resultado do scan
|
|
96
|
+
* @param {Object} results - Resultados do scan
|
|
97
|
+
* @param {string} targetUrl - URL alvo
|
|
98
|
+
* @param {string} category - Categoria testada
|
|
99
|
+
* @param {string} duration - Duração do scan
|
|
100
|
+
* @param {string} reportPath - Caminho do relatório
|
|
101
|
+
* @param {string} reportDir - Diretório de relatórios
|
|
102
|
+
* @returns {string} Box formatado
|
|
103
|
+
*/
|
|
104
|
+
createResultBox(results, targetUrl, category, duration, reportPath, reportDir) {
|
|
105
|
+
const hasVulns = results.vulns_found > 0;
|
|
106
|
+
const statusIcon = hasVulns ? colors.icon.error : colors.icon.success;
|
|
107
|
+
const statusColor = hasVulns ? colors.error : colors.success;
|
|
108
|
+
const statusText = hasVulns ? `${results.vulns_found} XSS FOUND` : "ALL CLEAN";
|
|
109
|
+
|
|
110
|
+
const isTermux = this.detectTermux();
|
|
111
|
+
const maxWidth = isTermux ? 80 : 80;
|
|
112
|
+
const boxWidth = isTermux ? 90 : 100;
|
|
113
|
+
|
|
114
|
+
const displayReportPath = this.wrapPath(reportPath, maxWidth, "Report");
|
|
115
|
+
const displayReportDir = this.wrapPath(reportDir, maxWidth, "Reports folder");
|
|
116
|
+
|
|
117
|
+
const contentLines = [
|
|
118
|
+
`${colors.action.bold(`${statusIcon} SCAN COMPLETE`)}`,
|
|
119
|
+
"",
|
|
120
|
+
`${colors.muted("Target")} ${colors.url(this.truncateUrl(targetUrl, maxWidth))}`,
|
|
121
|
+
`${colors.muted("Category")} ${colors.highlight(category)}`,
|
|
122
|
+
`${colors.muted("Tests")} ${colors.primary.bold(String(results.total_tests))}`,
|
|
123
|
+
`${colors.muted("Duration")} ${colors.highlight.bold(duration + "s")}`,
|
|
124
|
+
`${colors.muted("Result")} ${statusColor.bold(statusText)}`,
|
|
125
|
+
"",
|
|
126
|
+
`${colors.muted("📄 Report:")}`,
|
|
127
|
+
`${colors.link(displayReportPath)}`,
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
if (reportDir) {
|
|
131
|
+
contentLines.push("");
|
|
132
|
+
contentLines.push(`${colors.muted("📂 Reports folder:")}`);
|
|
133
|
+
contentLines.push(`${colors.dim(displayReportDir)}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
contentLines.push("");
|
|
137
|
+
contentLines.push(`${colors.dim("─".repeat(50))}`);
|
|
138
|
+
contentLines.push("");
|
|
139
|
+
|
|
140
|
+
if (isTermux && reportDir) {
|
|
141
|
+
contentLines.push(`${colors.muted("📋 List reports (Termux):")}`);
|
|
142
|
+
contentLines.push(`${colors.action.bold(` ls -la ${reportDir}/`)}`);
|
|
143
|
+
contentLines.push("");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
contentLines.push(`${colors.text("📁 To open reports folder:")}`);
|
|
147
|
+
contentLines.push(`${colors.action.bold(" rav-xss --open-reports")}`);
|
|
148
|
+
contentLines.push(`${colors.muted(" or")}`);
|
|
149
|
+
contentLines.push(`${colors.action.bold(" rav-xss -r")}`);
|
|
150
|
+
|
|
151
|
+
return this.createBox(contentLines.join("\n"), {
|
|
152
|
+
borderStyle: "double",
|
|
153
|
+
padding: isTermux ? { top: 1, bottom: 1, left: 2, right: 2 } : 2,
|
|
154
|
+
margin: { top: 2, bottom: 1 },
|
|
155
|
+
borderColor: hasVulns ? theme.border.error : theme.border.success,
|
|
156
|
+
width: isTermux ? boxWidth : undefined
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 📱 Detecta se está executando no Termux
|
|
162
|
+
* @returns {boolean} true se estiver no Termux
|
|
163
|
+
*/
|
|
164
|
+
detectTermux() {
|
|
165
|
+
if (process.env.TERMUX_VERSION) return true;
|
|
166
|
+
if (process.env.PREFIX?.includes("com.termux")) return true;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const os = require("os");
|
|
170
|
+
const hostname = os.hostname();
|
|
171
|
+
if (hostname && (
|
|
172
|
+
hostname.toLowerCase().includes("termux") ||
|
|
173
|
+
hostname.toLowerCase().includes("android")
|
|
174
|
+
)) return true;
|
|
175
|
+
} catch (e) { }
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const fs = require("fs");
|
|
179
|
+
if (fs.existsSync("/data/data/com.termux")) return true;
|
|
180
|
+
} catch (e) { }
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 📏 Formata caminho com quebra de linha se necessário
|
|
187
|
+
* @param {string} filePath - Caminho completo
|
|
188
|
+
* @param {number} maxLength - Tamanho máximo por linha
|
|
189
|
+
* @param {string} label - Rótulo para indentação
|
|
190
|
+
* @returns {string} Caminho formatado
|
|
191
|
+
*/
|
|
192
|
+
wrapPath(filePath, maxLength = 55, label = "") {
|
|
193
|
+
if (!filePath) return "N/A";
|
|
194
|
+
|
|
195
|
+
const indent = " ".repeat(14);
|
|
196
|
+
|
|
197
|
+
if (filePath.length <= maxLength) {
|
|
198
|
+
return filePath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const parts = [];
|
|
202
|
+
let remaining = filePath;
|
|
203
|
+
let firstLine = true;
|
|
204
|
+
|
|
205
|
+
while (remaining.length > 0) {
|
|
206
|
+
if (remaining.length <= maxLength) {
|
|
207
|
+
parts.push(remaining);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let splitPos = remaining.lastIndexOf("/", maxLength);
|
|
212
|
+
if (splitPos === -1 || splitPos < maxLength / 2) {
|
|
213
|
+
splitPos = remaining.lastIndexOf("\\", maxLength);
|
|
214
|
+
}
|
|
215
|
+
if (splitPos === -1 || splitPos < maxLength / 2) {
|
|
216
|
+
splitPos = maxLength;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
parts.push(remaining.substring(0, splitPos + 1));
|
|
220
|
+
remaining = remaining.substring(splitPos + 1);
|
|
221
|
+
firstLine = false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const formattedParts = parts.map((part, index) => {
|
|
225
|
+
if (index === 0) return part;
|
|
226
|
+
return indent + part;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return formattedParts.join("\n");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
createExitBox() {
|
|
233
|
+
const content = [
|
|
234
|
+
colors.highlight.bold("👋 GOODBYE!"),
|
|
235
|
+
"",
|
|
236
|
+
colors.text(packageInfo.name),
|
|
237
|
+
colors.muted("Authorized testing only"),
|
|
238
|
+
"",
|
|
239
|
+
`${colors.text("Feito com")} ${colors.danger("💚")} ${colors.text("por")} ${colors.primary.bold(packageInfo.wuser)}`,
|
|
240
|
+
"",
|
|
241
|
+
`${colors.icon.link} ${colors.link(packageInfo.site)}`
|
|
242
|
+
].join("\n");
|
|
243
|
+
|
|
244
|
+
return this.createBox(content, {
|
|
245
|
+
borderStyle: "round",
|
|
246
|
+
borderColor: theme.border.warning,
|
|
247
|
+
padding: 2,
|
|
248
|
+
margin: 1,
|
|
249
|
+
textAlignment: "center"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 🔗 Trunca a URL para exibição compacta
|
|
255
|
+
* @param {string} url - URL completa
|
|
256
|
+
* @param {number} maxLength - Comprimento máximo
|
|
257
|
+
* @returns {string} URL truncada
|
|
258
|
+
*/
|
|
259
|
+
truncateUrl(url, maxLength = 55) {
|
|
260
|
+
if (!url) return "N/A";
|
|
261
|
+
if (url.length <= maxLength) return url;
|
|
262
|
+
return url.substring(0, maxLength - 3) + "...";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
module.exports = new BoxManager();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
|
|
8
|
+
const ensureDir = (dirPath) => {
|
|
9
|
+
if (!fs.existsSync(dirPath)) {
|
|
10
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const timestamp = () => new Date().toISOString().replace(/[:.]/g, "-");
|
|
16
|
+
|
|
17
|
+
const loadPayloads = (filePath) => {
|
|
18
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
19
|
+
return content.split("\n").map(l => l.trim()).filter(Boolean);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = { sleep, ensureDir, timestamp, loadPayloads };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { colors } = require("../config/colors");
|
|
4
|
+
const boxManager = require("./box");
|
|
5
|
+
const packageInfo = require("./packageInfo");
|
|
6
|
+
|
|
7
|
+
class Logger {
|
|
8
|
+
static log(level, msg) {
|
|
9
|
+
const styles = {
|
|
10
|
+
info: `${colors.action.bold(" ℹ ")}${colors.text(msg)}`,
|
|
11
|
+
vuln: `${colors.error.bold(" ⚠ ")}${colors.danger(msg)}`,
|
|
12
|
+
safe: `${colors.success(" ✓ ")}${colors.muted(msg)}`,
|
|
13
|
+
warn: `${colors.warning.bold(" ⚡ ")}${colors.highlight2(msg)}`,
|
|
14
|
+
done: `${colors.success.bold(" ✔ ")}${colors.text(msg)}`,
|
|
15
|
+
error: `${colors.error.bold(" ✗ ")}${colors.danger(msg)}`
|
|
16
|
+
};
|
|
17
|
+
console.log(styles[level] || ` ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static showBanner(config, totalPayloads, category, targetUrl) {
|
|
21
|
+
console.clear();
|
|
22
|
+
const banner = boxManager.createWelcomeBox(config, totalPayloads, category, targetUrl);
|
|
23
|
+
console.log(banner);
|
|
24
|
+
console.log(colors.dim("\n" + "─".repeat(60) + "\n"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static showTarget(target, index, total) {
|
|
28
|
+
const box = boxManager.createTargetBox(target, index, total);
|
|
29
|
+
console.log(box);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static showResults(results, targetUrl, category, duration, reportPath, reportDir) {
|
|
33
|
+
const box = boxManager.createResultBox(results, targetUrl, category, duration, reportPath, reportDir);
|
|
34
|
+
console.log(box);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static showExit() {
|
|
38
|
+
console.clear();
|
|
39
|
+
const box = boxManager.createExitBox();
|
|
40
|
+
console.log(box);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { Logger };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 📄 Carrega informações do package.json
|
|
8
|
+
*/
|
|
9
|
+
class PackageInfo {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.packageData = this.loadPackageInfo();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Carrega informações do package.json
|
|
16
|
+
* @returns {Object} Dados do package.json
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
loadPackageInfo() {
|
|
20
|
+
try {
|
|
21
|
+
const possiblePaths = [
|
|
22
|
+
path.join(__dirname, "..", "..", "package.json"),
|
|
23
|
+
path.join(__dirname, "..", "package.json"),
|
|
24
|
+
path.join(process.cwd(), "package.json"),
|
|
25
|
+
path.join(__dirname, "package.json"),
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
let packagePath = null;
|
|
29
|
+
let packageJson = null;
|
|
30
|
+
|
|
31
|
+
const triedPaths = [];
|
|
32
|
+
|
|
33
|
+
for (const testPath of possiblePaths) {
|
|
34
|
+
triedPaths.push(testPath);
|
|
35
|
+
if (fs.existsSync(testPath)) {
|
|
36
|
+
packagePath = testPath;
|
|
37
|
+
packageJson = fs.readFileSync(testPath, "utf8");
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!packagePath || !packageJson) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`package.json not found!\n` +
|
|
45
|
+
`Tried paths:\n` +
|
|
46
|
+
triedPaths.map(p => ` - ${p} (${fs.existsSync(p) ? 'EXISTS' : 'NOT FOUND'})`).join("\n")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = JSON.parse(packageJson);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
name: data.name || "rav-xss",
|
|
54
|
+
version: data.version || "V1",
|
|
55
|
+
wuser: "RavenaStar",
|
|
56
|
+
site: "https://ravenastar.com",
|
|
57
|
+
description: data.description || "⚙️ CLI/NPM | RAV XSS | 🎯 Basic Reflected XSS scanner for bug bounty programs.",
|
|
58
|
+
author: data.author || "RavenaStar",
|
|
59
|
+
license: data.license || "MIT",
|
|
60
|
+
homepage: data.homepage || "https://github.com/ravenastar-js/rav-xss/"
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (process.argv.includes("--verbose") || process.env.NODE_ENV === "development") {
|
|
64
|
+
console.error("❌ Erro ao carregar package.json:", error.message);
|
|
65
|
+
}
|
|
66
|
+
return this.getFallbackInfo();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Informações de fallback
|
|
72
|
+
* @returns {Object} Dados padrão
|
|
73
|
+
*/
|
|
74
|
+
getFallbackInfo() {
|
|
75
|
+
return {
|
|
76
|
+
name: "rav-xss",
|
|
77
|
+
version: "V1",
|
|
78
|
+
wuser: "RavenaStar",
|
|
79
|
+
site: "https://ravenastar.com",
|
|
80
|
+
description: "⚙️ CLI/NPM | RAV XSS | 🎯 Basic Reflected XSS scanner for bug bounty programs.",
|
|
81
|
+
author: "ravenastar-js",
|
|
82
|
+
license: "MIT",
|
|
83
|
+
homepage: "https://github.com/ravenastar-js/rav-xss/"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Força recarregar do disco (útil se o package.json foi atualizado)
|
|
89
|
+
*/
|
|
90
|
+
reload() {
|
|
91
|
+
this.packageData = this.loadPackageInfo();
|
|
92
|
+
return this.packageData;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Retorna todas as informações
|
|
97
|
+
* @returns {Object} Todas as informações
|
|
98
|
+
*/
|
|
99
|
+
get allInfo() {
|
|
100
|
+
return this.packageData;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retorna o nome
|
|
105
|
+
* @returns {string} Nome do pacote
|
|
106
|
+
*/
|
|
107
|
+
get name() {
|
|
108
|
+
return this.packageData.name;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Retorna a versão
|
|
113
|
+
* @returns {string} Versão
|
|
114
|
+
*/
|
|
115
|
+
get version() {
|
|
116
|
+
return this.packageData.version;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Retorna a descrição
|
|
121
|
+
* @returns {string} Descrição
|
|
122
|
+
*/
|
|
123
|
+
get description() {
|
|
124
|
+
return this.packageData.description;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Retorna o autor
|
|
129
|
+
* @returns {string} Autor
|
|
130
|
+
*/
|
|
131
|
+
get author() {
|
|
132
|
+
return this.packageData.author;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Retorna o usuário/criador
|
|
137
|
+
* @returns {string} wuser
|
|
138
|
+
*/
|
|
139
|
+
get wuser() {
|
|
140
|
+
return this.packageData.wuser;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Retorna o site
|
|
145
|
+
* @returns {string} Site
|
|
146
|
+
*/
|
|
147
|
+
get site() {
|
|
148
|
+
return this.packageData.site;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Retorna a licença
|
|
153
|
+
* @returns {string} Licença
|
|
154
|
+
*/
|
|
155
|
+
get license() {
|
|
156
|
+
return this.packageData.license;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Retorna a homepage
|
|
161
|
+
* @returns {string} Homepage
|
|
162
|
+
*/
|
|
163
|
+
get homepage() {
|
|
164
|
+
return this.packageData.homepage;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Debug: Mostra qual caminho foi usado
|
|
169
|
+
* @returns {string} Caminho do package.json
|
|
170
|
+
*/
|
|
171
|
+
get debugPath() {
|
|
172
|
+
try {
|
|
173
|
+
const possiblePaths = [
|
|
174
|
+
path.join(__dirname, "..", "..", "package.json"),
|
|
175
|
+
path.join(__dirname, "..", "package.json"),
|
|
176
|
+
path.join(process.cwd(), "package.json"),
|
|
177
|
+
path.join(__dirname, "package.json"),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (const testPath of possiblePaths) {
|
|
181
|
+
if (fs.existsSync(testPath)) {
|
|
182
|
+
return `FOUND: ${testPath}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return "NOT FOUND in any path";
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return "ERROR: " + e.message;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = new PackageInfo();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { timestamp } = require("./helpers");
|
|
6
|
+
|
|
7
|
+
class Reporter {
|
|
8
|
+
constructor(reportDir) {
|
|
9
|
+
this.reportDir = path.resolve(reportDir);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
generateTextReport(results, targetUrl) {
|
|
13
|
+
const duration = ((new Date(results.scan_end) - new Date(results.scan_start)) / 1000).toFixed(1);
|
|
14
|
+
let report = "";
|
|
15
|
+
report += "═".repeat(60) + "\n";
|
|
16
|
+
report += " XSS REFLECTION SCANNER — REPORT\n";
|
|
17
|
+
report += "═".repeat(60) + "\n\n";
|
|
18
|
+
report += ` Date : ${new Date(results.scan_start).toLocaleString("en-US")}\n`;
|
|
19
|
+
report += ` Target : ${targetUrl}\n`;
|
|
20
|
+
report += ` Duration : ${duration}s\n`;
|
|
21
|
+
report += ` Tests : ${results.total_tests}\n`;
|
|
22
|
+
report += ` Vulnerable : ${results.vulns_found}\n`;
|
|
23
|
+
report += "\n" + "─".repeat(60) + "\n\n";
|
|
24
|
+
|
|
25
|
+
if (results.findings.length > 0) {
|
|
26
|
+
report += " FINDINGS:\n";
|
|
27
|
+
report += " " + "─".repeat(55) + "\n";
|
|
28
|
+
for (const f of results.findings) {
|
|
29
|
+
report += `\n [#${f.index}] ${f.payload}\n`;
|
|
30
|
+
report += ` URL: ${f.url}\n`;
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
report += " ✓ No reflected XSS detected.\n";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
report += "\n" + "═".repeat(60) + "\n";
|
|
37
|
+
report += ` Completed: ${new Date(results.scan_end).toLocaleString("en-US")}\n`;
|
|
38
|
+
report += " Authorized testing only.\n";
|
|
39
|
+
report += "═".repeat(60) + "\n";
|
|
40
|
+
return report;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
saveReport(results, targetUrl) {
|
|
44
|
+
if (!fs.existsSync(this.reportDir)) {
|
|
45
|
+
fs.mkdirSync(this.reportDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ts = timestamp();
|
|
49
|
+
const textReport = this.generateTextReport(results, targetUrl);
|
|
50
|
+
const textPath = path.join(this.reportDir, `xss_report_${ts}.txt`).replace(/\\/g, "/");
|
|
51
|
+
fs.writeFileSync(textPath, textReport);
|
|
52
|
+
return { textPath };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { Reporter };
|