Skip to content

Commit

Permalink
refactor: remove ASTDeps class and rename Anaysis to SourceFile (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken authored Dec 18, 2023
1 parent 9f8ebd6 commit f183c3e
Show file tree
Hide file tree
Showing 18 changed files with 136 additions and 281 deletions.
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,19 @@ require(Buffer.from("6673", "hex").toString());
Then use `js-x-ray` to run an analysis of the JavaScript code:
```js
import { runASTAnalysis } from "@nodesecure/js-x-ray";
import { readFileSync } from "fs";
import { readFileSync } from "node:fs";

const str = readFileSync("./file.js", "utf-8");
const { warnings, dependencies } = runASTAnalysis(str);
const { warnings, dependencies } = runASTAnalysis(
readFileSync("./file.js", "utf-8")
);

const dependenciesName = [...dependencies];
const inTryDeps = [...dependencies.getDependenciesInTryStatement()];

console.log(dependenciesName);
console.log(inTryDeps);
console.log(dependencies);
console.dir(warnings, { depth: null });
```

The analysis will return: `http` (in try), `crypto`, `util` and `fs`.

> [!NOTE]
> [!TIP]
> There is also a lot of suspicious code example in the `./examples` cases directory. Feel free to try the tool on these files.
## Warnings
Expand Down
7 changes: 4 additions & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
Report,
ReportOnFile,
RuntimeFileOptions,
RuntimeOptions
RuntimeOptions,
SourceLocation,
Dependency
} from "./types/api.js";
import {
Warning,
Expand All @@ -13,7 +15,6 @@ import {
WarningName,
WarningNameWithValue
} from "./types/warnings.js";
import { ASTDeps, Dependency } from "./types/astdeps.js";

declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental" | "i18n" | "severity">>;

Expand All @@ -25,7 +26,7 @@ export {
ReportOnFile,
RuntimeFileOptions,
RuntimeOptions,
ASTDeps,
SourceLocation,
Dependency,
Warning,
WarningDefault,
Expand Down
27 changes: 15 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as meriyah from "meriyah";
import isMinified from "is-minified-code";

// Import Internal Dependencies
import Analysis from "./src/Analysis.js";
import { SourceFile } from "./src/SourceFile.js";
import { warnings } from "./src/warnings.js";
import * as utils from "./src/utils.js";

Expand All @@ -20,7 +20,10 @@ const kMeriyahDefaultOptions = {
jsx: true
};

export function runASTAnalysis(str, options = Object.create(null)) {
export function runASTAnalysis(
str,
options = Object.create(null)
) {
const {
module = true,
isMinified = false,
Expand All @@ -35,8 +38,7 @@ export function runASTAnalysis(str, options = Object.create(null)) {
removeHTMLComments
});

const sastAnalysis = new Analysis();
sastAnalysis.analyzeSourceString(str);
const source = new SourceFile(str);

// we walk each AST Nodes, this is a purely synchronous I/O
walk(body, {
Expand All @@ -46,23 +48,24 @@ export function runASTAnalysis(str, options = Object.create(null)) {
return;
}

const action = sastAnalysis.walk(node);
const action = source.walk(node);
if (action === "skip") {
this.skip();
}
}
});

const dependencies = sastAnalysis.dependencies;
const { idsLengthAvg, stringScore, warnings } = sastAnalysis.getResult(isMinified);
const isOneLineRequire = body.length <= 1 && dependencies.size <= 1;

return {
dependencies, warnings, idsLengthAvg, stringScore, isOneLineRequire
...source.getResult(isMinified),
dependencies: source.dependencies,
isOneLineRequire: body.length <= 1 && source.dependencies.size <= 1
};
}

export async function runASTAnalysisOnFile(pathToFile, options = {}) {
export async function runASTAnalysisOnFile(
pathToFile,
options = {}
) {
try {
const {
packageName = null,
Expand All @@ -80,7 +83,7 @@ export async function runASTAnalysisOnFile(pathToFile, options = {}) {
removeHTMLComments
});
if (packageName !== null) {
data.dependencies.removeByName(packageName);
data.dependencies.delete(packageName);
}

return {
Expand Down
63 changes: 0 additions & 63 deletions src/ASTDeps.js

This file was deleted.

36 changes: 24 additions & 12 deletions src/Analysis.js → src/SourceFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { VariableTracer } from "@nodesecure/estree-ast-utils";
// Import Internal Dependencies
import { rootLocation, toArrayLocation } from "./utils.js";
import { generateWarning } from "./warnings.js";
import ASTDeps from "./ASTDeps.js";
import { isObfuscatedCode, hasTrojanSource } from "./obfuscators/index.js";
import { runOnProbes } from "./probes/index.js";

Expand All @@ -18,7 +17,8 @@ const kDictionaryStrParts = [

const kMaximumEncodedLiterals = 10;

export default class Analysis {
export class SourceFile {
inTryStatement = false;
hasDictionaryString = false;
hasPrefixedIdentifiers = false;
varkinds = { var: 0, let: 0, const: 0 };
Expand All @@ -34,17 +34,35 @@ export default class Analysis {
};
identifiersName = [];

constructor() {
constructor(sourceCodeString) {
this.tracer = new VariableTracer()
.enableDefaultTracing()
.trace("crypto.createHash", {
followConsecutiveAssignment: true, moduleName: "crypto"
});

this.dependencies = new ASTDeps();
this.dependencies = new Map();
this.encodedLiterals = new Map();
this.warnings = [];
this.literalScores = [];

if (hasTrojanSource(sourceCodeString)) {
this.addWarning("obfuscated-code", "trojan-source");
}
}

addDependency(name, location = null, unsafe = false) {
if (typeof name !== "string" || name.trim() === "") {
return;
}

const dependencyName = name.charAt(name.length - 1) === "/" ?
name.slice(0, -1) : name;
this.dependencies.set(dependencyName, {
unsafe,
inTry: this.inTryStatement,
...(location === null ? {} : { location })
});
}

addWarning(name, value, location = rootLocation()) {
Expand All @@ -68,12 +86,6 @@ export default class Analysis {
}
}

analyzeSourceString(sourceString) {
if (hasTrojanSource(sourceString)) {
this.addWarning("obfuscated-code", "trojan-source");
}
}

analyzeString(str) {
const score = Utils.stringSuspicionScore(str);
if (score !== 0) {
Expand Down Expand Up @@ -143,10 +155,10 @@ export default class Analysis {

// Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause
if (node.type === "TryStatement" && typeof node.handler !== "undefined") {
this.dependencies.isInTryStmt = true;
this.inTryStatement = true;
}
else if (node.type === "CatchClause") {
this.dependencies.isInTryStmt = false;
this.inTryStatement = false;
}

return runOnProbes(node, this);
Expand Down
2 changes: 1 addition & 1 deletion src/probes/isImportDeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function main(node, options) {
if (node.source.value.startsWith("data:text/javascript")) {
analysis.addWarning("unsafe-import", node.source.value, node.loc);
}
analysis.dependencies.add(node.source.value, node.loc);
analysis.addDependency(node.source.value, node.loc);
}

export default {
Expand Down
2 changes: 1 addition & 1 deletion src/probes/isLiteral.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function main(node, options) {
// If the value we are retrieving is the name of a Node.js dependency,
// then we add it to the dependencies list and we throw an unsafe-import at the current location.
if (kNodeDeps.has(value)) {
analysis.dependencies.add(value, node.loc);
analysis.addDependency(value, node.loc);
analysis.addWarning("unsafe-import", null, node.loc);
}
else if (value === "require" || !Hex.isSafe(node.value)) {
Expand Down
10 changes: 5 additions & 5 deletions src/probes/isRequire.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function main(node, options) {
// const foo = "http"; require(foo);
case "Identifier":
if (analysis.tracer.literalIdentifiers.has(arg.name)) {
analysis.dependencies.add(
analysis.addDependency(
analysis.tracer.literalIdentifiers.get(arg.name),
node.loc
);
Expand All @@ -50,7 +50,7 @@ function main(node, options) {

// require("http")
case "Literal":
analysis.dependencies.add(arg.value, node.loc);
analysis.addDependency(arg.value, node.loc);
break;

// require(["ht", "tp"])
Expand All @@ -63,7 +63,7 @@ function main(node, options) {
analysis.addWarning("unsafe-import", null, node.loc);
}
else {
analysis.dependencies.add(value, node.loc);
analysis.addDependency(value, node.loc);
}
break;
}
Expand All @@ -80,7 +80,7 @@ function main(node, options) {
tracer, stopOnUnsupportedNode: true
});

analysis.dependencies.add([...iter].join(""), node.loc);
analysis.addDependency([...iter].join(""), node.loc);
}
catch {
analysis.addWarning("unsafe-import", null, node.loc);
Expand All @@ -91,7 +91,7 @@ function main(node, options) {
// require(Buffer.from("...", "hex").toString());
case "CallExpression": {
walkRequireCallExpression(arg, tracer)
.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
.forEach((depName) => analysis.addDependency(depName, node.loc, true));

analysis.addWarning("unsafe-import", null, node.loc);

Expand Down
Loading

0 comments on commit f183c3e

Please sign in to comment.