@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.
@@ -0,0 +1,125 @@
1
+ # Nuxt.js Research
2
+ ## Tech Detection
3
+ Nuxt.js is a framework based on Vue.js. So, if the Vue.js is detected, then it is essential to detect Nuxt.js for accuracy.
4
+
5
+ To detect Nuxt.js, following technique(s) can be used:
6
+ - Search for "/_nuxt" in following attributes
7
+ - `src`, `href`
8
+
9
+ ## Lazy Loaded Files
10
+ ### Analysis of [GitLab's About Site](https://about.gitlab.com/)
11
+ It was found that all of the JS files were loaded from the `/_nuxt-new` directory instead of `/_nuxt` directory. The path to the JS files were located in the `<link>` tags' `href` attribute. These `<link>` tags had the value of `rel` attribute as `modulepreload`, and the values of `as` attribute as `script`.
12
+
13
+ Additionally, there were some `<script>` tags found with `src` attribute as the path to the JS files, which started with `/_nuxt`.
14
+
15
+ ### Analysis of [Vue Mastery](https://www.vuemastery.com/)
16
+ Similar to Gitlab's About Page, Vue Mastery had `<link>` tags with `rel` attribute as `preload` and `href` attribute as the path to the JS files. These `<link>` tags had the value of `as` attribute as `script`, which is common between two sites.
17
+
18
+ Additionally, there were some `<script>` tags found with `src` attribute as the path to the JS files, which started with `/_nuxt`.
19
+
20
+ Upon analysis of JS files that can be downloaded with the above two methods, it was found that some of the JS files contained functions to generate dynamic JS files based on the function. One of such function was found in https://www.vuemastery.com/_nuxt/2551e78.js, which was present in a `<script>` tag on the home page source. The function responsible for generating dynamic JS files is:
21
+ ```js
22
+ f.p = "/_nuxt/",
23
+ // --snip--
24
+ script.src = function(e) {
25
+ return f.p + "" + {
26
+ 0: "dad8928",
27
+ 1: "45a7974",
28
+ 2: "4632233",
29
+ 3: "1b5c8a7",
30
+ 4: "605bec3",
31
+ 5: "b2d6888",
32
+ 6: "6f126a6",
33
+ 7: "0660b70",
34
+ 10: "b2c4841",
35
+ 11: "dd7a220",
36
+ 12: "f9f7852",
37
+ 13: "4204cef",
38
+ 14: "6b95651",
39
+ 15: "d23fd5a",
40
+ 16: "674fd56",
41
+ 17: "e43e7d0",
42
+ 18: "6745258",
43
+ 19: "6ea1659",
44
+ 20: "ebdc343",
45
+ 21: "b82338f",
46
+ 22: "6b6976c",
47
+ 23: "3652e0c",
48
+ 24: "ab7b2c1",
49
+ 25: "4d1b31e",
50
+ 26: "4dadfea",
51
+ 27: "e3dff7a",
52
+ 28: "cc26714",
53
+ 29: "5fda719",
54
+ 30: "cae7d03",
55
+ 31: "23f9ebd",
56
+ 32: "8e6c9c2",
57
+ 33: "fe28953",
58
+ 34: "d31580e",
59
+ 35: "da95621",
60
+ 36: "edc6dfe",
61
+ 37: "5a045a0",
62
+ 38: "50dfb9c",
63
+ 39: "6ef34b4",
64
+ 40: "102f187",
65
+ 41: "c1d8d4f",
66
+ 42: "4a0b48e",
67
+ 43: "635c550",
68
+ 44: "0266231",
69
+ 45: "8fd697f",
70
+ 46: "5107115",
71
+ 47: "608af85",
72
+ 48: "500d25b",
73
+ 49: "da3579b",
74
+ 50: "06ee20b",
75
+ 51: "43e16e2",
76
+ 52: "4c13496",
77
+ 53: "a1eb1a8",
78
+ 54: "141dbb5",
79
+ 55: "c9cbc38",
80
+ 56: "7306da6",
81
+ 57: "415103c",
82
+ 58: "57bbbbd",
83
+ 59: "1963258",
84
+ 62: "4fca0c9",
85
+ 63: "93260d7",
86
+ 64: "55a6cb8",
87
+ 65: "15afb5c"
88
+ }[e] + ".js"
89
+ }(e);
90
+ ```
91
+
92
+ ### Analysis of [Vue.js Amsterdam](https://vuejs.amsterdam/)
93
+ Apart from the methods mentioned above, it was found that a lot of JS paths were present in those which are already loaded. They were simple strings like `./sitemap.d7a8ffd7.js`, so they can be extracted on string analysis.
94
+
95
+ ## Client-Side Paths/URLs
96
+ ### Analysis of [GitLab's About Site](https://about.gitlab.com/)
97
+ It was found that the client-side paths/URLs were present in the https://about.gitlab.com/_nuxt-new/builds/meta/2d555104-4dad-4b33-a02f-128a966a0c7b.json file. This file was found in the following tab on the home page's source code:
98
+ ```html
99
+ <link rel="preload" as="fetch" fetchpriority="low" crossorigin="anonymous" href="/_nuxt-new/builds/meta/2d555104-4dad-4b33-a02f-128a966a0c7b.json">
100
+ ```
101
+
102
+ Here's a preview of the JSON file:
103
+ ```json
104
+ {
105
+ "id": "2d555104-4dad-4b33-a02f-128a966a0c7b",
106
+ "timestamp": 1750133987484,
107
+ "matcher": {
108
+ "static": {},
109
+ "wildcard": {},
110
+ "dynamic": {}
111
+ },
112
+ "prerendered": [
113
+ "/de-de/contact-sales",
114
+ "/de-de/get-help",
115
+ --snip--
116
+ "/contact-sales",
117
+ "/analysts",
118
+ --snip
119
+ "/ja-jp",
120
+ "/ja-jp/search",
121
+ "/",
122
+ "/search"
123
+ ]
124
+ }
125
+ ```
@@ -0,0 +1,9 @@
1
+ # Vue.js Research
2
+ ## Tech Detection
3
+ To detect Vue.js, following technique(s) can be used:
4
+ - Load the webpage in the browser, and find the `data-v-*` attribute
5
+ - Note that this attribute might NOT be present if the webpage is not loaded in the browser, i.e. by directly getting the page source
6
+ - However, it was found that most of the Vue.JS sites were using Nuxt. The research for this can be found at [Nuxt.js Research](./nuxt_js.md)
7
+
8
+ ## Lazy Loaded Files
9
+ ### Analysis of [Vue.JS official site](https://vuejs.org/)
@@ -0,0 +1,145 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import parser from "@babel/parser";
5
+ import _traverse from "@babel/traverse";
6
+ import prettier from "prettier";
7
+
8
+ const traverse = _traverse.default;
9
+
10
+ /**
11
+ * Extracts all string literals from all .js files in a given directory and its
12
+ * subdirectories and writes them to a JSON file.
13
+ * @param {string} directory - The directory to scan for .js files
14
+ * @param {string} output_file - The file to write the extracted strings to
15
+ */
16
+ const strings = async (
17
+ directory,
18
+ output_file,
19
+ extract_urls,
20
+ extracted_url_path,
21
+ ) => {
22
+ console.log(chalk.cyan("[i] Loading 'Strings' module"));
23
+
24
+ // check if the directory exists
25
+ if (!fs.existsSync(directory)) {
26
+ console.log(chalk.red("[!] Directory does not exist"));
27
+ return;
28
+ }
29
+
30
+ console.log(chalk.cyan(`[i] Scanning ${directory} directory`));
31
+
32
+ // get all files in the directory and sub-directories
33
+ const files = fs.readdirSync(directory, { recursive: true });
34
+
35
+ // filter out non JS files
36
+ const jsFiles = files.filter((file) => file.endsWith(".js"));
37
+
38
+ // read all JS files
39
+ let js_files_path = [];
40
+ for (const file of jsFiles) {
41
+ const filePath = path.join(directory, file);
42
+ if (!fs.lstatSync(filePath).isDirectory()) {
43
+ js_files_path.push(filePath);
44
+ }
45
+ }
46
+
47
+ console.log(chalk.cyan(`[i] Found ${js_files_path.length} JS files`));
48
+
49
+ // read all JS files
50
+ let all_strings = {};
51
+ for (const file of js_files_path) {
52
+ const fileContent = fs.readFileSync(file, "utf-8");
53
+
54
+ // parse the file contents with babel
55
+ const ast = parser.parse(fileContent, {
56
+ sourceType: "unambiguous",
57
+ plugins: ["jsx", "typescript"],
58
+ });
59
+
60
+ let strings = [];
61
+
62
+ traverse(ast, {
63
+ StringLiteral(path) {
64
+ strings.push(path.node.value);
65
+ },
66
+ });
67
+
68
+ all_strings[file] = strings;
69
+ }
70
+
71
+ let strings_count = 0;
72
+ for (const file of Object.keys(all_strings)) {
73
+ strings_count += all_strings[file].length;
74
+ }
75
+
76
+ console.log(chalk.cyan(`[i] Extracted ${strings_count} strings`));
77
+
78
+ // write to a JSON file
79
+ const formatted = await prettier.format(JSON.stringify(all_strings), {
80
+ parser: "json",
81
+ printWidth: 80,
82
+ singleQuote: true,
83
+ });
84
+ fs.writeFileSync(output_file, formatted);
85
+
86
+ console.log(chalk.green(`[✓] Extracted strings to ${output_file}`));
87
+
88
+ if (extract_urls) {
89
+ console.log(chalk.cyan("[i] Extracting URLs and paths from strings"));
90
+
91
+ let urls = [];
92
+ let paths = [];
93
+
94
+ for (const file of Object.keys(all_strings)) {
95
+ for (const string of all_strings[file]) {
96
+ if (string.match(/^https?:\/\/[a-zA-Z0-9\.\-_]+\/?.*$/)) {
97
+ // like https://site.com
98
+ urls.push(string);
99
+ }
100
+ if (string.match(/^\/.+$/)) {
101
+ // like /path/resource
102
+ // make sure that the path doesn't start with two special chars except '/_'
103
+ if (string.match(/^\/[^a-zA-Z0-9]/) && !string.startsWith("/_")) {
104
+ // ignore the path
105
+ } else {
106
+ paths.push(string);
107
+ }
108
+ }
109
+ if (string.match(/^[a-zA-Z0-9_\-]\/[a-zA-Z0-9_\-].*$/)) {
110
+ // like path/to/resource
111
+ paths.push(string);
112
+ }
113
+ if (string.startsWith("./") || string.startsWith("../")) {
114
+ // like "./path/to/resource" or "../path/to/resource"
115
+ paths.push(string);
116
+ }
117
+ }
118
+ }
119
+
120
+ // dedupe the two lists
121
+ urls = [...new Set(urls)];
122
+ paths = [...new Set(paths)];
123
+
124
+ console.log(
125
+ chalk.cyan(`[i] Found ${urls.length} URLs and ${paths.length} paths`),
126
+ );
127
+
128
+ // write to a JSON file
129
+ const formatted_urls = await prettier.format(
130
+ JSON.stringify({ urls, paths }),
131
+ {
132
+ parser: "json",
133
+ printWidth: 80,
134
+ singleQuote: true,
135
+ },
136
+ );
137
+ fs.writeFileSync(extracted_url_path, formatted_urls);
138
+
139
+ console.log(
140
+ chalk.green(`[✓] Written URLs and paths to ${extracted_url_path}`),
141
+ );
142
+ }
143
+ };
144
+
145
+ export default strings;
@@ -0,0 +1,156 @@
1
+ import chalk from "chalk";
2
+ import * as cheerio from "cheerio";
3
+ import makeRequest from "../utility/makeReq.js";
4
+ import puppeteer from "puppeteer";
5
+
6
+ /**
7
+ * Detects if a webpage uses Next.js by checking if any HTML tag has a src,
8
+ * srcset, or imageSrcSet attribute that starts with "/_next/".
9
+ * @param {CheerioStatic} $ - The Cheerio object containing the parsed HTML.
10
+ * @returns {Promise<{detected: boolean, evidence: string}>}
11
+ * A promise that resolves to an object with two properties:
12
+ * - detected: A boolean indicating whether Next.js was detected.
13
+ * - evidence: A string with the evidence of the detection, or an empty string
14
+ * if Next.js was not detected.
15
+ */
16
+ const checkNextJS = async ($) => {
17
+ let detected = false;
18
+ let evidence = "";
19
+ // iterate through each HTML tag, and file tag value that starts with `/_next/`
20
+ $("*").each((_, el) => {
21
+ const tag = $(el).get(0).tagName;
22
+
23
+ // check the value of three attributes
24
+ const src = $(el).attr("src");
25
+ const srcSet = $(el).attr("srcset");
26
+ const imageSrcSet = $(el).attr("imageSrcSet");
27
+
28
+ if (src || srcSet || imageSrcSet) {
29
+ if (src && src.startsWith("/_next/")) {
30
+ detected = true;
31
+ evidence = `${tag} :: ${src}`;
32
+ } else if (srcSet && srcSet.startsWith("/_next/")) {
33
+ detected = true;
34
+ evidence = `${tag} :: ${srcSet}`;
35
+ } else if (imageSrcSet && imageSrcSet.startsWith("/_next/")) {
36
+ detected = true;
37
+ evidence = `${tag} :: ${imageSrcSet}`;
38
+ }
39
+ }
40
+ });
41
+
42
+ return { detected, evidence };
43
+ };
44
+
45
+ /**
46
+ * Detects if a webpage uses Vue.js by checking if any HTML tag has a data-v-* attribute.
47
+ * @param {CheerioStatic} $ - The Cheerio object containing the parsed HTML.
48
+ * @returns {Promise<{detected: boolean, evidence: string}>}
49
+ * A promise that resolves to an object with two properties:
50
+ * - detected: A boolean indicating whether Vue.js was detected.
51
+ * - evidence: A string with the evidence of the detection, or an empty string
52
+ * if Vue.js was not detected.
53
+ */
54
+ const checkVueJS = async ($) => {
55
+ let detected = false;
56
+ let evidence = "";
57
+
58
+ $("*").each((_, el) => {
59
+ const tag = $(el).get(0).tagName;
60
+ const attribs = el.attribs;
61
+ if (attribs) {
62
+ for (const [attrName, attrValue] of Object.entries(attribs)) {
63
+ if (attrName.startsWith("data-v-")) {
64
+ detected = true;
65
+ evidence = `${tag} :: ${attrName}`;
66
+ }
67
+ }
68
+ }
69
+ });
70
+
71
+ return { detected, evidence };
72
+ };
73
+
74
+
75
+ const checkNuxtJS = async ($)=>{
76
+ let detected = false;
77
+ let evidence = "";
78
+
79
+ // go through the page source, and check for "/_nuxt" in the src or href attribute
80
+ $("*").each((_, el)=>{
81
+ const tag = $(el).get(0).tagName;
82
+ const attribs = el.attribs;
83
+ if (attribs) {
84
+ for (const [attrName, attrValue] of Object.entries(attribs)) {
85
+ if (attrName === "src" || attrName === "href") {
86
+ if (attrValue.includes("/_nuxt")) {
87
+ detected = true;
88
+ evidence = `${attrName} :: ${attrValue}`;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ });
94
+
95
+ return { detected, evidence };
96
+ }
97
+
98
+ /**
99
+ * Detects the front-end framework used by a webpage.
100
+ * @param {string} url - The URL of the webpage to be detected.
101
+ * @returns {Promise<{name: string, evidence: string}> | null}
102
+ * A promise that resolves to an object with two properties:
103
+ * - name: A string indicating the detected framework, or null if no framework was detected.
104
+ * - evidence: A string with the evidence of the detection, or an empty string if no framework was detected.
105
+ */
106
+ const frameworkDetect = async (url) => {
107
+ console.log(chalk.cyan("[i] Detecting front-end framework"));
108
+
109
+ // get the page source
110
+ const res = await makeRequest(url);
111
+
112
+ // get the page source in the browser
113
+ const browser = await puppeteer.launch({
114
+ headless: true,
115
+ args: [
116
+ "--disable-gpu",
117
+ "--disable-dev-shm-usage",
118
+ "--disable-setuid-sandbox",
119
+ "--no-sandbox",
120
+ ],
121
+ });
122
+ const page = await browser.newPage();
123
+ await page.goto(url);
124
+ await new Promise((resolve) => setTimeout(resolve, 5000));
125
+ const pageSource = await page.content();
126
+ await browser.close();
127
+
128
+ // if (res === null || res === undefined) {
129
+ // return;
130
+ // }
131
+
132
+ // const pageSource = await res.text();
133
+
134
+ // cheerio to parse the page source
135
+ const $ = cheerio.load(pageSource);
136
+
137
+ // check all technologies one by one
138
+ const result_checkNextJS = await checkNextJS($);
139
+ const result_checkVueJS = await checkVueJS($);
140
+
141
+ if (result_checkNextJS.detected === true) {
142
+ return { name: "next", evidence: result_checkNextJS.evidence };
143
+ } else if (result_checkVueJS.detected === true) {
144
+ console.log(chalk.green("[✓] Vue.js detected"));
145
+ console.log(chalk.cyan(`[i] Checking Nuxt.JS`), chalk.dim("(Nuxt.JS is built on Vue.js)"));
146
+ const result_checkNuxtJS = await checkNuxtJS($);
147
+ if (result_checkNuxtJS.detected === true) {
148
+ return { name: "nuxt", evidence: result_checkNuxtJS.evidence };
149
+ }
150
+ return { name: "vue", evidence: result_checkVueJS.evidence };
151
+ }
152
+
153
+ return null;
154
+ };
155
+
156
+ export default frameworkDetect;
@@ -0,0 +1,6 @@
1
+
2
+ export let apiGatewayConfigFile = "";
3
+ export let useApiGateway = false;
4
+
5
+ export const setApiGatewayConfigFile = (file) => { apiGatewayConfigFile = file; };
6
+ export const setUseApiGateway = (value) => { useApiGateway = value; };
@@ -0,0 +1,179 @@
1
+ import chalk from "chalk";
2
+ import puppeteer from "puppeteer";
3
+ import * as globals from "./globals.js";
4
+ import { get } from "../api_gateway/genReq.js";
5
+
6
+ // random user agents
7
+ const UAs = [
8
+ "Chrome/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
9
+ "Chrome/Windows: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
10
+ "Chrome/Windows: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
11
+ "Chrome/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
12
+ "Chrome/Linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
13
+ "Chrome/iPhone: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
14
+ "Chrome/iPhone (request desktop): Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87 Version/11.1.1 Safari/605.1.15",
15
+ "Chrome/iPad: Mozilla/5.0 (iPad; CPU OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
16
+ "Chrome/iPod: Mozilla/5.0 (iPod; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
17
+ "Chrome/Android: Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36",
18
+ "Chrome/Android: Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36",
19
+ "Chrome/Android: Mozilla/5.0 (Linux; Android 10; LM-Q720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36",
20
+ "Firefox/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0",
21
+ "Firefox/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11.1; rv:84.0) Gecko/20100101 Firefox/84.0",
22
+ "Firefox/Linux: Mozilla/5.0 (X11; Linux i686; rv:84.0) Gecko/20100101 Firefox/84.0",
23
+ "Firefox/iPhone: Mozilla/5.0 (iPhone; CPU iPhone OS 11_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/30.0 Mobile/15E148 Safari/605.1.15",
24
+ "Firefox/iPad: Mozilla/5.0 (iPad; CPU OS 11_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/30.0 Mobile/15E148 Safari/605.1.15",
25
+ "Firefox/Android: Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/84.0",
26
+ "Safari/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
27
+ "Safari/iPhone: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
28
+ "Safari/iPhone (request desktop): Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15",
29
+ "Safari/iPad: Mozilla/5.0 (iPad; CPU OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
30
+ "IE11/Windows: Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko",
31
+ "Edge/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66",
32
+ "Edge/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66",
33
+ "Edge/Android: Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36 EdgA/45.12.4.5121",
34
+ "Edge/iOS: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 EdgiOS/45.11.11 Mobile/15E148 Safari/605.1.15",
35
+ "Opera/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 OPR/73.0.3856.329",
36
+ "Opera/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 OPR/73.0.3856.329",
37
+ "Opera/Linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 OPR/73.0.3856.329",
38
+ "Opera/Android: Mozilla/5.0 (Linux; Android 10; VOG-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36 OPR/61.1.3076.56625",
39
+ "Vivaldi/Windows: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
40
+ "Vivaldi/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
41
+ "Vivaldi/Linux: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
42
+ "Yandex/Windows: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 YaBrowser/20.12.0 Yowser/2.5 Safari/537.36",
43
+ "Yandex/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 YaBrowser/20.12.0 Yowser/2.5 Safari/537.36",
44
+ "Yandex/iOS: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 YaBrowser/20.11.2.199 Mobile/15E148 Safari/604.1",
45
+ "Yandex/Android: Mozilla/5.0 (Linux; arm_64; Android 11; SM-G965F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 YaBrowser/20.12.29.180 Mobile Safari/537.36",
46
+ "Chrome/ChromeOS: Mozilla/5.0 (X11; CrOS x86_64 13505.63.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
47
+ "Safari/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15",
48
+ "Firefox/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11.1; rv:84.0) Gecko/20100101 Firefox/84.0",
49
+ "Chrome/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
50
+ "Vivaldi/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
51
+ "Edge/macOS: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66",
52
+ "Safari/iOS: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
53
+ "Chrome/iOS: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1",
54
+ "Firefox/iOS: Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/30.0 Mobile/15E148 Safari/605.1.15",
55
+ "Edge/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66",
56
+ "Internet-Explorer/Windows: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
57
+ "Chrome/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
58
+ "Firefox/Windows: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0",
59
+ "Vivaldi/Windows: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Vivaldi/3.5",
60
+ "Chrome/Android: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36",
61
+ "Firefox/Android: Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/84.0",
62
+ ];
63
+
64
+ const makeRequest = async (url, args) => {
65
+ if (globals.useApiGateway) {
66
+ let get_headers;
67
+ if (args && args.headers) {
68
+ get_headers = args.headers;
69
+ } else {
70
+ get_headers = {
71
+ "User-Agent": UAs[Math.floor(Math.random() * UAs.length)],
72
+ Accept:
73
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
74
+ "Accept-Language": "en-US,en;q=0.9",
75
+ "Sec-Fetch-Site": "same-origin",
76
+ "Sec-Fetch-Mode": "cors",
77
+ "Sec-Fetch-Dest": "empty",
78
+ Referer: url,
79
+ Origin: url,
80
+ };
81
+ }
82
+
83
+ const body = await get(url, get_headers);
84
+
85
+ // craft a Response, and return that
86
+ const response = new Response(body);
87
+ return response;
88
+ } else {
89
+ if (args === undefined) {
90
+ args = {
91
+ headers: {
92
+ "User-Agent": UAs[Math.floor(Math.random() * UAs.length)],
93
+ Accept:
94
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
95
+ "Accept-Language": "en-US,en;q=0.9",
96
+ "Sec-Fetch-Site": "same-origin",
97
+ "Sec-Fetch-Mode": "cors",
98
+ "Sec-Fetch-Dest": "empty",
99
+ Referer: url,
100
+ Origin: url,
101
+ },
102
+ };
103
+ }
104
+ let res;
105
+ let counter = 0;
106
+ while (true) {
107
+ try {
108
+ res = await fetch(url, args);
109
+ if (res) {
110
+ break;
111
+ }
112
+ } catch (err) {
113
+ counter++;
114
+ if (counter > 10) {
115
+ console.log(chalk.red(`[!] Failed to fetch ${url}`));
116
+ return null;
117
+ }
118
+ // sleep 0.5 s before retrying
119
+ await new Promise((resolve) => setTimeout(resolve, 500));
120
+ continue;
121
+ }
122
+ }
123
+
124
+ const preservedRes = res.clone();
125
+
126
+ // check if this is a firewall
127
+ // CF first
128
+ const resp_text = await res.text();
129
+ if (resp_text.includes("/?bm-verify=")) {
130
+ console.log(
131
+ chalk.yellow(
132
+ `[!] CF Firewall detected. Trying to bypass with headless browser`,
133
+ ),
134
+ );
135
+ // if it is, load it in a headless browser
136
+ const browser = await puppeteer.launch({
137
+ headless: true,
138
+ args: [
139
+ "--disable-gpu",
140
+ "--disable-dev-shm-usage",
141
+ "--disable-setuid-sandbox",
142
+ "--no-sandbox",
143
+ ],
144
+ });
145
+ const page = await browser.newPage();
146
+ await page.goto(url);
147
+ await new Promise((resolve) => setTimeout(resolve, 5000));
148
+ const content = await page.content();
149
+ await browser.close();
150
+ return new Response(content);
151
+ } else if (resp_text.includes("<title>Just a moment...</title>")) {
152
+ console.log(
153
+ chalk.yellow(
154
+ `[!] CF Firewall detected. Trying to bypass with headless browser`,
155
+ ),
156
+ );
157
+ // if it is, load it in a headless browser
158
+ const browser = await puppeteer.launch({
159
+ headless: true,
160
+ args: [
161
+ "--disable-gpu",
162
+ "--disable-dev-shm-usage",
163
+ "--disable-setuid-sandbox",
164
+ "--no-sandbox",
165
+ ],
166
+ });
167
+ const page = await browser.newPage();
168
+ await page.goto(url);
169
+ await new Promise((resolve) => setTimeout(resolve, 5000));
170
+ const content = await page.content();
171
+ await browser.close();
172
+ return new Response(content);
173
+ }
174
+
175
+ return preservedRes;
176
+ }
177
+ };
178
+
179
+ export default makeRequest;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Resolves a given path against a base URL using the URL constructor.
3
+ *
4
+ * The function handles various cases of path resolution:
5
+ * - If the base URL does not end with a '/', its last segment is treated as a "file",
6
+ * and relative paths are resolved from its "directory".
7
+ * - Examples:
8
+ * - url='https://site.com/something', path='./main.js' => 'https://site.com/main.js'
9
+ * (Base for resolution becomes 'https://site.com/')
10
+ * - url='https://site.com/something/', path='./main.js' => 'https://site.com/something/main.js'
11
+ * (Base for resolution is 'https://site.com/something/')
12
+ * - url='https://site.com/something/other', path='../main.js' => 'https://site.com/main.js'
13
+ * (Base for resolution becomes 'https://site.com/something/', then '../' navigates up)
14
+ *
15
+ * @param {string} url - The base URL to resolve against.
16
+ * @param {string} path - The path to resolve.
17
+ * @returns {Promise<string>} - A promise that resolves to the fully resolved URL as a string.
18
+ * @throws Will throw an error if the resolution fails.
19
+ */
20
+ const resolvePath = async (url, path) => {
21
+ try {
22
+ // The URL constructor handles various cases of path resolution.
23
+ // If 'url' (the base URL) does not end with a '/', its last path segment
24
+ // is typically treated as a "file", and relative paths are resolved
25
+ // from the "directory" containing that "file".
26
+ // This behavior aligns with the provided examples:
27
+ // - url='https://site.com/something', path='./main.js' => 'https://site.com/main.js'
28
+ // (Base for resolution becomes 'https://site.com/')
29
+ // - url='https://site.com/something/', path='./main.js' => 'https://site.com/something/main.js'
30
+ // (Base for resolution is 'https://site.com/something/')
31
+ // - url='https://site.com/something/other', path='../main.js' => 'https://site.com/main.js'
32
+ // (Base for resolution becomes 'https://site.com/something/', then '../' navigates up)
33
+ const resolvedUrl = new URL(path, url);
34
+ return resolvedUrl.href;
35
+ } catch (e) {
36
+ console.error(`Error resolving path "${path}" with base URL "${url}": ${e.message}`);
37
+ // Rethrowing the error to signal failure to the caller.
38
+ // Alternative error handling (e.g., returning null) can be implemented if required.
39
+ throw e;
40
+ }
41
+ };
42
+
43
+ export default resolvePath;