@shriyanss/js-recon 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.
- package/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/api_gateway/checkFeasibility.js +25 -0
- package/api_gateway/checkFireWallBlocking.js +17 -0
- package/api_gateway/genReq.js +214 -0
- package/api_gateway/index.js +325 -0
- package/endpoints/index.js +7 -0
- package/globalConfig.js +12 -0
- package/index.js +69 -0
- package/lazyLoad/downloadFilesUtil.js +122 -0
- package/lazyLoad/downloadLoadedJsUtil.js +54 -0
- package/lazyLoad/globals.js +15 -0
- package/lazyLoad/index.js +167 -0
- package/lazyLoad/next_js/next_GetJSScript.js +99 -0
- package/lazyLoad/next_js/next_GetLazyResources.js +201 -0
- package/lazyLoad/next_js/next_SubsequentRequests.js +138 -0
- package/lazyLoad/nuxt_js/nuxt_astParse.js +194 -0
- package/lazyLoad/nuxt_js/nuxt_getFromPageSource.js +77 -0
- package/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js +99 -0
- package/package.json +40 -0
- package/research/firewall_bypass.md +38 -0
- package/research/next_js.md +116 -0
- package/research/nuxt_js.md +125 -0
- package/research/vue_js.md +9 -0
- package/strings/index.js +145 -0
- package/techDetect/index.js +156 -0
- package/utility/globals.js +6 -0
- package/utility/makeReq.js +179 -0
- package/utility/resolvePath.js +43 -0
- package/utility/runSandboxed.js +25 -0
- package/utility/urlUtils.js +22 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import {
|
|
3
|
+
APIGatewayClient,
|
|
4
|
+
CreateRestApiCommand,
|
|
5
|
+
DeleteRestApiCommand,
|
|
6
|
+
} from "@aws-sdk/client-api-gateway";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import checkFeasibility from "./checkFeasibility.js";
|
|
9
|
+
|
|
10
|
+
// read the docs for all the methods for api gateway at https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/api-gateway/
|
|
11
|
+
// for the rate limits, refer to https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html
|
|
12
|
+
|
|
13
|
+
const randomRegion = () => {
|
|
14
|
+
const apiGatewayRegions = [
|
|
15
|
+
"us-east-2", // US East (Ohio)
|
|
16
|
+
"us-east-1", // US East (N. Virginia)
|
|
17
|
+
"us-west-1", // US West (N. California)
|
|
18
|
+
"us-west-2", // US West (Oregon)
|
|
19
|
+
"af-south-1", // Africa (Cape Town)
|
|
20
|
+
"ap-east-1", // Asia Pacific (Hong Kong)
|
|
21
|
+
"ap-south-2", // Asia Pacific (Hyderabad)
|
|
22
|
+
"ap-southeast-3", // Asia Pacific (Jakarta)
|
|
23
|
+
"ap-southeast-5", // Asia Pacific (Malaysia)
|
|
24
|
+
"ap-southeast-4", // Asia Pacific (Melbourne)
|
|
25
|
+
"ap-south-1", // Asia Pacific (Mumbai)
|
|
26
|
+
"ap-northeast-3", // Asia Pacific (Osaka)
|
|
27
|
+
"ap-northeast-2", // Asia Pacific (Seoul)
|
|
28
|
+
"ap-southeast-1", // Asia Pacific (Singapore)
|
|
29
|
+
"ap-southeast-2", // Asia Pacific (Sydney)
|
|
30
|
+
"ap-east-2", // Asia Pacific (Taipei)
|
|
31
|
+
"ap-southeast-7", // Asia Pacific (Thailand)
|
|
32
|
+
"ap-northeast-1", // Asia Pacific (Tokyo)
|
|
33
|
+
"ca-central-1", // Canada (Central)
|
|
34
|
+
"ca-west-1", // Canada West (Calgary)
|
|
35
|
+
"eu-central-1", // Europe (Frankfurt)
|
|
36
|
+
"eu-west-1", // Europe (Ireland)
|
|
37
|
+
"eu-west-2", // Europe (London)
|
|
38
|
+
"eu-south-1", // Europe (Milan)
|
|
39
|
+
"eu-west-3", // Europe (Paris)
|
|
40
|
+
"eu-south-2", // Europe (Spain)
|
|
41
|
+
"eu-north-1", // Europe (Stockholm)
|
|
42
|
+
"eu-central-2", // Europe (Zurich)
|
|
43
|
+
"il-central-1", // Israel (Tel Aviv)
|
|
44
|
+
"mx-central-1", // Mexico (Central)
|
|
45
|
+
"me-south-1", // Middle East (Bahrain)
|
|
46
|
+
"me-central-1", // Middle East (UAE)
|
|
47
|
+
"sa-east-1", // South America (São Paulo)
|
|
48
|
+
];
|
|
49
|
+
return apiGatewayRegions[
|
|
50
|
+
Math.floor(Math.random() * apiGatewayRegions.length)
|
|
51
|
+
];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let aws_access_key;
|
|
55
|
+
let aws_secret_key;
|
|
56
|
+
let region;
|
|
57
|
+
let configFile;
|
|
58
|
+
|
|
59
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a new API Gateway.
|
|
63
|
+
*
|
|
64
|
+
* @async
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
const createGateway = async () => {
|
|
68
|
+
console.log(chalk.cyan("[i] Creating API Gateway"));
|
|
69
|
+
const client = new APIGatewayClient({
|
|
70
|
+
region,
|
|
71
|
+
credentials: {
|
|
72
|
+
accessKeyId: aws_access_key,
|
|
73
|
+
secretAccessKey: aws_secret_key,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const apigw_created_at = Date.now();
|
|
78
|
+
const apigw_name = `js_recon-${apigw_created_at}-${Math.floor(Math.random() * 1000)}`;
|
|
79
|
+
const command = new CreateRestApiCommand({
|
|
80
|
+
name: apigw_name,
|
|
81
|
+
description: `API Gateway for JS Recon created at ${new Intl.DateTimeFormat(
|
|
82
|
+
"en-US",
|
|
83
|
+
{
|
|
84
|
+
year: "numeric",
|
|
85
|
+
month: "long",
|
|
86
|
+
day: "2-digit",
|
|
87
|
+
hour: "2-digit",
|
|
88
|
+
minute: "2-digit",
|
|
89
|
+
second: "2-digit",
|
|
90
|
+
timeZoneName: "short",
|
|
91
|
+
},
|
|
92
|
+
).format(apigw_created_at)}`,
|
|
93
|
+
endpointConfiguration: {
|
|
94
|
+
ipAddressType: "dualstack",
|
|
95
|
+
types: ["REGIONAL"],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const response = await client.send(command);
|
|
99
|
+
await sleep(3000);
|
|
100
|
+
console.log(chalk.green(`[✓] Created API Gateway`));
|
|
101
|
+
console.log(chalk.bgGreen("ID:"), chalk.green(response.id));
|
|
102
|
+
console.log(chalk.bgGreen("Name:"), chalk.green(apigw_name));
|
|
103
|
+
console.log(chalk.bgGreen("Region:"), chalk.green(region));
|
|
104
|
+
|
|
105
|
+
// load the config file if any. Else, create a new one
|
|
106
|
+
let config = {};
|
|
107
|
+
try {
|
|
108
|
+
config = JSON.parse(fs.readFileSync(configFile));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
config = {};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
config[apigw_name] = {
|
|
114
|
+
id: response.id,
|
|
115
|
+
name: apigw_name,
|
|
116
|
+
description: response.description,
|
|
117
|
+
created_at: apigw_created_at,
|
|
118
|
+
region: region,
|
|
119
|
+
access_key: aws_access_key,
|
|
120
|
+
secret_key: aws_secret_key,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
124
|
+
console.log(chalk.green(`[✓] Config saved to ${configFile}`));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Destroy an API Gateway.
|
|
129
|
+
*
|
|
130
|
+
* @async
|
|
131
|
+
* @param {string} id - The ID of the API Gateway to destroy.
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
134
|
+
const destroyGateway = async (id) => {
|
|
135
|
+
console.log(chalk.cyan("[i] Destroying API Gateway"));
|
|
136
|
+
if (!id) {
|
|
137
|
+
console.log(chalk.red("[!] Please provide an API Gateway ID"));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// read the config file
|
|
141
|
+
let config = JSON.parse(fs.readFileSync(configFile));
|
|
142
|
+
// get the name of the api gateway
|
|
143
|
+
let name = Object.keys(config).find((key) => config[key].id === id);
|
|
144
|
+
|
|
145
|
+
console.log(chalk.bgGreen("Name:"), chalk.green(name));
|
|
146
|
+
console.log(chalk.bgGreen("ID:"), chalk.green(id));
|
|
147
|
+
console.log(chalk.bgGreen("Region:"), chalk.green(config[name].region));
|
|
148
|
+
region = config[name].region;
|
|
149
|
+
|
|
150
|
+
const client = new APIGatewayClient({
|
|
151
|
+
region,
|
|
152
|
+
credentials: {
|
|
153
|
+
accessKeyId: aws_access_key,
|
|
154
|
+
secretAccessKey: aws_secret_key,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const command = new DeleteRestApiCommand({
|
|
159
|
+
restApiId: id,
|
|
160
|
+
});
|
|
161
|
+
await client.send(command);
|
|
162
|
+
|
|
163
|
+
// remove from the config file
|
|
164
|
+
delete config[name];
|
|
165
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
166
|
+
|
|
167
|
+
await sleep(30000);
|
|
168
|
+
|
|
169
|
+
console.log(chalk.green(`[✓] Destroyed API Gateway: ${id}`));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Destroy all API Gateways.
|
|
174
|
+
*
|
|
175
|
+
* @async
|
|
176
|
+
* @returns {Promise<void>}
|
|
177
|
+
*/
|
|
178
|
+
const destroyAllGateways = async () => {
|
|
179
|
+
console.log(chalk.cyan("[i] Destroying all API Gateways"));
|
|
180
|
+
// read the config file
|
|
181
|
+
let config = JSON.parse(fs.readFileSync(configFile));
|
|
182
|
+
|
|
183
|
+
// destroy all the gateways
|
|
184
|
+
for (const [key, value] of Object.entries(config)) {
|
|
185
|
+
const client = new APIGatewayClient({
|
|
186
|
+
region: value.region,
|
|
187
|
+
credentials: {
|
|
188
|
+
accessKeyId: aws_access_key,
|
|
189
|
+
secretAccessKey: aws_secret_key,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
console.log(
|
|
193
|
+
chalk.cyan(
|
|
194
|
+
`[i] Destroying API Gateway: ${key} : ${value.id} : ${value.region}`,
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const command = new DeleteRestApiCommand({
|
|
199
|
+
restApiId: value.id,
|
|
200
|
+
});
|
|
201
|
+
await sleep(30000);
|
|
202
|
+
await client.send(command);
|
|
203
|
+
console.log(
|
|
204
|
+
chalk.green(
|
|
205
|
+
`[✓] Destroyed API Gateway: ${key} : ${value.id} : ${value.region}`,
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// nullify the config file
|
|
211
|
+
fs.writeFileSync(configFile, JSON.stringify({}, null, 2));
|
|
212
|
+
console.log(chalk.green("[✓] Destroyed all API Gateways"));
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* List all API Gateways.
|
|
217
|
+
*
|
|
218
|
+
* @async
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
const listGateways = async () => {
|
|
222
|
+
console.log(chalk.cyan("[i] Listing all API Gateways"));
|
|
223
|
+
|
|
224
|
+
// read the config file, and list these
|
|
225
|
+
|
|
226
|
+
// check if the config file exists
|
|
227
|
+
if (!fs.existsSync(configFile)) {
|
|
228
|
+
console.log(chalk.red("[!] Config file does not exist"));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const config = JSON.parse(fs.readFileSync(configFile));
|
|
233
|
+
|
|
234
|
+
// if list is empty
|
|
235
|
+
if (Object.keys(config).length === 0) {
|
|
236
|
+
console.log(chalk.red("[!] No API Gateways found"));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(chalk.green("[✓] List of API Gateways"));
|
|
241
|
+
|
|
242
|
+
for (const [key, value] of Object.entries(config)) {
|
|
243
|
+
console.log(chalk.bgGreen("Name:"), chalk.green(key));
|
|
244
|
+
console.log(chalk.bgGreen("ID:"), chalk.green(value.id));
|
|
245
|
+
console.log(chalk.bgGreen("Region:"), chalk.green(value.region));
|
|
246
|
+
console.log("\n");
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Main function for API Gateway.
|
|
252
|
+
*
|
|
253
|
+
* @async
|
|
254
|
+
* @param {boolean} initInput - Whether to initialize the API Gateway.
|
|
255
|
+
* @param {string} destroyInput - The ID of the API Gateway to destroy.
|
|
256
|
+
* @param {boolean} destroyAllInput - Whether to destroy all API Gateways.
|
|
257
|
+
* @param {boolean} listInput - Whether to list all API Gateways.
|
|
258
|
+
* @param {string} regionInput - The region to use.
|
|
259
|
+
* @param {string} accessKey - The access key to use.
|
|
260
|
+
* @param {string} secretKey - The secret key to use.
|
|
261
|
+
* @param {string} configInput - The config file to use.
|
|
262
|
+
* @returns {Promise<void>}
|
|
263
|
+
*/
|
|
264
|
+
const apiGateway = async (
|
|
265
|
+
initInput,
|
|
266
|
+
destroyInput,
|
|
267
|
+
destroyAllInput,
|
|
268
|
+
listInput,
|
|
269
|
+
regionInput,
|
|
270
|
+
accessKey,
|
|
271
|
+
secretKey,
|
|
272
|
+
configInput,
|
|
273
|
+
feasibilityInput,
|
|
274
|
+
feasibilityUrlInput,
|
|
275
|
+
) => {
|
|
276
|
+
console.log(chalk.cyan("[i] Loading 'API Gateway' module"));
|
|
277
|
+
|
|
278
|
+
// if feasibility is true, check feasibility
|
|
279
|
+
if (feasibilityInput) {
|
|
280
|
+
if (!feasibilityUrlInput) {
|
|
281
|
+
console.log(chalk.red("[!] Please provide a URL to check feasibility of"));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await checkFeasibility(feasibilityUrlInput);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// configure the access and secret key
|
|
289
|
+
aws_access_key = accessKey || process.env.AWS_ACCESS_KEY_ID || undefined;
|
|
290
|
+
aws_secret_key = secretKey || process.env.AWS_SECRET_ACCESS_KEY || undefined;
|
|
291
|
+
region = regionInput || randomRegion();
|
|
292
|
+
configFile = configInput || "config.json";
|
|
293
|
+
|
|
294
|
+
if (!aws_access_key || !aws_secret_key) {
|
|
295
|
+
console.log(chalk.red("[!] AWS Access Key or Secret Key not found"));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(chalk.cyan(`[i] Using region: ${region}`));
|
|
300
|
+
|
|
301
|
+
const keyMask = (key) => {
|
|
302
|
+
if (key.length < 6) return key;
|
|
303
|
+
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
304
|
+
};
|
|
305
|
+
console.log(chalk.cyan(`[i] Using access key: ${keyMask(aws_access_key)}`));
|
|
306
|
+
|
|
307
|
+
// create a new API gateway
|
|
308
|
+
if (initInput) {
|
|
309
|
+
await createGateway();
|
|
310
|
+
} else if (destroyInput) {
|
|
311
|
+
await destroyGateway(destroyInput);
|
|
312
|
+
} else if (destroyAllInput) {
|
|
313
|
+
await destroyAllGateways();
|
|
314
|
+
} else if (listInput) {
|
|
315
|
+
await listGateways();
|
|
316
|
+
} else {
|
|
317
|
+
console.log(
|
|
318
|
+
chalk.red(
|
|
319
|
+
"[!] Please provide a valid action (-i/--init or -d/--destroy or --destroy-all)",
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export default apiGateway;
|
package/globalConfig.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const githubURL = "https://github.com/shriyanss/js-recon"
|
|
2
|
+
const version = "1.0.0";
|
|
3
|
+
const toolDesc = "JS Recon Tool";
|
|
4
|
+
|
|
5
|
+
global.CONFIG = {
|
|
6
|
+
github: githubURL,
|
|
7
|
+
notFoundMessage: `If you believe this is an error or is a new technology, please create an issue on ${githubURL} and we'll figure it out for you`,
|
|
8
|
+
version: version,
|
|
9
|
+
toolDesc: toolDesc
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default CONFIG;
|
package/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import lazyLoad from "./lazyLoad/index.js";
|
|
4
|
+
import endpoints from "./endpoints/index.js";
|
|
5
|
+
import CONFIG from "./globalConfig.js";
|
|
6
|
+
import strings from "./strings/index.js";
|
|
7
|
+
import apiGateway from "./api_gateway/index.js";
|
|
8
|
+
import * as globals from "./utility/globals.js";
|
|
9
|
+
|
|
10
|
+
program.version(CONFIG.version).description(CONFIG.toolDesc);
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command("lazyload")
|
|
14
|
+
.description("Run lazy load module")
|
|
15
|
+
.requiredOption("-u, --url <url/file>", "Target URL or a file containing a list of URLs (one per line)")
|
|
16
|
+
.option("-o, --output <directory>", "Output directory", "output")
|
|
17
|
+
.option("--strict-scope", "Download JS files from only the input URL domain", false)
|
|
18
|
+
.option("-s, --scope <scope>", "Download JS files from specific domains (comma-separated)", "*")
|
|
19
|
+
.option("-t, --threads <threads>", "Number of threads to use", 1)
|
|
20
|
+
.option("--subsequent-requests", "Download JS files from subsequent requests", false)
|
|
21
|
+
.option("--urls-file <file>", "Input JSON file containing URLs", "extracted_urls.json")
|
|
22
|
+
.option("--api-gateway", "Generate requests using API Gateway", false)
|
|
23
|
+
.option("--api-gateway-config <file>", "API Gateway config file", ".api_gateway_config.json")
|
|
24
|
+
.action(async (cmd) => {
|
|
25
|
+
globals.setApiGatewayConfigFile(cmd.apiGatewayConfig);
|
|
26
|
+
globals.setUseApiGateway(cmd.apiGateway);
|
|
27
|
+
await lazyLoad(cmd.url, cmd.output, cmd.strictScope, cmd.scope.split(","), cmd.threads, cmd.subsequentRequests, cmd.urlsFile);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command("endpoints")
|
|
32
|
+
.description("Extract API endpoints")
|
|
33
|
+
.requiredOption("-u, --url <url>", "Target URL")
|
|
34
|
+
.option("-o, --output <file>", "Output file")
|
|
35
|
+
.action((cmd) => {
|
|
36
|
+
endpoints(cmd.url, cmd.output);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command("strings")
|
|
41
|
+
.description("Extract strings from JS files")
|
|
42
|
+
.requiredOption("-d, --directory <directory>", "Directory containing JS files")
|
|
43
|
+
.option("-o, --output <file>", "JSON file to save the strings", "strings.json")
|
|
44
|
+
.option("-e, --extract-urls", "Extract URLs from strings", false)
|
|
45
|
+
.option("--extracted-url-path <file>", "Output JSON file for extracted URLs and paths", "extracted_urls.json")
|
|
46
|
+
.action((cmd) => {
|
|
47
|
+
strings(cmd.directory, cmd.output, cmd.extractUrls, cmd.extractedUrlPath);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command("api-gateway")
|
|
52
|
+
.description("Configure AWS API Gateway to rotate IP addresses")
|
|
53
|
+
.option("-i, --init", "Initialize the config file (create API)", false)
|
|
54
|
+
.option("-d, --destroy <id>", "Destroy API with the given ID")
|
|
55
|
+
.option("--destroy-all", "Destroy all the API created by this tool in all regions", false)
|
|
56
|
+
.option("-r, --region <region>", "AWS region (default: random region)")
|
|
57
|
+
.option("-a, --access-key <access-key>", "AWS access key (if not provided, AWS_ACCESS_KEY_ID environment variable will be used)")
|
|
58
|
+
.option("-s, --secret-key <secret-key>", "AWS secret key (if not provided, AWS_SECRET_ACCESS_KEY environment variable will be used)")
|
|
59
|
+
.option("-c, --config <config>", "Name of the config file", ".api_gateway_config.json")
|
|
60
|
+
.option("-l, --list", "List all the API created by this tool", false)
|
|
61
|
+
.option("--feasibility", "Check feasibility of API Gateway", false)
|
|
62
|
+
.option("--feasibility-url <url>", "URL to check feasibility of")
|
|
63
|
+
.action((cmd) => {
|
|
64
|
+
globals.setApiGatewayConfigFile(cmd.config);
|
|
65
|
+
globals.setUseApiGateway(true);
|
|
66
|
+
apiGateway(cmd.init, cmd.destroy, cmd.destroyAll, cmd.list, cmd.region, cmd.accessKey, cmd.secretKey, cmd.config, cmd.feasibility, cmd.feasibilityUrl);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import prettier from "prettier";
|
|
5
|
+
import makeRequest from "../utility/makeReq.js";
|
|
6
|
+
import { getURLDirectory } from "../utility/urlUtils.js";
|
|
7
|
+
import { getScope, getMaxReqQueue } from "./globals.js"; // Import scope and max_req_queue functions
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Downloads a list of URLs and saves them as files in the specified output directory.
|
|
11
|
+
* It creates the necessary subdirectories based on the URL's host and path.
|
|
12
|
+
* If the URL does not end with `.js`, it is skipped.
|
|
13
|
+
* The function logs the progress and any errors to the console.
|
|
14
|
+
* @param {string[]} urls - An array of URLs to be downloaded.
|
|
15
|
+
* @param {string} output - The directory where the downloaded files will be saved.
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
const downloadFiles = async (urls, output) => {
|
|
19
|
+
console.log(
|
|
20
|
+
chalk.cyan(`[i] Attempting to download ${urls.length} JS chunks`),
|
|
21
|
+
);
|
|
22
|
+
fs.mkdirSync(output, { recursive: true });
|
|
23
|
+
|
|
24
|
+
// to store ignored JS domain
|
|
25
|
+
let ignoredJSFiles = [];
|
|
26
|
+
let ignoredJSDomains = [];
|
|
27
|
+
|
|
28
|
+
let download_count = 0;
|
|
29
|
+
let queue = 0;
|
|
30
|
+
|
|
31
|
+
const downloadPromises = urls.map(async (url) => {
|
|
32
|
+
try {
|
|
33
|
+
if (url.match(/\.js/)) {
|
|
34
|
+
// get the directory of the url
|
|
35
|
+
const { host, directory } = getURLDirectory(url);
|
|
36
|
+
|
|
37
|
+
// check scope of file. Only if in scope, download it
|
|
38
|
+
if (!getScope().includes("*")) {
|
|
39
|
+
if (!getScope().includes(host)) {
|
|
40
|
+
ignoredJSFiles.push(url);
|
|
41
|
+
if (!ignoredJSDomains.includes(host)) {
|
|
42
|
+
ignoredJSDomains.push(host);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// make the directory inside the output folder
|
|
49
|
+
const childDir = path.join(output, host, directory);
|
|
50
|
+
fs.mkdirSync(childDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// check if queue is full. If so, then wait for random time between
|
|
53
|
+
// 50 to 300 ms. Then, check again, and loop the process
|
|
54
|
+
while (queue >= getMaxReqQueue()) {
|
|
55
|
+
await new Promise((resolve) =>
|
|
56
|
+
setTimeout(resolve, Math.random() * 250 + 50),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
queue++;
|
|
60
|
+
const res = await makeRequest(url);
|
|
61
|
+
queue--;
|
|
62
|
+
|
|
63
|
+
const file = `// JS Source: ${url}\n${await res.text()}`;
|
|
64
|
+
let filename;
|
|
65
|
+
try {
|
|
66
|
+
filename = url
|
|
67
|
+
.split("/")
|
|
68
|
+
.pop()
|
|
69
|
+
.match(/[a-zA-Z0-9\.\-_]+\.js/)[0];
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// split the URL into multiple chunks. then iterate
|
|
72
|
+
// through it, and find whatever matches with JS ext
|
|
73
|
+
const chunks = url.split("/");
|
|
74
|
+
for (const chunk of chunks) {
|
|
75
|
+
if (chunk.match(/\.js$/)) {
|
|
76
|
+
filename = chunk;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!filename) { // Handle cases where filename might not be found
|
|
83
|
+
console.warn(chalk.yellow(`[!] Could not determine filename for URL: ${url}. Skipping.`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const filePath = path.join(childDir, filename);
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(
|
|
90
|
+
filePath,
|
|
91
|
+
await prettier.format(file, { parser: "babel" }),
|
|
92
|
+
);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(chalk.red(`[!] Failed to write file: ${filePath}`));
|
|
95
|
+
}
|
|
96
|
+
download_count++;
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(chalk.red(`[!] Failed to download: ${url}`));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await Promise.all(downloadPromises);
|
|
104
|
+
|
|
105
|
+
if (ignoredJSFiles.length > 0) {
|
|
106
|
+
console.log(
|
|
107
|
+
chalk.yellow(
|
|
108
|
+
`[i] Ignored ${ignoredJSFiles.length} JS files across ${ignoredJSDomains.length} domain(s) - ${ignoredJSDomains.join(", ")}`,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (download_count > 0) {
|
|
114
|
+
console.log(
|
|
115
|
+
chalk.green(
|
|
116
|
+
`[✓] Downloaded ${download_count} JS chunks to ${output} directory`,
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default downloadFiles;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import puppeteer from "puppeteer";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Downloads all the lazy loaded JS files from a given URL.
|
|
6
|
+
*
|
|
7
|
+
* It opens a headless browser instance, navigates to the given URL, and
|
|
8
|
+
* intercepts all the requests. It checks if the request is a JS file
|
|
9
|
+
* and if it is a GET request. If both conditions are satisfied, the URL
|
|
10
|
+
* is added to the array of URLs. Finally, it closes the browser instance
|
|
11
|
+
* and returns the array of URLs.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} url - The URL of the webpage to fetch and parse.
|
|
14
|
+
* @returns {Promise<string[]|undefined>} - A promise that resolves to an array of
|
|
15
|
+
* absolute URLs pointing to JavaScript files found in the page, or undefined for invalid URL.
|
|
16
|
+
*/
|
|
17
|
+
const downloadLoadedJs = async (url) => {
|
|
18
|
+
if (!url.match(/https?:\/\/[a-zA-Z0-9\._\-]+/)) {
|
|
19
|
+
console.log(chalk.red("[!] Invalid URL"));
|
|
20
|
+
return; // Return undefined as per JSDoc
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const browser = await puppeteer.launch({
|
|
24
|
+
headless: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const page = await browser.newPage();
|
|
28
|
+
|
|
29
|
+
await page.setRequestInterception(true);
|
|
30
|
+
|
|
31
|
+
let js_urls_local = []; // Use a local variable, not the global one
|
|
32
|
+
page.on("request", async (request) => {
|
|
33
|
+
// get the request url
|
|
34
|
+
const req_url = request.url(); // Renamed to avoid conflict with outer 'url'
|
|
35
|
+
|
|
36
|
+
// see if the request is a JS file, and is a get request
|
|
37
|
+
if (
|
|
38
|
+
request.method() === "GET" &&
|
|
39
|
+
req_url.match(/https?:\/\/[a-z\._\-]+\/.+\.js\??.*/)
|
|
40
|
+
) {
|
|
41
|
+
js_urls_local.push(req_url);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await request.continue();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await page.goto(url);
|
|
48
|
+
|
|
49
|
+
await browser.close();
|
|
50
|
+
|
|
51
|
+
return js_urls_local;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default downloadLoadedJs;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
let scope = [];
|
|
3
|
+
let js_urls = [];
|
|
4
|
+
let max_req_queue;
|
|
5
|
+
|
|
6
|
+
export const getScope = () => scope;
|
|
7
|
+
export const setScope = (newScope) => { scope = newScope; };
|
|
8
|
+
export const pushToScope = (item) => { scope.push(item); };
|
|
9
|
+
|
|
10
|
+
export const getJsUrls = () => js_urls;
|
|
11
|
+
export const clearJsUrls = () => { js_urls = []; };
|
|
12
|
+
export const pushToJsUrls = (url) => { js_urls.push(url); };
|
|
13
|
+
|
|
14
|
+
export const getMaxReqQueue = () => max_req_queue;
|
|
15
|
+
export const setMaxReqQueue = (newMax) => { max_req_queue = newMax; };
|