@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 ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shriyans Sudhi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # js-recon
2
+ ## Installation
3
+ To install the tool, run:
4
+ ```bash
5
+ npm i -g @shriyanss/js-recon
6
+ ```
7
+
8
+ ## Usage
9
+ ```
10
+ $ js-recon -h
11
+ Usage: js-recon [options] [command]
12
+
13
+ JS Recon Tool
14
+
15
+ Options:
16
+ -V, --version output the version number
17
+ -h, --help display help for command
18
+
19
+ Commands:
20
+ lazyload [options] Run lazy load module
21
+ endpoints [options] Extract API endpoints
22
+ strings [options] Extract strings from JS files
23
+ api-gateway [options] Configure AWS API Gateway to rotate IP addresses
24
+ help [command] display help for command
25
+ ```
26
+
27
+ ### Lazy load
28
+ ```
29
+ $ js-recon lazyload -h
30
+ Usage: js-recon lazyload [options]
31
+
32
+ Run lazy load module
33
+
34
+ Options:
35
+ -u, --url <url/file> Target URL or a file containing a list of URLs (one per line)
36
+ -o, --output <directory> Output directory (default: "output")
37
+ --strict-scope Download JS files from only the input URL domain (default: false)
38
+ -s, --scope <scope> Download JS files from specific domains (comma-separated) (default: "*")
39
+ -t, --threads <threads> Number of threads to use (default: 1)
40
+ --subsequent-requests Download JS files from subsequent requests (default: false)
41
+ --urls-file <file> Input JSON file containing URLs (default: "extracted_urls.json")
42
+ --api-gateway Generate requests using API Gateway (default: false)
43
+ --api-gateway-config <file> API Gateway config file (default: ".api_gateway_config.json")
44
+ -h, --help display help for command
45
+ ```
46
+
47
+ ### Strings
48
+ ```
49
+ $ js-recon strings -h
50
+ Usage: js-recon strings [options]
51
+
52
+ Extract strings from JS files
53
+
54
+ Options:
55
+ -d, --directory <directory> Directory containing JS files
56
+ -o, --output <file> JSON file to save the strings (default: "strings.json")
57
+ -e, --extract-urls Extract URLs from strings (default: false)
58
+ --extracted-url-path <file> Output JSON file for extracted URLs and paths (default: "extracted_urls.json")
59
+ -h, --help display help for command
60
+ ```
61
+
62
+ ### API Gateway
63
+ ```
64
+ $ js-recon api-gateway -h
65
+ Usage: js-recon api-gateway [options]
66
+
67
+ Configure AWS API Gateway to rotate IP addresses
68
+
69
+ Options:
70
+ -i, --init Initialize the config file (create API) (default: false)
71
+ -d, --destroy <id> Destroy API with the given ID
72
+ --destroy-all Destroy all the API created by this tool in all regions (default: false)
73
+ -r, --region <region> AWS region (default: random region)
74
+ -a, --access-key <access-key> AWS access key (if not provided, AWS_ACCESS_KEY_ID environment variable will be used)
75
+ -s, --secret-key <secret-key> AWS secret key (if not provided, AWS_SECRET_ACCESS_KEY environment variable will be used)
76
+ -c, --config <config> Name of the config file (default: ".api_gateway_config.json")
77
+ -l, --list List all the API created by this tool (default: false)
78
+ --feasibility Check feasibility of API Gateway (default: false)
79
+ --feasibility-url <url> URL to check feasibility of
80
+ -h, --help display help for command
81
+ ```
82
+
83
+ ## Features
84
+ ### Download lazy loaded JS files
85
+ You can download the lazy loaded files. To do so, you can run the following command
86
+ ```bash
87
+ js-recon lazyload -u <url> -o <output>
88
+ ```
89
+
90
+ For example, you can try this with [Vercel Docs](https://vercel.com/docs):
91
+ ```bash
92
+ js-recon lazyload -u https://vercel.com/docs
93
+ ```
94
+
95
+ Currently, the following JS frameworks are supported:
96
+ - Next.js (read research [here](research/next_js.md))
97
+
98
+ ### Extract strings from JS files
99
+ You can extract strings from JS files. To do so, you can run the following command
100
+ ```bash
101
+ js-recon strings -d <directory> -o <output>
102
+ ```
103
+
104
+ For example, you can try this with [1Password](https://1password.com):
105
+ ```bash
106
+ js-recon strings -d output/1password.com -o strings.json
107
+ ```
108
+
109
+ ## Examples
110
+ ### Get all possible JS files for a Next.js app
111
+ *You can read the full research for the same [here](research/next_js.md#lazy-loaded-files)*
112
+
113
+ First of all, run the lazy load module (strict scope and 1 thread for accurate results) [research1](research/next_js.md#analysis-of-vercel-docs):
114
+ ```bash
115
+ js-recon lazyload -u <url> -o <output> --strict-scope -t 1
116
+ ```
117
+
118
+ Then, get all the strings from the JS files found. Also, extract URLs and paths found in those JS files.:
119
+ ```bash
120
+ js-recon strings -d <directory> -o <output> -e
121
+ ```
122
+
123
+ Finally, parse those URLs and paths to get more JS files (note the `--subsequent-requests` flag apart from `--strict-scope` and `--threads`) [research](research/next_js.md#analysis-of-xai):
124
+ ```bash
125
+ js-recon endpoints -u <url> -o <output> --strict-scope -t 1 --subsequent-requests
126
+ ```
127
+
128
+ ### Use AWS API Gateway to rotate IP address on each request
129
+ First of all, the user has to configure the API keys for AWS with right permissions to use the API Gateway module of the tool. The access key and the secret key can be stored in the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` respectively. Alternatively, these can be provided as command line argument to the tool.
130
+
131
+ Once this is done, the configuration file can be generated. The configuration file is a JSON file that contains the API Gateway information, including the API Gateway ID, region, access key, and secret key. This file is used by the tool in the runtime. The configuration file can be generated by running the following command:
132
+ ```bash
133
+ js-recon api-gateway -i -r <region> -a <access-key> -s <secret-key>
134
+ ```
135
+ If the region is not provided, the tool will select a random region from a pre-defined list. If the `-a` and `-s` flags aren't provided, it will default to the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` respectively.
136
+
137
+ This command will generate a new API gateway in the specified region, which can be inspected on the AWS console. The API gateway ID will be stored in the configuration file. The configuration file can be found at `./.api_gateway_config.json` by default.
138
+
139
+ The user can list the API gateways generated by running the following command:
140
+ ```bash
141
+ js-recon api-gateway -l
142
+ ```
143
+
144
+ The user can generate as many API gateways as they want. The tool will automatically select a random API gateway from the configuration file to make requests to. Creating multiple gateways in different region can help in changing the region of the IP address on each request, however, **creating multiple gateways in the same region is not recommended** as AG will rotate the IP address on each request.
145
+
146
+ It is recommended to check if the firewall is blocking the requests from the API gateway. The user can do so by running the following command:
147
+ ```bash
148
+ js-recon api-gateway --feasibility --feasibility-url <url>
149
+ ```
150
+
151
+ Next, the API gateway can be utilized for rotating the IP address on each request. To do so, the user can add the `--api-gateway` flag to the lazy load module. The user can also provide the API gateway config file using the `--api-gateway-config` flag if they have changed the defaults.
152
+
153
+ For example,
154
+ ```bash
155
+ js-recon lazyload -u <url> -o <output> --api-gateway
156
+ ```
157
+
158
+ Now that the user has completed their task, they can delete the API gateway using the following command:
159
+ ```bash
160
+ js-recon api-gateway -d <api-gateway-id>
161
+ ```
162
+
163
+ Alternatively, they can delete all the API gateways using the following command:
164
+ ```bash
165
+ js-recon api-gateway --destroy-all
166
+ ```
@@ -0,0 +1,25 @@
1
+ import { get } from "./genReq.js";
2
+ import chalk from "chalk";
3
+ import checkFireWallBlocking from "./checkFireWallBlocking.js";
4
+
5
+ const checkFeasibility = async (url) => {
6
+ console.log(chalk.cyan(`[i] Checking feasibility of API Gateway with ${url}`));
7
+ try {
8
+ // send 10 requests, and check if any of those contain any signs of blocking
9
+ for (let i = 0; i < 10; i++) {
10
+ const response = await get(url);
11
+ const isFireWallBlocking = await checkFireWallBlocking(response);
12
+ if (isFireWallBlocking) {
13
+ console.log(chalk.magenta("[!] Please try again without API Gateway"));
14
+ return;
15
+ }
16
+ }
17
+ console.log(chalk.green("[✓] Feasibility check passed."), chalk.dim("However, this doesn't represent the true nature of the firewall used."));
18
+ } catch (error) {
19
+ console.log(
20
+ chalk.red(`[!] An error occured in feasibility check: ${error}`),
21
+ );
22
+ }
23
+ };
24
+
25
+ export default checkFeasibility;
@@ -0,0 +1,17 @@
1
+ import chalk from "chalk";
2
+
3
+ const checkFireWallBlocking = async (body) => {
4
+
5
+ // check common signs of CF first
6
+ if (body.includes("<title>Just a moment...</title>")) {
7
+ console.log(chalk.red("[!] Cloudflare detected"));
8
+ return true;
9
+ } else if (body.includes("<title>Attention Required! | Cloudflare</title>")) {
10
+ console.log(chalk.red("[!] Cloudflare detected"));
11
+ return true;
12
+ }
13
+
14
+ return false;
15
+ };
16
+
17
+ export default checkFireWallBlocking;
@@ -0,0 +1,214 @@
1
+ import {
2
+ APIGatewayClient,
3
+ CreateResourceCommand,
4
+ GetResourcesCommand,
5
+ PutMethodCommand,
6
+ PutIntegrationCommand,
7
+ // CreateDeploymentCommand,
8
+ // CreateStageCommand,
9
+ PutIntegrationResponseCommand,
10
+ PutMethodResponseCommand,
11
+ TestInvokeMethodCommand,
12
+ DeleteResourceCommand,
13
+ } from "@aws-sdk/client-api-gateway";
14
+ import fs from "fs";
15
+ import md5 from "md5";
16
+ import chalk from "chalk";
17
+ import * as globals from "../utility/globals.js";
18
+ import checkFireWallBlocking from "./checkFireWallBlocking.js";
19
+
20
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
21
+
22
+ /**
23
+ * Given a URL, generates a new API Gateway for it and returns the response of the URL.
24
+ * @param {string} url The URL to generate an API Gateway for.
25
+ * @param {object} [headers] The headers to include in the request.
26
+ * @returns {Promise<string>} The response of the URL.
27
+ */
28
+ const get = async (url, headers) => {
29
+ // read the config file
30
+ let config = JSON.parse(fs.readFileSync(globals.apiGatewayConfigFile));
31
+ // select a random api gateway
32
+ let apiGateway =
33
+ Object.keys(config)[Math.floor(Math.random() * Object.keys(config).length)];
34
+
35
+ const client = new APIGatewayClient({
36
+ region: config[apiGateway].region,
37
+ credentials: {
38
+ accessKeyId: config[apiGateway].access_key,
39
+ secretAccessKey: config[apiGateway].secret_key,
40
+ },
41
+ });
42
+
43
+ // get the root resource id
44
+ const getResourceCommand = new GetResourcesCommand({
45
+ restApiId: config[apiGateway].id,
46
+ limit: 999999999,
47
+ });
48
+ const getResourceResponse = await client.send(getResourceCommand);
49
+ await sleep(200);
50
+
51
+ // before creating a resource, check if the resource already exists
52
+ const resourceExists = getResourceResponse.items.find(
53
+ (item) => item.pathPart === md5(url),
54
+ );
55
+
56
+ let newResourceResponse;
57
+ if (resourceExists) {
58
+ // console.log(chalk.yellow("[!] Resource already exists"));
59
+ newResourceResponse = {
60
+ id: resourceExists.id,
61
+ };
62
+ } else {
63
+ // create a new resource
64
+ let rootId;
65
+ if (getResourceResponse.items.find((item) => item.path === "/")) {
66
+ rootId = getResourceResponse.items.find((item) => item.path === "/").id;
67
+ } else {
68
+ rootId = getResourceResponse.items[0].parentId;
69
+ }
70
+ const newResourceCommand = new CreateResourceCommand({
71
+ restApiId: config[apiGateway].id,
72
+ parentId: rootId,
73
+ pathPart: md5(url), // md5 of the url
74
+ });
75
+ newResourceResponse = await client.send(newResourceCommand);
76
+ await sleep(200);
77
+
78
+ // add a new method
79
+ const newMethodCommand = new PutMethodCommand({
80
+ restApiId: config[apiGateway].id,
81
+ resourceId: newResourceResponse.id,
82
+ httpMethod: "GET",
83
+ authorizationType: "NONE",
84
+ requestParameters: {
85
+ "method.request.header.RSC": false,
86
+ "method.request.header.User-Agent": false,
87
+ "method.request.header.Referer": false,
88
+ "method.request.header.Accept": false,
89
+ "method.request.header.Accept-Language": false,
90
+ "method.request.header.Accept-Encoding": false,
91
+ "method.request.header.Content-Type": false,
92
+ "method.request.header.Content-Length": false,
93
+ "method.request.header.Origin": false,
94
+ "method.request.header.X-Forwarded-For": false,
95
+ "method.request.header.X-Forwarded-Host": false,
96
+ "method.request.header.X-IP": false,
97
+ "method.request.header.X-Forwarded-Proto": false,
98
+ "method.request.header.X-Forwarded-Port": false,
99
+ "method.request.header.Sec-Fetch-Site": false,
100
+ "method.request.header.Sec-Fetch-Mode": false,
101
+ "method.request.header.Sec-Fetch-Dest": false,
102
+ },
103
+ integrationHttpMethod: "GET",
104
+ type: "HTTP",
105
+ timeoutInMillis: 29000,
106
+ });
107
+ const newMethodResponse = await client.send(newMethodCommand);
108
+ await sleep(100);
109
+
110
+ // create new integration
111
+ const newIntegrationCommand = new PutIntegrationCommand({
112
+ restApiId: config[apiGateway].id,
113
+ resourceId: newResourceResponse.id,
114
+ httpMethod: "GET",
115
+ integrationHttpMethod: "GET",
116
+ type: "HTTP",
117
+ timeoutInMillis: 29000,
118
+ uri: url,
119
+ });
120
+ const newIntegrationResponse = await client.send(newIntegrationCommand);
121
+ await sleep(100);
122
+
123
+ // create a new method response
124
+ const newMethodResponseCommand = new PutMethodResponseCommand({
125
+ httpMethod: "GET",
126
+ resourceId: newResourceResponse.id,
127
+ restApiId: config[apiGateway].id,
128
+ statusCode: "200",
129
+ });
130
+ const newMethodResponseResponse = await client.send(
131
+ newMethodResponseCommand,
132
+ );
133
+ await sleep(100);
134
+
135
+ // put integration response
136
+ const putIntegrationResponseCommand = new PutIntegrationResponseCommand({
137
+ httpMethod: "GET",
138
+ resourceId: newResourceResponse.id,
139
+ restApiId: config[apiGateway].id,
140
+ statusCode: "200",
141
+ });
142
+ const putIntegrationResponseResponse = await client.send(
143
+ putIntegrationResponseCommand,
144
+ );
145
+ await sleep(100);
146
+ }
147
+
148
+ // Generate dynamic stage name
149
+ // const dynamicStageName = `prod-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
150
+ // console.log(chalk.blue(`[*] Using dynamic stage name: ${dynamicStageName}`));
151
+
152
+ // Create a deployment
153
+ // const createDeploymentCommand = new CreateDeploymentCommand({
154
+ // restApiId: config[apiGateway].id,
155
+ // stageName: dynamicStageName,
156
+ // description: `Deployment for ${url} at ${new Date().toISOString()} to stage ${dynamicStageName}`,
157
+ // });
158
+ // const deploymentResponse = await client.send(createDeploymentCommand);
159
+ // console.log(chalk.green("[+] Deployment created:"), deploymentResponse.id);
160
+ // await sleep(100);
161
+
162
+ const testInvokeMethodQuery = new TestInvokeMethodCommand({
163
+ httpMethod: "GET",
164
+ resourceId: newResourceResponse.id,
165
+ restApiId: config[apiGateway].id,
166
+ headers: headers || {},
167
+ });
168
+ const testInvokeMethodResponse = await client.send(testInvokeMethodQuery);
169
+ await sleep(100);
170
+
171
+ const body = await testInvokeMethodResponse.body;
172
+
173
+ // check if any firewall is there in the way
174
+ const isFireWallBlocking = await checkFireWallBlocking(body);
175
+
176
+ // delete the resource
177
+ const deleteResourceCommand = new DeleteResourceCommand({
178
+ restApiId: config[apiGateway].id,
179
+ resourceId: newResourceResponse.id,
180
+ });
181
+ try {
182
+ await client.send(deleteResourceCommand);
183
+ } catch {}
184
+
185
+ if (isFireWallBlocking) {
186
+ console.log(chalk.magenta("[!] Please try again without API Gateway"));
187
+ process.exit(1);
188
+ }
189
+
190
+ return body;
191
+
192
+ // create a new stage
193
+ // dynamicStageName = `prod-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
194
+ // const newStageCommand = new CreateStageCommand({
195
+ // restApiId: config[apiGateway].id,
196
+ // stageName: dynamicStageName, // Use dynamic stage name
197
+ // deploymentId: deploymentResponse.id, // Use the ID from the deployment
198
+ // cacheClusterEnabled: false,
199
+ // // cacheClusterSize: "0", // Removed as cacheClusterEnabled is false
200
+ // methodSettings: [
201
+ // {
202
+ // httpMethod: "*",
203
+ // // resourceId: "*", // This might need to be more specific if you don't want it for all resources
204
+ // throttlingBurstLimit: 5,
205
+ // throttlingRateLimit: 10,
206
+ // },
207
+ // ],
208
+ // });
209
+ // const newStageResponse = await client.send(newStageCommand);
210
+ // await sleep(100);
211
+ // console.log(newStageResponse);
212
+ };
213
+
214
+ export { get };