@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,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/)
|
package/strings/index.js
ADDED
|
@@ -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,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;
|