@nodesecure/js-x-ray 5.1.0 → 6.0.1
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 +29 -20
- package/index.d.ts +3 -3
- package/index.js +38 -9
- package/package.json +12 -9
- package/src/ASTDeps.js +1 -1
- package/src/Analysis.js +32 -14
- package/src/obfuscators/index.js +5 -1
- package/src/obfuscators/jjencode.js +27 -27
- package/src/obfuscators/jsfuck.js +11 -11
- package/src/obfuscators/obfuscator-io.js +13 -13
- package/src/obfuscators/trojan-source.js +19 -2
- package/src/probes/index.js +70 -68
- package/src/probes/isArrayExpression.js +11 -8
- package/src/probes/isAssignmentExpression.js +29 -29
- package/src/probes/isClassDeclaration.js +25 -0
- package/src/probes/{isFunctionDeclaration.js → isFunction.js} +12 -1
- package/src/probes/isLiteral.js +49 -52
- package/src/probes/isLiteralRegex.js +1 -3
- package/src/probes/isMemberExpression.js +16 -31
- package/src/probes/isMethodDefinition.js +25 -0
- package/src/probes/isRegexObject.js +8 -3
- package/src/probes/isRequire.js +57 -64
- package/src/probes/isUnsafeCallee.js +3 -1
- package/src/probes/isVariableDeclaration.js +30 -104
- package/src/probes/isWeakCrypto.js +19 -15
- package/src/utils.js +25 -147
- package/src/warnings.js +5 -4
- package/types/api.d.ts +2 -2
- package/types/warnings.d.ts +1 -1
- package/src/constants.js +0 -35
- package/src/probes/isAssignmentExprOrMemberExpr.js +0 -23
package/src/probes/isRequire.js
CHANGED
|
@@ -1,54 +1,46 @@
|
|
|
1
1
|
/* eslint-disable consistent-return */
|
|
2
2
|
|
|
3
|
-
// Import Internal Dependencies
|
|
4
|
-
import { isRequireGlobalMemberExpr, getMemberExprName, arrExprToString, concatBinaryExpr } from "../utils.js";
|
|
5
|
-
|
|
6
3
|
// Import Third-party Dependencies
|
|
7
4
|
import { Hex } from "@nodesecure/sec-literal";
|
|
8
5
|
import { walk } from "estree-walker";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return false;
|
|
6
|
+
import {
|
|
7
|
+
concatBinaryExpression,
|
|
8
|
+
arrayExpressionToString,
|
|
9
|
+
getMemberExpressionIdentifier,
|
|
10
|
+
getCallExpressionIdentifier
|
|
11
|
+
} from "@nodesecure/estree-ast-utils";
|
|
12
|
+
|
|
13
|
+
function validateNode(node, { tracer }) {
|
|
14
|
+
const id = getCallExpressionIdentifier(node);
|
|
15
|
+
if (id === null) {
|
|
16
|
+
return [false];
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
}
|
|
19
|
+
const data = tracer.getDataFromIdentifier(id);
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return isRequireGlobalMemberExpr(getMemberExprName(node.callee));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isRequireIdentifiers(node, analysis) {
|
|
35
|
-
if (node.type !== "CallExpression") {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
|
|
39
|
-
|
|
40
|
-
return analysis.requireIdentifiers.has(fullName);
|
|
21
|
+
return [
|
|
22
|
+
data !== null && data.name === "require",
|
|
23
|
+
data?.identifierOrMemberExpr ?? void 0
|
|
24
|
+
];
|
|
41
25
|
}
|
|
42
26
|
|
|
43
27
|
function main(node, options) {
|
|
44
28
|
const { analysis } = options;
|
|
29
|
+
const { tracer } = analysis;
|
|
30
|
+
|
|
31
|
+
if (node.arguments.length === 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const arg = node.arguments.at(0);
|
|
45
35
|
|
|
46
|
-
const arg = node.arguments[0];
|
|
47
36
|
switch (arg.type) {
|
|
48
37
|
// const foo = "http"; require(foo);
|
|
49
38
|
case "Identifier":
|
|
50
|
-
if (analysis.
|
|
51
|
-
analysis.dependencies.add(
|
|
39
|
+
if (analysis.tracer.literalIdentifiers.has(arg.name)) {
|
|
40
|
+
analysis.dependencies.add(
|
|
41
|
+
analysis.tracer.literalIdentifiers.get(arg.name),
|
|
42
|
+
node.loc
|
|
43
|
+
);
|
|
52
44
|
}
|
|
53
45
|
else {
|
|
54
46
|
analysis.addWarning("unsafe-import", null, node.loc);
|
|
@@ -60,9 +52,12 @@ function main(node, options) {
|
|
|
60
52
|
analysis.dependencies.add(arg.value, node.loc);
|
|
61
53
|
break;
|
|
62
54
|
|
|
63
|
-
// require(["ht"
|
|
55
|
+
// require(["ht", "tp"])
|
|
64
56
|
case "ArrayExpression": {
|
|
65
|
-
const value =
|
|
57
|
+
const value = [...arrayExpressionToString(arg, { tracer })]
|
|
58
|
+
.join("")
|
|
59
|
+
.trim();
|
|
60
|
+
|
|
66
61
|
if (value === "") {
|
|
67
62
|
analysis.addWarning("unsafe-import", null, node.loc);
|
|
68
63
|
}
|
|
@@ -75,23 +70,27 @@ function main(node, options) {
|
|
|
75
70
|
// require("ht" + "tp");
|
|
76
71
|
case "BinaryExpression": {
|
|
77
72
|
if (arg.operator !== "+") {
|
|
73
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
78
74
|
break;
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
try {
|
|
78
|
+
const iter = concatBinaryExpression(arg, {
|
|
79
|
+
tracer, stopOnUnsupportedNode: true
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
analysis.dependencies.add([...iter].join(""), node.loc);
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
analysis.
|
|
84
|
+
catch {
|
|
85
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
87
86
|
}
|
|
88
87
|
break;
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
// require(Buffer.from("...", "hex").toString());
|
|
92
91
|
case "CallExpression": {
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
walkRequireCallExpression(arg)
|
|
93
|
+
.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
|
|
95
94
|
|
|
96
95
|
analysis.addWarning("unsafe-import", null, node.loc);
|
|
97
96
|
|
|
@@ -104,7 +103,7 @@ function main(node, options) {
|
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
function
|
|
106
|
+
function walkRequireCallExpression(nodeToWalk) {
|
|
108
107
|
const dependencies = new Set();
|
|
109
108
|
|
|
110
109
|
walk(nodeToWalk, {
|
|
@@ -113,34 +112,30 @@ function parseRequireCallExpression(nodeToWalk) {
|
|
|
113
112
|
return;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
const rootArgument = node.arguments.at(0);
|
|
116
|
+
if (rootArgument.type === "Literal" && Hex.isHex(rootArgument.value)) {
|
|
117
|
+
dependencies.add(Buffer.from(rootArgument.value, "hex").toString());
|
|
118
118
|
|
|
119
119
|
return this.skip();
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const fullName = node.callee.type === "MemberExpression" ?
|
|
122
|
+
const fullName = node.callee.type === "MemberExpression" ?
|
|
123
|
+
[...getMemberExpressionIdentifier(node.callee)].join(".") :
|
|
124
|
+
node.callee.name;
|
|
125
|
+
|
|
123
126
|
switch (fullName) {
|
|
124
127
|
case "Buffer.from": {
|
|
125
|
-
const [element
|
|
128
|
+
const [element] = node.arguments;
|
|
126
129
|
|
|
127
130
|
if (element.type === "ArrayExpression") {
|
|
128
|
-
const depName =
|
|
129
|
-
|
|
130
|
-
dependencies.add(depName);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else if (element.type === "Literal" && convert.type === "Literal" && convert.value === "hex") {
|
|
134
|
-
const value = Buffer.from(element.value, "hex").toString();
|
|
135
|
-
dependencies.add(value);
|
|
131
|
+
const depName = [...arrayExpressionToString(element)].join("").trim();
|
|
132
|
+
dependencies.add(depName);
|
|
136
133
|
}
|
|
137
134
|
break;
|
|
138
135
|
}
|
|
139
136
|
case "require.resolve": {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (element.type === "Literal") {
|
|
143
|
-
dependencies.add(element.value);
|
|
137
|
+
if (rootArgument.type === "Literal") {
|
|
138
|
+
dependencies.add(rootArgument.value);
|
|
144
139
|
}
|
|
145
140
|
break;
|
|
146
141
|
}
|
|
@@ -148,9 +143,7 @@ function parseRequireCallExpression(nodeToWalk) {
|
|
|
148
143
|
}
|
|
149
144
|
});
|
|
150
145
|
|
|
151
|
-
return
|
|
152
|
-
dependencies: [...dependencies]
|
|
153
|
-
};
|
|
146
|
+
return [...dependencies];
|
|
154
147
|
}
|
|
155
148
|
|
|
156
149
|
export default {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Import Internal Dependencies
|
|
2
2
|
import { isUnsafeCallee } from "../utils.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,6 +15,8 @@ function main(node, options) {
|
|
|
15
15
|
const { analysis, data: calleeName } = options;
|
|
16
16
|
|
|
17
17
|
analysis.addWarning("unsafe-stmt", calleeName, node.loc);
|
|
18
|
+
|
|
19
|
+
return Symbol.for("skipWalk");
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export default {
|
|
@@ -1,104 +1,30 @@
|
|
|
1
|
-
//
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (node.init.type === "Literal") {
|
|
32
|
-
analysis.identifiers.set(node.id.name, String(node.init.value));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Searching for someone who assign require to a variable, ex:
|
|
36
|
-
// const r = require
|
|
37
|
-
else if (node.init.type === "Identifier") {
|
|
38
|
-
if (kUnsafeCallee.has(node.init.name)) {
|
|
39
|
-
analysis.addWarning("unsafe-assign", node.init.name, node.loc);
|
|
40
|
-
}
|
|
41
|
-
else if (analysis.requireIdentifiers.has(node.init.name)) {
|
|
42
|
-
analysis.requireIdentifiers.add(node.id.name);
|
|
43
|
-
analysis.addWarning("unsafe-assign", node.init.name, node.loc);
|
|
44
|
-
}
|
|
45
|
-
else if (globalParts.has(node.init.name)) {
|
|
46
|
-
analysis.globalParts.set(node.id.name, node.init.name);
|
|
47
|
-
getRequirablePatterns(analysis.globalParts)
|
|
48
|
-
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Same as before but for pattern like process.mainModule and require.resolve
|
|
53
|
-
else if (node.init.type === "MemberExpression") {
|
|
54
|
-
const value = getMemberExprName(node.init);
|
|
55
|
-
const members = value.split(".");
|
|
56
|
-
|
|
57
|
-
if (analysis.globalParts.has(members[0]) || members.every((part) => globalParts.has(part))) {
|
|
58
|
-
analysis.globalParts.set(node.id.name, members.slice(1).join("."));
|
|
59
|
-
analysis.addWarning("unsafe-assign", value, node.loc);
|
|
60
|
-
}
|
|
61
|
-
getRequirablePatterns(analysis.globalParts)
|
|
62
|
-
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
63
|
-
|
|
64
|
-
if (isRequireStatement(value)) {
|
|
65
|
-
analysis.requireIdentifiers.add(node.id.name);
|
|
66
|
-
analysis.addWarning("unsafe-assign", value, node.loc);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else if (isUnsafeCallee(node.init)[0]) {
|
|
70
|
-
analysis.globalParts.set(node.id.name, "global");
|
|
71
|
-
globalParts.add(node.id.name);
|
|
72
|
-
analysis.requireIdentifiers.add(`${node.id.name}.${processMainModuleRequire}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isRequireStatement(value) {
|
|
78
|
-
return value.startsWith("require") ||
|
|
79
|
-
value.startsWith(processMainModuleRequire) ||
|
|
80
|
-
isRequireGlobalMemberExpr(value);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getRequirablePatterns(parts) {
|
|
84
|
-
const result = new Set();
|
|
85
|
-
|
|
86
|
-
for (const [id, path] of parts.entries()) {
|
|
87
|
-
if (path === "process") {
|
|
88
|
-
result.add(`${id}.mainModule.require`);
|
|
89
|
-
}
|
|
90
|
-
else if (path === "mainModule") {
|
|
91
|
-
result.add(`${id}.require`);
|
|
92
|
-
}
|
|
93
|
-
else if (path.includes("require")) {
|
|
94
|
-
result.add(id);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [...result];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default {
|
|
102
|
-
name: "isVariableDeclaration",
|
|
103
|
-
validateNode, main, breakOnMatch: false
|
|
104
|
-
};
|
|
1
|
+
// Import Third-party Dependencies
|
|
2
|
+
import {
|
|
3
|
+
getVariableDeclarationIdentifiers
|
|
4
|
+
} from "@nodesecure/estree-ast-utils";
|
|
5
|
+
|
|
6
|
+
// In case we are matching a Variable declaration, we have to save the identifier
|
|
7
|
+
// This allow the AST Analysis to retrieve required dependency when the stmt is mixed with variables.
|
|
8
|
+
function validateNode(node) {
|
|
9
|
+
return [
|
|
10
|
+
node.type === "VariableDeclaration"
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function main(mainNode, options) {
|
|
15
|
+
const { analysis } = options;
|
|
16
|
+
|
|
17
|
+
analysis.varkinds[mainNode.kind]++;
|
|
18
|
+
|
|
19
|
+
for (const node of mainNode.declarations) {
|
|
20
|
+
analysis.idtypes.variableDeclarator++;
|
|
21
|
+
for (const { name } of getVariableDeclarationIdentifiers(node.id)) {
|
|
22
|
+
analysis.identifiersName.push({ name, type: "variableDeclarator" });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
name: "isVariableDeclaration",
|
|
29
|
+
validateNode, main, breakOnMatch: false
|
|
30
|
+
};
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
+
// Import Third-party Dependencies
|
|
2
|
+
import { getCallExpressionIdentifier } from "@nodesecure/estree-ast-utils";
|
|
3
|
+
|
|
1
4
|
// CONSTANTS
|
|
2
|
-
const kWeakAlgorithms = new Set([
|
|
5
|
+
const kWeakAlgorithms = new Set([
|
|
6
|
+
"md5",
|
|
7
|
+
"sha1",
|
|
8
|
+
"ripemd160",
|
|
9
|
+
"md4",
|
|
10
|
+
"md2"
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function validateNode(node, { tracer }) {
|
|
14
|
+
const id = getCallExpressionIdentifier(node);
|
|
15
|
+
if (id === null || !tracer.importedModules.has("crypto")) {
|
|
16
|
+
return [false];
|
|
17
|
+
}
|
|
3
18
|
|
|
4
|
-
|
|
5
|
-
const isCallExpression = node.type === "CallExpression";
|
|
6
|
-
const isSimpleIdentifier = isCallExpression &&
|
|
7
|
-
node.callee.type === "Identifier" &&
|
|
8
|
-
node.callee.name === "createHash";
|
|
9
|
-
const isMemberExpression = isCallExpression &&
|
|
10
|
-
node.callee.type === "MemberExpression" &&
|
|
11
|
-
node.callee.property.name === "createHash";
|
|
19
|
+
const data = tracer.getDataFromIdentifier(id);
|
|
12
20
|
|
|
13
|
-
return [
|
|
21
|
+
return [data !== null && data.identifierOrMemberExpr === "crypto.createHash"];
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
function main(node, { analysis }) {
|
|
17
25
|
const arg = node.arguments.at(0);
|
|
18
|
-
const isCryptoImported = analysis.dependencies.has("crypto");
|
|
19
26
|
|
|
20
|
-
if (
|
|
21
|
-
kWeakAlgorithms.has(arg.value) &&
|
|
22
|
-
isCryptoImported
|
|
23
|
-
) {
|
|
27
|
+
if (kWeakAlgorithms.has(arg.value)) {
|
|
24
28
|
analysis.addWarning("weak-crypto", arg.value, node.loc);
|
|
25
29
|
}
|
|
26
30
|
}
|
package/src/utils.js
CHANGED
|
@@ -1,166 +1,44 @@
|
|
|
1
1
|
// Import Third-party Dependencies
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { globalIdentifiers, processMainModuleRequire } from "./constants.js";
|
|
6
|
-
|
|
7
|
-
// CONSTANTS
|
|
8
|
-
const kBinaryExprTypes = new Set(["Literal", "BinaryExpression", "Identifier"]);
|
|
9
|
-
const kExperimentalWarnings = new Set(["weak-crypto"]);
|
|
2
|
+
import {
|
|
3
|
+
getCallExpressionIdentifier
|
|
4
|
+
} from "@nodesecure/estree-ast-utils";
|
|
10
5
|
|
|
11
6
|
export function notNullOrUndefined(value) {
|
|
12
7
|
return value !== null && value !== void 0;
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
export function* getIdName(node) {
|
|
16
|
-
switch (node.type) {
|
|
17
|
-
case "Identifier":
|
|
18
|
-
yield node.name;
|
|
19
|
-
break;
|
|
20
|
-
case "RestElement":
|
|
21
|
-
yield node.argument.name;
|
|
22
|
-
break;
|
|
23
|
-
case "AssignmentPattern":
|
|
24
|
-
yield node.left.name;
|
|
25
|
-
break;
|
|
26
|
-
case "ArrayPattern":
|
|
27
|
-
yield* node.elements.filter(notNullOrUndefined).map((id) => [...getIdName(id)]).flat();
|
|
28
|
-
break;
|
|
29
|
-
case "ObjectPattern":
|
|
30
|
-
yield* node.properties.filter(notNullOrUndefined).map((property) => [...getIdName(property)]).flat();
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function isRequireGlobalMemberExpr(value) {
|
|
36
|
-
return [...globalIdentifiers]
|
|
37
|
-
.some((name) => value.startsWith(`${name}.${processMainModuleRequire}`));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
10
|
export function isUnsafeCallee(node) {
|
|
41
|
-
|
|
42
|
-
return [false, null];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (node.callee.type === "Identifier") {
|
|
46
|
-
return [node.callee.name === "eval", "eval"];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (node.callee.type !== "CallExpression") {
|
|
50
|
-
return [false, null];
|
|
51
|
-
}
|
|
52
|
-
const callee = node.callee.callee;
|
|
53
|
-
|
|
54
|
-
return [callee.type === "Identifier" && callee.name === "Function", "Function"];
|
|
55
|
-
}
|
|
11
|
+
const identifier = getCallExpressionIdentifier(node);
|
|
56
12
|
|
|
57
|
-
|
|
58
|
-
|
|
13
|
+
// For Function we are looking for this: `Function("...")();`
|
|
14
|
+
// A double CallExpression
|
|
15
|
+
return [
|
|
16
|
+
identifier === "eval" || (identifier === "Function" && node.callee.type === "CallExpression"),
|
|
17
|
+
identifier
|
|
18
|
+
];
|
|
59
19
|
}
|
|
60
20
|
|
|
61
|
-
export function
|
|
62
|
-
|
|
63
|
-
const isArrayExpr = typeof elements === "object" && Reflect.has(elements, "elements");
|
|
64
|
-
const localElements = isArrayExpr ? elements.elements : elements;
|
|
65
|
-
|
|
66
|
-
for (const row of localElements) {
|
|
67
|
-
if (row.type === "Literal") {
|
|
68
|
-
if (row.value === "") {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const value = Number(row.value);
|
|
73
|
-
ret += Number.isNaN(value) ? row.value : String.fromCharCode(value);
|
|
74
|
-
}
|
|
75
|
-
else if (row.type === "Identifier" && identifiers !== null && identifiers.has(row.name)) {
|
|
76
|
-
ret += identifiers.get(row.name);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return ret;
|
|
21
|
+
export function rootLocation() {
|
|
22
|
+
return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
|
|
81
23
|
}
|
|
82
24
|
|
|
83
|
-
export function
|
|
84
|
-
const {
|
|
85
|
-
if (!kBinaryExprTypes.has(left.type) || !kBinaryExprTypes.has(right.type)) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
let str = "";
|
|
89
|
-
|
|
90
|
-
for (const childNode of [left, right]) {
|
|
91
|
-
switch (childNode.type) {
|
|
92
|
-
case "BinaryExpression": {
|
|
93
|
-
const value = concatBinaryExpr(childNode, identifiers);
|
|
94
|
-
if (value !== null) {
|
|
95
|
-
str += value;
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
case "ArrayExpression": {
|
|
100
|
-
str += arrExprToString(childNode.elements, identifiers);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
case "Literal":
|
|
104
|
-
str += childNode.value;
|
|
105
|
-
break;
|
|
106
|
-
case "Identifier":
|
|
107
|
-
if (identifiers.has(childNode.name)) {
|
|
108
|
-
str += identifiers.get(childNode.name);
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
25
|
+
export function toArrayLocation(location = rootLocation()) {
|
|
26
|
+
const { start, end = start } = location;
|
|
113
27
|
|
|
114
|
-
return
|
|
28
|
+
return [
|
|
29
|
+
[start.line || 0, start.column || 0],
|
|
30
|
+
[end.line || 0, end.column || 0]
|
|
31
|
+
];
|
|
115
32
|
}
|
|
116
33
|
|
|
117
|
-
export function
|
|
118
|
-
|
|
34
|
+
export function extractNode(expectedType) {
|
|
35
|
+
return (callback, nodes) => {
|
|
36
|
+
const finalNodes = Array.isArray(nodes) ? nodes : [nodes];
|
|
119
37
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
break;
|
|
124
|
-
case "Identifier":
|
|
125
|
-
name += node.object.name;
|
|
126
|
-
break;
|
|
127
|
-
case "Literal":
|
|
128
|
-
name += node.object.value;
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
switch (node.property.type) {
|
|
133
|
-
case "Identifier":
|
|
134
|
-
name += `.${node.property.name}`;
|
|
135
|
-
break;
|
|
136
|
-
case "Literal":
|
|
137
|
-
name += `.${node.property.value}`;
|
|
138
|
-
break;
|
|
139
|
-
case "CallExpression": {
|
|
140
|
-
const args = node.property.arguments;
|
|
141
|
-
if (args.length > 0 && args[0].type === "Literal" && Hex.isHex(args[0].value)) {
|
|
142
|
-
name += `.${Buffer.from(args[0].value, "hex").toString()}`;
|
|
143
|
-
}
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
case "BinaryExpression": {
|
|
147
|
-
const value = concatBinaryExpr(node.property);
|
|
148
|
-
if (value !== null && value.trim() !== "") {
|
|
149
|
-
name += `.${value}`;
|
|
38
|
+
for (const node of finalNodes) {
|
|
39
|
+
if (notNullOrUndefined(node) && node.type === expectedType) {
|
|
40
|
+
callback(node);
|
|
150
41
|
}
|
|
151
|
-
break;
|
|
152
42
|
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return name;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function rootLocation() {
|
|
159
|
-
return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function toArrayLocation(location = rootLocation()) {
|
|
163
|
-
const { start, end = start } = location;
|
|
164
|
-
|
|
165
|
-
return [[start.line || 0, start.column || 0], [end.line || 0, end.column || 0]];
|
|
43
|
+
};
|
|
166
44
|
}
|
package/src/warnings.js
CHANGED
|
@@ -19,10 +19,6 @@ export const warnings = Object.freeze({
|
|
|
19
19
|
i18n: "sast_warnings.unsafe_stmt",
|
|
20
20
|
severity: "Warning"
|
|
21
21
|
},
|
|
22
|
-
"unsafe-assign": {
|
|
23
|
-
i18n: "sast_warnings.unsafe_assign",
|
|
24
|
-
severity: "Warning"
|
|
25
|
-
},
|
|
26
22
|
"encoded-literal": {
|
|
27
23
|
i18n: "sast_warnings.encoded_literal",
|
|
28
24
|
severity: "Information"
|
|
@@ -35,6 +31,11 @@ export const warnings = Object.freeze({
|
|
|
35
31
|
i18n: "sast_warnings.suspicious_literal",
|
|
36
32
|
severity: "Warning"
|
|
37
33
|
},
|
|
34
|
+
"suspicious-file": {
|
|
35
|
+
i18n: "sast_warnings.suspicious_file",
|
|
36
|
+
severity: "Critical",
|
|
37
|
+
experimental: true
|
|
38
|
+
},
|
|
38
39
|
"obfuscated-code": {
|
|
39
40
|
i18n: "sast_warnings.obfuscated_code",
|
|
40
41
|
severity: "Critical",
|
package/types/api.d.ts
CHANGED
package/types/warnings.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ type WarningNameWithValue = "parsing-error"
|
|
|
11
11
|
| "encoded-literal"
|
|
12
12
|
| "unsafe-regex"
|
|
13
13
|
| "unsafe-stmt"
|
|
14
|
-
| "unsafe-assign"
|
|
15
14
|
| "short-identifiers"
|
|
16
15
|
| "suspicious-literal"
|
|
16
|
+
| "suspicious-file"
|
|
17
17
|
| "obfuscated-code"
|
|
18
18
|
| "weak-crypto";
|
|
19
19
|
type WarningName = WarningNameWithValue | "unsafe-import";
|
package/src/constants.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is one of the way to get a valid require.
|
|
3
|
-
*
|
|
4
|
-
* @see https://nodejs.org/api/process.html#process_process_mainmodule
|
|
5
|
-
*/
|
|
6
|
-
export const processMainModuleRequire = "process.mainModule.require";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* JavaScript dangerous global identifiers that can be used by hackers
|
|
10
|
-
*/
|
|
11
|
-
export const globalIdentifiers = new Set(["global", "globalThis", "root", "GLOBAL", "window"]);
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Dangerous Global identifiers parts
|
|
15
|
-
*/
|
|
16
|
-
export const globalParts = new Set([...globalIdentifiers, "process", "mainModule", "require"]);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Dangerous Unicode control characters that can be used by hackers
|
|
20
|
-
* to perform trojan source.
|
|
21
|
-
*/
|
|
22
|
-
export const unsafeUnicodeControlCharacters = [
|
|
23
|
-
"\u202A",
|
|
24
|
-
"\u202B",
|
|
25
|
-
"\u202D",
|
|
26
|
-
"\u202E",
|
|
27
|
-
"\u202C",
|
|
28
|
-
"\u2066",
|
|
29
|
-
"\u2067",
|
|
30
|
-
"\u2068",
|
|
31
|
-
"\u2069",
|
|
32
|
-
"\u200E",
|
|
33
|
-
"\u200F",
|
|
34
|
-
"\u061C"
|
|
35
|
-
];
|