@nodesecure/js-x-ray 9.0.0 → 9.1.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/LICENSE +21 -21
- package/README.md +9 -209
- package/package.json +62 -77
- package/src/{AstAnalyser.js → AstAnalyser.ts} +283 -222
- package/src/{Deobfuscator.js → Deobfuscator.ts} +228 -195
- package/src/{EntryFilesAnalyser.js → EntryFilesAnalyser.ts} +206 -167
- package/src/JsSourceParser.ts +77 -0
- package/src/NodeCounter.ts +90 -0
- package/src/{ProbeRunner.js → ProbeRunner.ts} +167 -144
- package/src/SourceFile.ts +226 -0
- package/src/index.ts +5 -0
- package/src/obfuscators/freejsobfuscator.ts +17 -0
- package/src/obfuscators/{jjencode.js → jjencode.ts} +39 -27
- package/src/obfuscators/jsfuck.ts +19 -0
- package/src/obfuscators/{obfuscator-io.js → obfuscator-io.ts} +25 -13
- package/src/obfuscators/{trojan-source.js → trojan-source.ts} +3 -1
- package/src/probes/{isArrayExpression.js → isArrayExpression.ts} +12 -3
- package/src/probes/{isBinaryExpression.js → isBinaryExpression.ts} +74 -55
- package/src/probes/isESMExport.ts +50 -0
- package/src/probes/{isFetch.js → isFetch.ts} +28 -19
- package/src/probes/isImportDeclaration.ts +58 -0
- package/src/probes/{isLiteral.js → isLiteral.ts} +91 -70
- package/src/probes/isLiteralRegex.ts +42 -0
- package/src/probes/{isRegexObject.js → isRegexObject.ts} +71 -49
- package/src/probes/isRequire/RequireCallExpressionWalker.ts +142 -0
- package/src/probes/isRequire/{isRequire.js → isRequire.ts} +195 -148
- package/src/probes/isSerializeEnv.ts +65 -0
- package/src/probes/isSyncIO.ts +96 -0
- package/src/probes/isUnsafeCallee.ts +89 -0
- package/src/probes/isUnsafeCommand.ts +133 -0
- package/src/probes/isWeakCrypto.ts +69 -0
- package/src/types/estree.ts +35 -0
- package/src/utils/extractNode.ts +22 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/{exportAssignmentHasRequireLeave.js → isOneLineExpressionExport.ts} +70 -40
- package/src/utils/notNullOrUndefined.ts +5 -0
- package/src/utils/toArrayLocation.ts +22 -0
- package/src/warnings.ts +146 -0
- package/index.d.ts +0 -46
- package/index.js +0 -4
- package/src/JsSourceParser.js +0 -57
- package/src/NodeCounter.js +0 -76
- package/src/SourceFile.js +0 -147
- package/src/obfuscators/freejsobfuscator.js +0 -9
- package/src/obfuscators/jsfuck.js +0 -11
- package/src/probes/isESMExport.js +0 -31
- package/src/probes/isImportDeclaration.js +0 -33
- package/src/probes/isLiteralRegex.js +0 -31
- package/src/probes/isRequire/RequireCallExpressionWalker.js +0 -93
- package/src/probes/isUnsafeCallee.js +0 -35
- package/src/probes/isWeakCrypto.js +0 -37
- package/src/utils/extractNode.js +0 -14
- package/src/utils/index.js +0 -8
- package/src/utils/isNode.js +0 -5
- package/src/utils/isOneLineExpressionExport.js +0 -24
- package/src/utils/isUnsafeCallee.js +0 -28
- package/src/utils/notNullOrUndefined.js +0 -3
- package/src/utils/rootLocation.js +0 -3
- package/src/utils/toArrayLocation.js +0 -11
- package/src/warnings.js +0 -77
- package/types/api.d.ts +0 -177
- package/types/warnings.d.ts +0 -36
package/src/warnings.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Import Third-party Dependencies
|
|
2
|
+
import type { ESTree } from "meriyah";
|
|
3
|
+
|
|
4
|
+
// Import Internal Dependencies
|
|
5
|
+
import {
|
|
6
|
+
toArrayLocation,
|
|
7
|
+
rootLocation,
|
|
8
|
+
type SourceArrayLocation
|
|
9
|
+
} from "./utils/toArrayLocation.js";
|
|
10
|
+
import { notNullOrUndefined } from "./utils/notNullOrUndefined.js";
|
|
11
|
+
|
|
12
|
+
export type OptionalWarningName =
|
|
13
|
+
| "synchronous-io";
|
|
14
|
+
|
|
15
|
+
export type WarningName =
|
|
16
|
+
| "parsing-error"
|
|
17
|
+
| "encoded-literal"
|
|
18
|
+
| "unsafe-regex"
|
|
19
|
+
| "unsafe-stmt"
|
|
20
|
+
| "short-identifiers"
|
|
21
|
+
| "suspicious-literal"
|
|
22
|
+
| "suspicious-file"
|
|
23
|
+
| "obfuscated-code"
|
|
24
|
+
| "weak-crypto"
|
|
25
|
+
| "shady-link"
|
|
26
|
+
| "unsafe-command"
|
|
27
|
+
| "unsafe-import"
|
|
28
|
+
| "serialize-environment"
|
|
29
|
+
| OptionalWarningName;
|
|
30
|
+
|
|
31
|
+
export interface Warning<T = WarningName> {
|
|
32
|
+
kind: T | (string & {});
|
|
33
|
+
file?: string;
|
|
34
|
+
value: string | null;
|
|
35
|
+
source: string;
|
|
36
|
+
location: null | SourceArrayLocation | SourceArrayLocation[];
|
|
37
|
+
i18n: string;
|
|
38
|
+
severity: "Information" | "Warning" | "Critical";
|
|
39
|
+
experimental?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const warnings = Object.freeze({
|
|
43
|
+
"parsing-error": {
|
|
44
|
+
i18n: "sast_warnings.parsing_error",
|
|
45
|
+
severity: "Information"
|
|
46
|
+
},
|
|
47
|
+
"unsafe-import": {
|
|
48
|
+
i18n: "sast_warnings.unsafe_import",
|
|
49
|
+
severity: "Warning"
|
|
50
|
+
},
|
|
51
|
+
"unsafe-regex": {
|
|
52
|
+
i18n: "sast_warnings.unsafe_regex",
|
|
53
|
+
severity: "Warning"
|
|
54
|
+
},
|
|
55
|
+
"unsafe-stmt": {
|
|
56
|
+
code: "unsafe-stmt",
|
|
57
|
+
i18n: "sast_warnings.unsafe_stmt",
|
|
58
|
+
severity: "Warning"
|
|
59
|
+
},
|
|
60
|
+
"encoded-literal": {
|
|
61
|
+
i18n: "sast_warnings.encoded_literal",
|
|
62
|
+
severity: "Information"
|
|
63
|
+
},
|
|
64
|
+
"short-identifiers": {
|
|
65
|
+
i18n: "sast_warnings.short_identifiers",
|
|
66
|
+
severity: "Warning"
|
|
67
|
+
},
|
|
68
|
+
"suspicious-literal": {
|
|
69
|
+
i18n: "sast_warnings.suspicious_literal",
|
|
70
|
+
severity: "Warning"
|
|
71
|
+
},
|
|
72
|
+
"suspicious-file": {
|
|
73
|
+
i18n: "sast_warnings.suspicious_file",
|
|
74
|
+
severity: "Critical",
|
|
75
|
+
experimental: false
|
|
76
|
+
},
|
|
77
|
+
"obfuscated-code": {
|
|
78
|
+
i18n: "sast_warnings.obfuscated_code",
|
|
79
|
+
severity: "Critical",
|
|
80
|
+
experimental: true
|
|
81
|
+
},
|
|
82
|
+
"weak-crypto": {
|
|
83
|
+
i18n: "sast_warnings.weak_crypto",
|
|
84
|
+
severity: "Information",
|
|
85
|
+
experimental: false
|
|
86
|
+
},
|
|
87
|
+
"shady-link": {
|
|
88
|
+
i18n: "sast_warnings.shady_link",
|
|
89
|
+
severity: "Warning",
|
|
90
|
+
experimental: false
|
|
91
|
+
},
|
|
92
|
+
"unsafe-command": {
|
|
93
|
+
i18n: "sast_warnings.unsafe-command",
|
|
94
|
+
severity: "Warning",
|
|
95
|
+
experimental: true
|
|
96
|
+
},
|
|
97
|
+
"synchronous-io": {
|
|
98
|
+
i18n: "sast_warnings.synchronous-io",
|
|
99
|
+
severity: "Warning",
|
|
100
|
+
experimental: true
|
|
101
|
+
},
|
|
102
|
+
"serialize-environment": {
|
|
103
|
+
i18n: "sast_warnings.serialize-environment",
|
|
104
|
+
severity: "Warning",
|
|
105
|
+
experimental: false
|
|
106
|
+
}
|
|
107
|
+
}) satisfies Record<WarningName, Pick<Warning, "experimental" | "i18n" | "severity">>;
|
|
108
|
+
|
|
109
|
+
export interface GenerateWarningOptions {
|
|
110
|
+
location?: ESTree.SourceLocation | null;
|
|
111
|
+
file?: string | null;
|
|
112
|
+
value: string | null;
|
|
113
|
+
source?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function generateWarning<T extends WarningName>(
|
|
117
|
+
kind: T,
|
|
118
|
+
options: GenerateWarningOptions
|
|
119
|
+
): Warning<T> {
|
|
120
|
+
const {
|
|
121
|
+
file = null,
|
|
122
|
+
value,
|
|
123
|
+
source = "JS-X-Ray"
|
|
124
|
+
} = options;
|
|
125
|
+
const location = options.location ?? rootLocation();
|
|
126
|
+
|
|
127
|
+
if (kind === "encoded-literal") {
|
|
128
|
+
return {
|
|
129
|
+
kind,
|
|
130
|
+
value,
|
|
131
|
+
location: [toArrayLocation(location)],
|
|
132
|
+
source,
|
|
133
|
+
...warnings[kind]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
kind,
|
|
139
|
+
location: toArrayLocation(location),
|
|
140
|
+
source,
|
|
141
|
+
...warnings[kind],
|
|
142
|
+
...(notNullOrUndefined(file) ? { file } : {}),
|
|
143
|
+
...(notNullOrUndefined(value) ? { value } : { value: null })
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
package/index.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AstAnalyser,
|
|
3
|
-
AstAnalyserOptions,
|
|
4
|
-
|
|
5
|
-
EntryFilesAnalyser,
|
|
6
|
-
EntryFilesAnalyserOptions,
|
|
7
|
-
|
|
8
|
-
SourceParser,
|
|
9
|
-
JsSourceParser,
|
|
10
|
-
Report,
|
|
11
|
-
ReportOnFile,
|
|
12
|
-
RuntimeFileOptions,
|
|
13
|
-
RuntimeOptions,
|
|
14
|
-
SourceLocation,
|
|
15
|
-
Dependency
|
|
16
|
-
} from "./types/api.js";
|
|
17
|
-
import {
|
|
18
|
-
Warning,
|
|
19
|
-
WarningDefault,
|
|
20
|
-
WarningLocation,
|
|
21
|
-
WarningName,
|
|
22
|
-
WarningNameWithValue
|
|
23
|
-
} from "./types/warnings.js";
|
|
24
|
-
|
|
25
|
-
declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental" | "i18n" | "severity">>;
|
|
26
|
-
|
|
27
|
-
export {
|
|
28
|
-
warnings,
|
|
29
|
-
AstAnalyser,
|
|
30
|
-
AstAnalyserOptions,
|
|
31
|
-
EntryFilesAnalyser,
|
|
32
|
-
EntryFilesAnalyserOptions,
|
|
33
|
-
JsSourceParser,
|
|
34
|
-
SourceParser,
|
|
35
|
-
Report,
|
|
36
|
-
ReportOnFile,
|
|
37
|
-
RuntimeFileOptions,
|
|
38
|
-
RuntimeOptions,
|
|
39
|
-
SourceLocation,
|
|
40
|
-
Dependency,
|
|
41
|
-
Warning,
|
|
42
|
-
WarningDefault,
|
|
43
|
-
WarningLocation,
|
|
44
|
-
WarningName,
|
|
45
|
-
WarningNameWithValue
|
|
46
|
-
}
|
package/index.js
DELETED
package/src/JsSourceParser.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// Import Third-party Dependencies
|
|
2
|
-
import * as meriyah from "meriyah";
|
|
3
|
-
|
|
4
|
-
// CONSTANTS
|
|
5
|
-
const kParsingOptions = {
|
|
6
|
-
next: true,
|
|
7
|
-
loc: true,
|
|
8
|
-
raw: true,
|
|
9
|
-
jsx: true
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export class JsSourceParser {
|
|
13
|
-
/**
|
|
14
|
-
* @param {object} options
|
|
15
|
-
* @param {boolean} options.isEcmaScriptModule
|
|
16
|
-
*/
|
|
17
|
-
parse(source, options = {}) {
|
|
18
|
-
const {
|
|
19
|
-
isEcmaScriptModule
|
|
20
|
-
} = options;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const { body } = meriyah.parseScript(
|
|
24
|
-
source,
|
|
25
|
-
{
|
|
26
|
-
...kParsingOptions,
|
|
27
|
-
module: isEcmaScriptModule,
|
|
28
|
-
globalReturn: !isEcmaScriptModule
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return body;
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
const isIllegalReturn = error.description.includes("Illegal return statement");
|
|
36
|
-
|
|
37
|
-
if (error.name === "SyntaxError" && (
|
|
38
|
-
error.description.includes("The import keyword") ||
|
|
39
|
-
error.description.includes("The export keyword") ||
|
|
40
|
-
isIllegalReturn
|
|
41
|
-
)) {
|
|
42
|
-
const { body } = meriyah.parseScript(
|
|
43
|
-
source,
|
|
44
|
-
{
|
|
45
|
-
...kParsingOptions,
|
|
46
|
-
module: true,
|
|
47
|
-
globalReturn: isIllegalReturn
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return body;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/NodeCounter.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Import Third-party Dependencies
|
|
2
|
-
import FrequencySet from "frequency-set";
|
|
3
|
-
|
|
4
|
-
// Import Internal Dependencies
|
|
5
|
-
import { isNode } from "./utils/index.js";
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line func-style
|
|
8
|
-
const noop = (node) => true;
|
|
9
|
-
|
|
10
|
-
export class NodeCounter {
|
|
11
|
-
lookup = null;
|
|
12
|
-
|
|
13
|
-
#count = 0;
|
|
14
|
-
#properties = null;
|
|
15
|
-
#filterFn = noop;
|
|
16
|
-
#matchFn = noop;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {!string} type
|
|
20
|
-
* @param {Object} [options]
|
|
21
|
-
* @param {string} [options.name]
|
|
22
|
-
* @param {(node: any) => boolean} [options.filter]
|
|
23
|
-
* @param {(node: any, nc: NodeCounter) => void} [options.match]
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* new NodeCounter("FunctionDeclaration");
|
|
27
|
-
* new NodeCounter("VariableDeclaration[kind]");
|
|
28
|
-
*/
|
|
29
|
-
constructor(type, options = {}) {
|
|
30
|
-
if (typeof type !== "string") {
|
|
31
|
-
throw new TypeError("type must be a string");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const typeResult = /([A-Za-z]+)(\[[a-zA-Z]+\])?/g.exec(type);
|
|
35
|
-
if (typeResult === null) {
|
|
36
|
-
throw new Error("invalid type argument syntax");
|
|
37
|
-
}
|
|
38
|
-
this.type = typeResult[1];
|
|
39
|
-
this.lookup = typeResult[2]?.slice(1, -1) ?? null;
|
|
40
|
-
this.name = options?.name ?? this.type;
|
|
41
|
-
if (this.lookup) {
|
|
42
|
-
this.#properties = new FrequencySet();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this.#filterFn = options.filter ?? noop;
|
|
46
|
-
this.#matchFn = options.match ?? noop;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
get count() {
|
|
50
|
-
return this.#count;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get properties() {
|
|
54
|
-
return Object.fromEntries(
|
|
55
|
-
this.#properties?.entries() ?? []
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
walk(node) {
|
|
60
|
-
if (!isNode(node) || node.type !== this.type) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (!this.#filterFn(node)) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
this.#count++;
|
|
68
|
-
if (this.lookup === null) {
|
|
69
|
-
this.#matchFn(node, this);
|
|
70
|
-
}
|
|
71
|
-
else if (this.lookup in node) {
|
|
72
|
-
this.#properties.add(node[this.lookup]);
|
|
73
|
-
this.#matchFn(node, this);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
package/src/SourceFile.js
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
// Import Third-party Dependencies
|
|
2
|
-
import { Utils, Literal } from "@nodesecure/sec-literal";
|
|
3
|
-
import { VariableTracer } from "@nodesecure/estree-ast-utils";
|
|
4
|
-
|
|
5
|
-
// Import Internal Dependencies
|
|
6
|
-
import { rootLocation, toArrayLocation } from "./utils/index.js";
|
|
7
|
-
import { generateWarning } from "./warnings.js";
|
|
8
|
-
import { ProbeRunner } from "./ProbeRunner.js";
|
|
9
|
-
import { Deobfuscator } from "./Deobfuscator.js";
|
|
10
|
-
import * as trojan from "./obfuscators/trojan-source.js";
|
|
11
|
-
|
|
12
|
-
// CONSTANTS
|
|
13
|
-
const kMaximumEncodedLiterals = 10;
|
|
14
|
-
|
|
15
|
-
export class SourceFile {
|
|
16
|
-
inTryStatement = false;
|
|
17
|
-
dependencyAutoWarning = false;
|
|
18
|
-
deobfuscator = new Deobfuscator();
|
|
19
|
-
dependencies = new Map();
|
|
20
|
-
encodedLiterals = new Map();
|
|
21
|
-
warnings = [];
|
|
22
|
-
/** @type {Set<string>} */
|
|
23
|
-
flags = new Set();
|
|
24
|
-
|
|
25
|
-
constructor(sourceCodeString, probesOptions = {}) {
|
|
26
|
-
this.tracer = new VariableTracer()
|
|
27
|
-
.enableDefaultTracing()
|
|
28
|
-
.trace("crypto.createHash", {
|
|
29
|
-
followConsecutiveAssignment: true, moduleName: "crypto"
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
let probes = ProbeRunner.Defaults;
|
|
33
|
-
if (Array.isArray(probesOptions.customProbes) && probesOptions.customProbes.length > 0) {
|
|
34
|
-
probes = probesOptions.skipDefaultProbes === true ? probesOptions.customProbes : [...probes, ...probesOptions.customProbes];
|
|
35
|
-
}
|
|
36
|
-
this.probesRunner = new ProbeRunner(this, probes);
|
|
37
|
-
|
|
38
|
-
if (trojan.verify(sourceCodeString)) {
|
|
39
|
-
this.addWarning("obfuscated-code", "trojan-source");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
addDependency(name, location = null, unsafe = this.dependencyAutoWarning) {
|
|
44
|
-
if (typeof name !== "string" || name.trim() === "") {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const dependencyName = name.charAt(name.length - 1) === "/" ?
|
|
49
|
-
name.slice(0, -1) : name;
|
|
50
|
-
this.dependencies.set(dependencyName, {
|
|
51
|
-
unsafe,
|
|
52
|
-
inTry: this.inTryStatement,
|
|
53
|
-
...(location === null ? {} : { location })
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (this.dependencyAutoWarning) {
|
|
57
|
-
this.addWarning("unsafe-import", dependencyName, location);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
addWarning(name, value, location = rootLocation()) {
|
|
62
|
-
const isEncodedLiteral = name === "encoded-literal";
|
|
63
|
-
if (isEncodedLiteral) {
|
|
64
|
-
if (this.encodedLiterals.size > kMaximumEncodedLiterals) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (this.encodedLiterals.has(value)) {
|
|
69
|
-
const index = this.encodedLiterals.get(value);
|
|
70
|
-
this.warnings[index].location.push(toArrayLocation(location));
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.warnings.push(generateWarning(name, { value, location }));
|
|
77
|
-
if (isEncodedLiteral) {
|
|
78
|
-
this.encodedLiterals.set(value, this.warnings.length - 1);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
analyzeLiteral(node, inArrayExpr = false) {
|
|
83
|
-
if (typeof node.value !== "string" || Utils.isSvg(node)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
this.deobfuscator.analyzeString(node.value);
|
|
87
|
-
|
|
88
|
-
const { hasHexadecimalSequence, hasUnicodeSequence, isBase64 } = Literal.defaultAnalysis(node);
|
|
89
|
-
if ((hasHexadecimalSequence || hasUnicodeSequence) && isBase64) {
|
|
90
|
-
if (inArrayExpr) {
|
|
91
|
-
this.deobfuscator.encodedArrayValue++;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
this.addWarning("encoded-literal", node.value, node.loc);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
getResult(isMinified) {
|
|
100
|
-
const obfuscatorName = this.deobfuscator.assertObfuscation(this);
|
|
101
|
-
if (obfuscatorName !== null) {
|
|
102
|
-
this.addWarning("obfuscated-code", obfuscatorName);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const identifiersLengthArr = this.deobfuscator.identifiers
|
|
106
|
-
.filter((value) => value.type !== "Property" && typeof value.name === "string")
|
|
107
|
-
.map((value) => value.name.length);
|
|
108
|
-
|
|
109
|
-
const [idsLengthAvg, stringScore] = [
|
|
110
|
-
sum(identifiersLengthArr),
|
|
111
|
-
sum(this.deobfuscator.literalScores)
|
|
112
|
-
];
|
|
113
|
-
if (!isMinified && identifiersLengthArr.length > 5 && idsLengthAvg <= 1.5) {
|
|
114
|
-
this.addWarning("short-identifiers", idsLengthAvg);
|
|
115
|
-
}
|
|
116
|
-
if (stringScore >= 3) {
|
|
117
|
-
this.addWarning("suspicious-literal", stringScore);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (this.encodedLiterals.size > kMaximumEncodedLiterals) {
|
|
121
|
-
this.addWarning("suspicious-file", null);
|
|
122
|
-
this.warnings = this.warnings
|
|
123
|
-
.filter((warning) => warning.kind !== "encoded-literal");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return { idsLengthAvg, stringScore, warnings: this.warnings };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
walk(node) {
|
|
130
|
-
this.tracer.walk(node);
|
|
131
|
-
this.deobfuscator.walk(node);
|
|
132
|
-
|
|
133
|
-
// Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause
|
|
134
|
-
if (node.type === "TryStatement" && node.handler) {
|
|
135
|
-
this.inTryStatement = true;
|
|
136
|
-
}
|
|
137
|
-
else if (node.type === "CatchClause") {
|
|
138
|
-
this.inTryStatement = false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return this.probesRunner.walk(node);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function sum(arr = []) {
|
|
146
|
-
return arr.length === 0 ? 0 : (arr.reduce((prev, curr) => prev + curr, 0) / arr.length);
|
|
147
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Import Third-party Dependencies
|
|
2
|
-
import { Utils } from "@nodesecure/sec-literal";
|
|
3
|
-
|
|
4
|
-
export function verify(identifiers, prefix) {
|
|
5
|
-
const pValue = Object.keys(prefix).pop();
|
|
6
|
-
const regexStr = `^${Utils.escapeRegExp(pValue)}[a-zA-Z]{1,2}[0-9]{0,2}$`;
|
|
7
|
-
|
|
8
|
-
return identifiers.every(({ name }) => new RegExp(regexStr).test(name));
|
|
9
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// CONSTANTS
|
|
2
|
-
const kJSFuckMinimumDoubleUnaryExpr = 5;
|
|
3
|
-
|
|
4
|
-
export function verify(counters) {
|
|
5
|
-
const hasZeroAssign = counters.AssignmentExpression === 0
|
|
6
|
-
&& counters.FunctionDeclaration === 0
|
|
7
|
-
&& counters.Property === 0
|
|
8
|
-
&& counters.VariableDeclarator === 0;
|
|
9
|
-
|
|
10
|
-
return hasZeroAssign && counters.DoubleUnaryExpression >= kJSFuckMinimumDoubleUnaryExpr;
|
|
11
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @description Search for ESM Export
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* export { bar } from "./foo.js";
|
|
6
|
-
* export * from "./bar.js";
|
|
7
|
-
*/
|
|
8
|
-
function validateNode(node) {
|
|
9
|
-
return [
|
|
10
|
-
/**
|
|
11
|
-
* We must be sure that the source property is a Literal to not fall in a trap
|
|
12
|
-
* export const foo = "bar";
|
|
13
|
-
*/
|
|
14
|
-
(node.type === "ExportNamedDeclaration" && node.source?.type === "Literal") ||
|
|
15
|
-
node.type === "ExportAllDeclaration"
|
|
16
|
-
];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function main(node, { sourceFile }) {
|
|
20
|
-
sourceFile.addDependency(
|
|
21
|
-
node.source.value,
|
|
22
|
-
node.loc
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default {
|
|
27
|
-
name: "isESMExport",
|
|
28
|
-
validateNode,
|
|
29
|
-
main,
|
|
30
|
-
breakOnMatch: true
|
|
31
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @description Search for ESM ImportDeclaration
|
|
3
|
-
* @see https://github.com/estree/estree/blob/master/es2015.md#importdeclaration
|
|
4
|
-
* @example
|
|
5
|
-
* import * as foo from "bar";
|
|
6
|
-
* import fs from "fs";
|
|
7
|
-
* import "make-promises-safe";
|
|
8
|
-
*/
|
|
9
|
-
function validateNode(node) {
|
|
10
|
-
return [
|
|
11
|
-
// Note: the source property is the right-side Literal part of the Import
|
|
12
|
-
["ImportDeclaration", "ImportExpression"].includes(node.type) && node.source.type === "Literal"
|
|
13
|
-
];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function main(node, options) {
|
|
17
|
-
const { sourceFile } = options;
|
|
18
|
-
|
|
19
|
-
// Searching for dangerous import "data:text/javascript;..." statement.
|
|
20
|
-
// see: https://2ality.com/2019/10/eval-via-import.html
|
|
21
|
-
if (node.source.value.startsWith("data:text/javascript")) {
|
|
22
|
-
sourceFile.addWarning("unsafe-import", node.source.value, node.loc);
|
|
23
|
-
}
|
|
24
|
-
sourceFile.addDependency(node.source.value, node.loc);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default {
|
|
28
|
-
name: "isImportDeclaration",
|
|
29
|
-
validateNode,
|
|
30
|
-
main,
|
|
31
|
-
breakOnMatch: true,
|
|
32
|
-
breakGroup: "import"
|
|
33
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Require Third-party Dependencies
|
|
2
|
-
import { isLiteralRegex } from "@nodesecure/estree-ast-utils";
|
|
3
|
-
import safeRegex from "safe-regex";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @description Search for RegExpLiteral AST Node
|
|
7
|
-
* @see https://github.com/estree/estree/blob/master/es5.md#regexpliteral
|
|
8
|
-
* @example
|
|
9
|
-
* /hello/
|
|
10
|
-
*/
|
|
11
|
-
function validateNode(node) {
|
|
12
|
-
return [
|
|
13
|
-
isLiteralRegex(node)
|
|
14
|
-
];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function main(node, options) {
|
|
18
|
-
const { sourceFile } = options;
|
|
19
|
-
|
|
20
|
-
// We use the safe-regex package to detect whether or not regex is safe!
|
|
21
|
-
if (!safeRegex(node.regex.pattern)) {
|
|
22
|
-
sourceFile.addWarning("unsafe-regex", node.regex.pattern, node.loc);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default {
|
|
27
|
-
name: "isLiteralRegex",
|
|
28
|
-
validateNode,
|
|
29
|
-
main,
|
|
30
|
-
breakOnMatch: false
|
|
31
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import { Hex } from "@nodesecure/sec-literal";
|
|
6
|
-
import { walk as doWalk } from "estree-walker";
|
|
7
|
-
import {
|
|
8
|
-
arrayExpressionToString,
|
|
9
|
-
getMemberExpressionIdentifier,
|
|
10
|
-
getCallExpressionArguments
|
|
11
|
-
} from "@nodesecure/estree-ast-utils";
|
|
12
|
-
|
|
13
|
-
export class RequireCallExpressionWalker {
|
|
14
|
-
constructor(tracer) {
|
|
15
|
-
this.tracer = tracer;
|
|
16
|
-
this.dependencies = new Set();
|
|
17
|
-
this.triggerWarning = true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
walk(nodeToWalk) {
|
|
21
|
-
this.dependencies = new Set();
|
|
22
|
-
this.triggerWarning = true;
|
|
23
|
-
|
|
24
|
-
// we need the `this` context of doWalk.enter
|
|
25
|
-
const self = this;
|
|
26
|
-
doWalk(nodeToWalk, {
|
|
27
|
-
enter(node) {
|
|
28
|
-
if (node.type !== "CallExpression" || node.arguments.length === 0) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const rootArgument = node.arguments.at(0);
|
|
33
|
-
if (rootArgument.type === "Literal" && Hex.isHex(rootArgument.value)) {
|
|
34
|
-
self.dependencies.add(Buffer.from(rootArgument.value, "hex").toString());
|
|
35
|
-
this.skip();
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const fullName = node.callee.type === "MemberExpression" ?
|
|
41
|
-
[...getMemberExpressionIdentifier(node.callee)].join(".") :
|
|
42
|
-
node.callee.name;
|
|
43
|
-
const tracedFullName = self.tracer.getDataFromIdentifier(fullName)?.identifierOrMemberExpr ?? fullName;
|
|
44
|
-
switch (tracedFullName) {
|
|
45
|
-
case "atob":
|
|
46
|
-
self.#handleAtob(node);
|
|
47
|
-
break;
|
|
48
|
-
case "Buffer.from":
|
|
49
|
-
self.#handleBufferFrom(node);
|
|
50
|
-
break;
|
|
51
|
-
case "require.resolve":
|
|
52
|
-
self.#handleRequireResolve(rootArgument);
|
|
53
|
-
break;
|
|
54
|
-
case "path.join":
|
|
55
|
-
self.#handlePathJoin(node);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return { dependencies: this.dependencies, triggerWarning: this.triggerWarning };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#handleAtob(node) {
|
|
65
|
-
const nodeArguments = getCallExpressionArguments(node, { tracer: this.tracer });
|
|
66
|
-
if (nodeArguments !== null) {
|
|
67
|
-
this.dependencies.add(Buffer.from(nodeArguments.at(0), "base64").toString());
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#handleBufferFrom(node) {
|
|
72
|
-
const [element] = node.arguments;
|
|
73
|
-
if (element.type === "ArrayExpression") {
|
|
74
|
-
const depName = [...arrayExpressionToString(element)].join("").trim();
|
|
75
|
-
this.dependencies.add(depName);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#handleRequireResolve(rootArgument) {
|
|
80
|
-
if (rootArgument.type === "Literal") {
|
|
81
|
-
this.dependencies.add(rootArgument.value);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
#handlePathJoin(node) {
|
|
86
|
-
if (!node.arguments.every((arg) => arg.type === "Literal" && typeof arg.value === "string")) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const constructedPath = path.posix.join(...node.arguments.map((arg) => arg.value));
|
|
90
|
-
this.dependencies.add(constructedPath);
|
|
91
|
-
this.triggerWarning = false;
|
|
92
|
-
}
|
|
93
|
-
}
|