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.
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const packageInfo = require("../utils/packageInfo");
6
+
7
+ const BASE_DIR = path.join(__dirname, "..", "..");
8
+ const CONFIG_PATH = path.join(BASE_DIR, "config.json");
9
+
10
+ /**
11
+ * 🏭 Obtém configuração padrão com valores fallback
12
+ * @returns {Object} Configuração padrão completa
13
+ */
14
+ const getDefaultConfig = () => ({
15
+ targets: [
16
+ {
17
+ name: "Default Target",
18
+ url: "http://www.sudo.co.il/xss/level0.php?email=[XSS]",
19
+ notes: "Example target"
20
+ }
21
+ ],
22
+ scanner: {
23
+ user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
24
+ timeout_ms: 8000,
25
+ delay_between_requests_ms: 500,
26
+ report_dir: path.join(BASE_DIR, "reports")
27
+ },
28
+ output: {
29
+ verbose: false,
30
+ show_safe: false
31
+ }
32
+ });
33
+
34
+ /**
35
+ * 📂 Carrega configuração do arquivo config.json
36
+ * @returns {Object} Configuração carregada ou padrão
37
+ */
38
+ const loadConfig = () => {
39
+ if (!fs.existsSync(CONFIG_PATH)) {
40
+ const defaultConfig = getDefaultConfig();
41
+ saveConfig(defaultConfig);
42
+ return defaultConfig;
43
+ }
44
+
45
+ try {
46
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
47
+ const merged = { ...getDefaultConfig(), ...config };
48
+
49
+ if (!merged.targets || merged.targets.length === 0) {
50
+ merged.targets = getDefaultConfig().targets;
51
+ }
52
+
53
+ if (merged.scanner?.report_dir && !path.isAbsolute(merged.scanner.report_dir)) {
54
+ merged.scanner.report_dir = path.join(BASE_DIR, merged.scanner.report_dir);
55
+ }
56
+
57
+ return merged;
58
+ } catch (err) {
59
+ console.error(`Error loading config: ${err.message}`);
60
+ return getDefaultConfig();
61
+ }
62
+ };
63
+
64
+ /**
65
+ * 💾 Salva configuração no arquivo config.json
66
+ * @param {Object} config - Configuração a ser salva
67
+ */
68
+ const saveConfig = (config) => {
69
+ try {
70
+ const dir = path.dirname(CONFIG_PATH);
71
+ if (!fs.existsSync(dir)) {
72
+ fs.mkdirSync(dir, { recursive: true });
73
+ }
74
+
75
+ const configToSave = { ...config };
76
+ delete configToSave.version;
77
+
78
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(configToSave, null, 2));
79
+ } catch (err) {
80
+ console.error(`Error saving config: ${err.message}`);
81
+ }
82
+ };
83
+
84
+ /**
85
+ * ✅ Valida a configuração e cria diretórios necessários
86
+ * @param {Object} config - Configuração a validar
87
+ * @returns {boolean} true se válida
88
+ */
89
+ const validateConfig = (config) => {
90
+ if (config.targets && config.targets.length > 0) {
91
+ for (const target of config.targets) {
92
+ if (!target.url || !target.url.includes("[XSS]")) {
93
+ console.error(`Invalid target URL: ${target.url}`);
94
+ return false;
95
+ }
96
+ }
97
+ }
98
+
99
+ const reportDir = config.scanner.report_dir;
100
+ if (reportDir && !fs.existsSync(reportDir)) {
101
+ try {
102
+ fs.mkdirSync(reportDir, { recursive: true });
103
+ } catch (err) {
104
+ console.error(`Error creating report directory: ${err.message}`);
105
+ return false;
106
+ }
107
+ }
108
+
109
+ return true;
110
+ };
111
+
112
+ module.exports = { loadConfig, saveConfig, validateConfig, getDefaultConfig };
@@ -0,0 +1,430 @@
1
+ "use strict";
2
+
3
+ const axios = require("axios");
4
+ const os = require("os");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+ const { execSync } = require("child_process");
8
+
9
+ let playwright = null;
10
+ try {
11
+ if (!isTermux()) {
12
+ playwright = require("playwright");
13
+ }
14
+ } catch (e) {
15
+ playwright = null;
16
+ }
17
+
18
+ /**
19
+ * 🖥️ Gerenciador de Modos de Execução
20
+ *
21
+ * Gerencia requisições HTTP via Axios (Modo Normal) e navegação
22
+ * via Playwright (Modo Navegador) com detecção automática de ambiente.
23
+ */
24
+ class BrowserManager {
25
+ constructor(config, args) {
26
+ this.config = config;
27
+ this.args = args;
28
+ this.mode = args.mode || config.mode || "axios";
29
+
30
+ if (isTermux()) {
31
+ this.mode = "axios";
32
+ }
33
+
34
+ if (this.mode === "playwright" && !playwright) {
35
+ this.mode = "axios";
36
+ }
37
+
38
+ this.browser = null;
39
+ this.responses = [];
40
+ this._browsersChecked = false;
41
+ }
42
+
43
+ /**
44
+ * 🔧 Verifica se os navegadores do Playwright estão instalados
45
+ * @returns {boolean} true se os navegadores estão disponíveis
46
+ */
47
+ arePlaywrightBrowsersInstalled() {
48
+ if (!playwright) return false;
49
+
50
+ try {
51
+ const registryPath = this.getPlaywrightBrowserPath();
52
+ if (!registryPath) return false;
53
+
54
+ return fs.existsSync(registryPath);
55
+ } catch (e) {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * 📁 Obtém o caminho esperado para os navegadores do Playwright
62
+ * @returns {string|null} Caminho do diretório de navegadores
63
+ */
64
+ getPlaywrightBrowserPath() {
65
+ try {
66
+ const os = require("os");
67
+ const home = os.homedir();
68
+
69
+ if (process.platform === "win32") {
70
+ const localAppData = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
71
+ return path.join(localAppData, "ms-playwright");
72
+ } else if (process.platform === "darwin") {
73
+ return path.join(home, "Library", "Caches", "ms-playwright");
74
+ } else {
75
+ const cacheDir = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
76
+ return path.join(cacheDir, "ms-playwright");
77
+ }
78
+ } catch (e) {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 🔧 Verifica e instala os navegadores do Playwright se necessário
85
+ * @returns {boolean} true se os navegadores estão prontos
86
+ */
87
+ ensurePlaywrightBrowsers() {
88
+ if (this._browsersChecked) return true;
89
+ if (!playwright) return false;
90
+
91
+ const installed = this.arePlaywrightBrowsersInstalled();
92
+
93
+ if (installed) {
94
+ this._browsersChecked = true;
95
+ return true;
96
+ }
97
+
98
+ console.log("\n" + [
99
+ "═".repeat(55),
100
+ " ⚠️ Playwright browsers not found!",
101
+ " Installing Chromium automatically...",
102
+ "═".repeat(55)
103
+ ].join("\n"));
104
+
105
+ try {
106
+ execSync("npx playwright install chromium", {
107
+ stdio: "inherit",
108
+ timeout: 120000
109
+ });
110
+ console.log(" ✅ Chromium installed successfully!\n");
111
+ this._browsersChecked = true;
112
+ return true;
113
+ } catch (installError) {
114
+ console.log([
115
+ "",
116
+ " ❌ Failed to install Chromium automatically.",
117
+ " Please install manually with:",
118
+ " npx playwright install chromium",
119
+ ""
120
+ ].join("\n"));
121
+ this._browsersChecked = true;
122
+ return false;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 🚀 Inicializa o navegador Playwright
128
+ * @returns {Promise<Object>} Instância do navegador
129
+ */
130
+ async launch() {
131
+ if (this.mode !== "playwright") return null;
132
+
133
+ if (!this.ensurePlaywrightBrowsers()) {
134
+ this.mode = "axios";
135
+ return null;
136
+ }
137
+
138
+ const browserType = playwright.chromium;
139
+
140
+ this.browser = await browserType.launch({
141
+ headless: this.config.scanner.headless !== false,
142
+ args: [
143
+ '--no-sandbox',
144
+ '--disable-setuid-sandbox',
145
+ '--disable-dev-shm-usage'
146
+ ]
147
+ });
148
+
149
+ return this.browser;
150
+ }
151
+
152
+ /**
153
+ * 🔒 Fecha a instância do navegador
154
+ * @returns {Promise<void>}
155
+ */
156
+ async close() {
157
+ if (this.browser) {
158
+ await this.browser.close();
159
+ this.browser = null;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 🌐 Cria um novo contexto de navegação
165
+ * @returns {Promise<Object>} Contexto do navegador
166
+ */
167
+ async createContext() {
168
+ if (!this.browser) return null;
169
+
170
+ return await this.browser.newContext({
171
+ userAgent: this.config.scanner.user_agent,
172
+ ignoreHTTPSErrors: true
173
+ });
174
+ }
175
+
176
+ /**
177
+ * 📄 Cria uma nova página com interceptação de recursos
178
+ * @param {Object} context - Contexto do navegador
179
+ * @returns {Promise<Object>} Página configurada
180
+ */
181
+ async createPage(context) {
182
+ if (!context) return null;
183
+
184
+ const page = await context.newPage();
185
+
186
+ await page.route("**/*.{png,jpg,jpeg,gif,woff,woff2,svg,css,ico,font}",
187
+ (route) => route.abort()
188
+ );
189
+
190
+ await page.route("**/*", (route, request) => {
191
+ this.captureResponse(request);
192
+ route.continue();
193
+ });
194
+
195
+ page.on("response", (response) => {
196
+ this.capturePlaywrightResponse(response);
197
+ });
198
+
199
+ return page;
200
+ }
201
+
202
+ /**
203
+ * 📡 Executa requisição HTTP no modo Axios
204
+ * @param {string} url - URL alvo
205
+ * @param {Object} options - Opções da requisição
206
+ * @returns {Promise<Object>} Resposta da requisição
207
+ */
208
+ async axiosRequest(url, options = {}) {
209
+ const config = {
210
+ timeout: this.config.scanner.timeout_ms || 8000,
211
+ headers: {
212
+ "User-Agent": this.config.scanner.user_agent,
213
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
214
+ "Accept-Language": "en-US,en;q=0.5",
215
+ ...options.headers
216
+ },
217
+ validateStatus: () => true,
218
+ maxRedirects: 5,
219
+ ...options
220
+ };
221
+
222
+ try {
223
+ const response = await axios.get(url, config);
224
+
225
+ this.responses.push({
226
+ url: url,
227
+ status: response.status,
228
+ headers: response.headers,
229
+ body: response.data,
230
+ timestamp: new Date().toISOString()
231
+ });
232
+
233
+ return response;
234
+ } catch (error) {
235
+ return {
236
+ status: 0,
237
+ data: "",
238
+ headers: {},
239
+ error: error.message
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * 🎭 Executa navegação no modo Playwright
246
+ * @param {string} url - URL alvo
247
+ * @param {Object} options - Opções de navegação
248
+ * @returns {Promise<Object>} Resultado da navegação
249
+ */
250
+ async playwrightRequest(url, options = {}) {
251
+ if (!this.browser) {
252
+ await this.launch();
253
+ }
254
+
255
+ if (this.mode === "axios") {
256
+ return await this.axiosRequest(url, options);
257
+ }
258
+
259
+ const context = await this.createContext();
260
+ const page = await this.createPage(context);
261
+
262
+ let dialogMessage = null;
263
+ let dialogHandled = false;
264
+
265
+ page.on("dialog", async (dialog) => {
266
+ dialogMessage = dialog.message();
267
+ dialogHandled = true;
268
+ await dialog.accept();
269
+ });
270
+
271
+ try {
272
+ const response = await page.goto(url, {
273
+ waitUntil: "domcontentloaded",
274
+ timeout: this.config.scanner.timeout_ms || 8000,
275
+ ...options
276
+ });
277
+
278
+ await page.waitForTimeout(1000);
279
+
280
+ const body = await page.content();
281
+
282
+ this.responses.push({
283
+ url: url,
284
+ status: response?.status() || 0,
285
+ headers: response?.headers() || {},
286
+ body: body,
287
+ timestamp: new Date().toISOString(),
288
+ dialogMessage: dialogMessage
289
+ });
290
+
291
+ const result = {
292
+ status: response?.status() || 0,
293
+ data: body,
294
+ headers: response?.headers() || {},
295
+ dialogMessage: dialogMessage
296
+ };
297
+
298
+ if (this.config.scanner.headless === false) {
299
+ await page.waitForTimeout(1500);
300
+ }
301
+
302
+ await context.close();
303
+ return result;
304
+ } catch (error) {
305
+ await context.close();
306
+ return {
307
+ status: 0,
308
+ data: "",
309
+ headers: {},
310
+ error: error.message
311
+ };
312
+ }
313
+ }
314
+
315
+ /**
316
+ * 🎯 Executa requisição no modo apropriado
317
+ * @param {string} url - URL alvo
318
+ * @param {Object} options - Opções da requisição
319
+ * @returns {Promise<Object>} Resposta da requisição
320
+ */
321
+ async request(url, options = {}) {
322
+ if (this.mode === "playwright") {
323
+ return await this.playwrightRequest(url, options);
324
+ }
325
+
326
+ return await this.axiosRequest(url, options);
327
+ }
328
+
329
+ /**
330
+ * 🔍 Captura detalhes da requisição Playwright
331
+ * @param {Object} request - Objeto de requisição Playwright
332
+ */
333
+ captureResponse(request) {
334
+ if (!request) return;
335
+
336
+ if (this.args.verbose) {
337
+ console.log(` 📤 Request: ${request.method()} ${request.url()}`);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 📥 Captura detalhes da resposta Playwright
343
+ * @param {Object} response - Objeto de resposta Playwright
344
+ */
345
+ capturePlaywrightResponse(response) {
346
+ if (!response) return;
347
+
348
+ if (this.args.verbose) {
349
+ console.log(` 📥 Response: ${response.status()} ${response.url()}`);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * 🧹 Limpa as respostas capturadas
355
+ */
356
+ clearResponses() {
357
+ this.responses = [];
358
+ }
359
+
360
+ /**
361
+ * 📊 Retorna as respostas capturadas
362
+ * @returns {Array} Lista de respostas
363
+ */
364
+ getResponses() {
365
+ return this.responses;
366
+ }
367
+
368
+ /**
369
+ * 🔄 Alterna entre os modos de execução
370
+ * @param {string} mode - Modo de execução ("axios" ou "playwright")
371
+ * @returns {string} Modo configurado
372
+ */
373
+ setMode(mode) {
374
+ if (mode === "playwright" && isTermux()) {
375
+ mode = "axios";
376
+ }
377
+
378
+ if (mode === "playwright" && !playwright) {
379
+ mode = "axios";
380
+ }
381
+
382
+ if (mode === "playwright") {
383
+ if (!this.ensurePlaywrightBrowsers()) {
384
+ mode = "axios";
385
+ }
386
+ }
387
+
388
+ this.mode = mode;
389
+ return this.mode;
390
+ }
391
+
392
+ /**
393
+ * 📋 Retorna o modo atual
394
+ * @returns {string} Modo de execução atual
395
+ */
396
+ getMode() {
397
+ return this.mode;
398
+ }
399
+
400
+ /**
401
+ * ✅ Verifica se o modo Playwright está disponível
402
+ * @returns {boolean} true se disponível
403
+ */
404
+ isPlaywrightAvailable() {
405
+ return !isTermux() && playwright !== null;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * 📱 Verifica se está executando no Termux (Android)
411
+ * @returns {boolean} true se estiver no Termux
412
+ */
413
+ function isTermux() {
414
+ if (process.env.TERMUX_VERSION) return true;
415
+ if (process.env.PREFIX?.includes("com.termux")) return true;
416
+
417
+ const hostname = os.hostname();
418
+ if (hostname && (
419
+ hostname.toLowerCase().includes("termux") ||
420
+ hostname.toLowerCase().includes("android")
421
+ )) return true;
422
+
423
+ try {
424
+ if (fs.existsSync("/data/data/com.termux")) return true;
425
+ } catch (e) { }
426
+
427
+ return false;
428
+ }
429
+
430
+ module.exports = { BrowserManager, isTermux };