@cyclonedx/cdxgen 12.2.0 → 12.3.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/README.md +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createOutputPlan,
|
|
5
|
+
deriveCycloneDxOutputPath,
|
|
6
|
+
deriveSpdxOutputPath,
|
|
7
|
+
normalizeOutputFormats,
|
|
8
|
+
} from "./exportUtils.js";
|
|
9
|
+
|
|
10
|
+
describe("exportUtils", () => {
|
|
11
|
+
it("normalizes comma-separated export formats", () => {
|
|
12
|
+
assert.deepStrictEqual(normalizeOutputFormats("cyclonedx,spdx-json"), [
|
|
13
|
+
"cyclonedx",
|
|
14
|
+
"spdx",
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("normalizes repeated format flags", () => {
|
|
19
|
+
assert.deepStrictEqual(normalizeOutputFormats(["cyclonedx", "spdx"]), [
|
|
20
|
+
"cyclonedx",
|
|
21
|
+
"spdx",
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("derives SPDX and CycloneDX sibling paths", () => {
|
|
26
|
+
assert.strictEqual(
|
|
27
|
+
deriveSpdxOutputPath("/tmp/bom.cdx.json"),
|
|
28
|
+
"/tmp/bom.spdx.json",
|
|
29
|
+
);
|
|
30
|
+
assert.strictEqual(
|
|
31
|
+
deriveCycloneDxOutputPath("/tmp/bom.spdx.json"),
|
|
32
|
+
"/tmp/bom.cdx.json",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("chooses SPDX automatically for .spdx.json outputs", () => {
|
|
37
|
+
const plan = createOutputPlan({ output: "/tmp/app.spdx.json" });
|
|
38
|
+
assert.strictEqual(plan.formats.has("spdx"), true);
|
|
39
|
+
assert.strictEqual(plan.formats.has("cyclonedx"), false);
|
|
40
|
+
assert.strictEqual(plan.outputs.spdx, "/tmp/app.spdx.json");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("creates sibling outputs for dual exports", () => {
|
|
44
|
+
const plan = createOutputPlan({
|
|
45
|
+
format: "cyclonedx,spdx",
|
|
46
|
+
output: "/tmp/app.cdx.json",
|
|
47
|
+
});
|
|
48
|
+
assert.strictEqual(plan.outputs.cyclonedx, "/tmp/app.cdx.json");
|
|
49
|
+
assert.strictEqual(plan.outputs.spdx, "/tmp/app.spdx.json");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("creates sibling outputs for repeated format flags", () => {
|
|
53
|
+
const plan = createOutputPlan({
|
|
54
|
+
format: ["cyclonedx", "spdx"],
|
|
55
|
+
output: "/tmp/app.cdx.json",
|
|
56
|
+
});
|
|
57
|
+
assert.strictEqual(plan.outputs.cyclonedx, "/tmp/app.cdx.json");
|
|
58
|
+
assert.strictEqual(plan.outputs.spdx, "/tmp/app.spdx.json");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
1
3
|
import process from "node:process";
|
|
2
4
|
|
|
3
5
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -16,8 +18,67 @@ import {
|
|
|
16
18
|
gitTreeHashes,
|
|
17
19
|
listFiles,
|
|
18
20
|
} from "./envcontext.js";
|
|
21
|
+
import { scanTextForHiddenUnicode } from "./unicodeScan.js";
|
|
19
22
|
import { getAllFiles } from "./utils.js";
|
|
20
23
|
|
|
24
|
+
const README_PATTERNS = [
|
|
25
|
+
"**/README*.{adoc,asciidoc,markdown,md,mdx,rst,txt}",
|
|
26
|
+
"**/readme*.{adoc,asciidoc,markdown,md,mdx,rst,txt}",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function buildReadmeSecurityComponents(discoveryPath, options) {
|
|
30
|
+
const matchedFiles = [];
|
|
31
|
+
for (const pattern of README_PATTERNS) {
|
|
32
|
+
const found = getAllFiles(discoveryPath, pattern, options);
|
|
33
|
+
if (found?.length) {
|
|
34
|
+
matchedFiles.push(...found);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const components = [];
|
|
38
|
+
for (const filePath of [...new Set(matchedFiles)]) {
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = readFileSync(filePath, { encoding: "utf-8" });
|
|
42
|
+
} catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const scan = scanTextForHiddenUnicode(raw, { syntax: "markdown" });
|
|
46
|
+
if (!scan.hasHiddenUnicode) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const properties = [
|
|
50
|
+
{ name: "SrcFile", value: filePath },
|
|
51
|
+
{ name: "cdx:file:kind", value: "readme" },
|
|
52
|
+
{ name: "cdx:file:hasHiddenUnicode", value: "true" },
|
|
53
|
+
{
|
|
54
|
+
name: "cdx:file:hiddenUnicodeCodePoints",
|
|
55
|
+
value: scan.codePoints.join(","),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "cdx:file:hiddenUnicodeLineNumbers",
|
|
59
|
+
value: scan.lineNumbers.join(","),
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
if (scan.inComments) {
|
|
63
|
+
properties.push({
|
|
64
|
+
name: "cdx:file:hiddenUnicodeInComments",
|
|
65
|
+
value: "true",
|
|
66
|
+
});
|
|
67
|
+
properties.push({
|
|
68
|
+
name: "cdx:file:hiddenUnicodeCommentCodePoints",
|
|
69
|
+
value: scan.commentCodePoints.join(","),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
components.push({
|
|
73
|
+
"bom-ref": `file:${filePath}`,
|
|
74
|
+
name: basename(filePath),
|
|
75
|
+
properties,
|
|
76
|
+
type: "file",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return components;
|
|
80
|
+
}
|
|
81
|
+
|
|
21
82
|
/**
|
|
22
83
|
* The parser registry. Pre-populated with the five built-in CI system parsers.
|
|
23
84
|
*
|
|
@@ -291,6 +352,14 @@ export function addFormulationSection(filePath, options, context = {}) {
|
|
|
291
352
|
components = components.concat(ciComponents);
|
|
292
353
|
}
|
|
293
354
|
|
|
355
|
+
const readmeSecurityComponents = buildReadmeSecurityComponents(
|
|
356
|
+
discoveryPath,
|
|
357
|
+
options,
|
|
358
|
+
);
|
|
359
|
+
if (readmeSecurityComponents.length) {
|
|
360
|
+
components = components.concat(readmeSecurityComponents);
|
|
361
|
+
}
|
|
362
|
+
|
|
294
363
|
// ── Environment variables ─────────────────────────────────────────────────
|
|
295
364
|
let environmentVars = gitBranch?.length
|
|
296
365
|
? [{ name: "GIT_BRANCH", value: gitBranch }]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { assert, describe, it } from "poku";
|
|
6
|
+
|
|
7
|
+
import { addFormulationSection } from "./formulationParsers.js";
|
|
8
|
+
|
|
9
|
+
function getProp(obj, name) {
|
|
10
|
+
return obj?.properties?.find((property) => property.name === name)?.value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("addFormulationSection()", () => {
|
|
14
|
+
it("adds README file components when hidden Unicode is detected", () => {
|
|
15
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "cdxgen-formulation-"));
|
|
16
|
+
writeFileSync(
|
|
17
|
+
path.join(tmpDir, "README.md"),
|
|
18
|
+
"# Demo\n<!-- hidden \u200B comment -->\nContent",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = addFormulationSection(tmpDir, { specVersion: 1.7 });
|
|
23
|
+
const formulation = result.formulation[0];
|
|
24
|
+
const readmeComponent = formulation.components.find(
|
|
25
|
+
(component) => getProp(component, "cdx:file:kind") === "readme",
|
|
26
|
+
);
|
|
27
|
+
assert.ok(readmeComponent, "expected README formulation component");
|
|
28
|
+
assert.strictEqual(
|
|
29
|
+
getProp(readmeComponent, "cdx:file:hasHiddenUnicode"),
|
|
30
|
+
"true",
|
|
31
|
+
);
|
|
32
|
+
assert.strictEqual(
|
|
33
|
+
getProp(readmeComponent, "cdx:file:hiddenUnicodeInComments"),
|
|
34
|
+
"true",
|
|
35
|
+
);
|
|
36
|
+
assert.match(
|
|
37
|
+
getProp(readmeComponent, "cdx:file:hiddenUnicodeCodePoints"),
|
|
38
|
+
/U\+200B/,
|
|
39
|
+
);
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(tmpDir, { force: true, recursive: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { dirNameStr, safeExistsSync } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
const GTFOBINS_INDEX_FILE = join(dirNameStr, "data", "gtfobins-index.json");
|
|
7
|
+
const GTFOBINS_REFERENCE_PREFIX = "https://gtfobins.github.io/gtfobins/";
|
|
8
|
+
const PRIVILEGED_CONTEXTS = ["sudo", "suid", "capabilities"];
|
|
9
|
+
const CONTAINER_ESCAPE_HELPERS = new Set([
|
|
10
|
+
"chroot",
|
|
11
|
+
"ctr",
|
|
12
|
+
"docker",
|
|
13
|
+
"kubectl",
|
|
14
|
+
"mount",
|
|
15
|
+
"nsenter",
|
|
16
|
+
"tar",
|
|
17
|
+
"unshare",
|
|
18
|
+
]);
|
|
19
|
+
const DIRECT_ALIASES = new Map([["nodejs", "node"]]);
|
|
20
|
+
const VERSIONED_ALIASES = [
|
|
21
|
+
{ pattern: /^python(?:\d+(?:\.\d+)*)?$/i, target: "python" },
|
|
22
|
+
{ pattern: /^perl(?:\d+(?:\.\d+)*)?$/i, target: "perl" },
|
|
23
|
+
{ pattern: /^ruby(?:\d+(?:\.\d+)*)?$/i, target: "ruby" },
|
|
24
|
+
{ pattern: /^php(?:\d+(?:\.\d+)*)?$/i, target: "php" },
|
|
25
|
+
{ pattern: /^lua(?:\d+(?:\.\d+)*)?$/i, target: "lua" },
|
|
26
|
+
{ pattern: /^node(?:\d+(?:\.\d+)*)?$/i, target: "node" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const GTFOBINS_INDEX = loadGtfoBinsIndex();
|
|
30
|
+
|
|
31
|
+
function loadGtfoBinsIndex() {
|
|
32
|
+
if (!safeExistsSync(GTFOBINS_INDEX_FILE)) {
|
|
33
|
+
return { entries: {}, source: GTFOBINS_REFERENCE_PREFIX, sourceRef: "" };
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(GTFOBINS_INDEX_FILE, "utf8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return { entries: {}, source: GTFOBINS_REFERENCE_PREFIX, sourceRef: "" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveCandidateName(candidate) {
|
|
43
|
+
if (!candidate || typeof candidate !== "string") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const trimmed = basename(candidate.trim());
|
|
47
|
+
if (!trimmed) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const normalized = trimmed.toLowerCase();
|
|
51
|
+
if (GTFOBINS_INDEX.entries?.[trimmed]) {
|
|
52
|
+
return { canonicalName: trimmed, matchSource: "basename" };
|
|
53
|
+
}
|
|
54
|
+
if (GTFOBINS_INDEX.entries?.[normalized]) {
|
|
55
|
+
return { canonicalName: normalized, matchSource: "basename" };
|
|
56
|
+
}
|
|
57
|
+
const directAlias = DIRECT_ALIASES.get(normalized);
|
|
58
|
+
if (directAlias && GTFOBINS_INDEX.entries?.[directAlias]) {
|
|
59
|
+
return { canonicalName: directAlias, matchSource: "alias" };
|
|
60
|
+
}
|
|
61
|
+
for (const aliasRule of VERSIONED_ALIASES) {
|
|
62
|
+
if (
|
|
63
|
+
aliasRule.pattern.test(normalized) &&
|
|
64
|
+
GTFOBINS_INDEX.entries?.[aliasRule.target]
|
|
65
|
+
) {
|
|
66
|
+
return { canonicalName: aliasRule.target, matchSource: "alias" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function deriveRiskTags(entry, canonicalName) {
|
|
73
|
+
const functions = new Set(entry?.functions || []);
|
|
74
|
+
const contexts = new Set(entry?.contexts || []);
|
|
75
|
+
const riskTags = new Set();
|
|
76
|
+
const hasExecPrimitive =
|
|
77
|
+
functions.has("shell") ||
|
|
78
|
+
functions.has("command") ||
|
|
79
|
+
functions.has("reverse-shell") ||
|
|
80
|
+
functions.has("bind-shell");
|
|
81
|
+
const hasNetworkPrimitive =
|
|
82
|
+
functions.has("upload") ||
|
|
83
|
+
functions.has("download") ||
|
|
84
|
+
functions.has("reverse-shell") ||
|
|
85
|
+
functions.has("bind-shell");
|
|
86
|
+
if (functions.has("privilege-escalation")) {
|
|
87
|
+
riskTags.add("privilege-escalation");
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
contexts.has("sudo") ||
|
|
91
|
+
contexts.has("suid") ||
|
|
92
|
+
contexts.has("capabilities")
|
|
93
|
+
) {
|
|
94
|
+
riskTags.add("privilege-escalation");
|
|
95
|
+
}
|
|
96
|
+
if (hasExecPrimitive && hasNetworkPrimitive) {
|
|
97
|
+
riskTags.add("lateral-movement");
|
|
98
|
+
}
|
|
99
|
+
if (functions.has("upload") || functions.has("file-read")) {
|
|
100
|
+
riskTags.add("data-exfiltration");
|
|
101
|
+
}
|
|
102
|
+
if (functions.has("file-write") || functions.has("library-load")) {
|
|
103
|
+
riskTags.add("persistence");
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
CONTAINER_ESCAPE_HELPERS.has(canonicalName) &&
|
|
107
|
+
(hasExecPrimitive ||
|
|
108
|
+
functions.has("privilege-escalation") ||
|
|
109
|
+
functions.has("library-load"))
|
|
110
|
+
) {
|
|
111
|
+
riskTags.add("container-escape");
|
|
112
|
+
}
|
|
113
|
+
return Array.from(riskTags).sort();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getGtfoBinsMetadata(name, linkedName) {
|
|
117
|
+
const directMatch = resolveCandidateName(name);
|
|
118
|
+
if (directMatch) {
|
|
119
|
+
const entry = GTFOBINS_INDEX.entries[directMatch.canonicalName];
|
|
120
|
+
return {
|
|
121
|
+
canonicalName: directMatch.canonicalName,
|
|
122
|
+
contexts: entry.contexts,
|
|
123
|
+
functions: entry.functions,
|
|
124
|
+
matchSource: directMatch.matchSource,
|
|
125
|
+
mitreTechniques: entry.mitreTechniques,
|
|
126
|
+
privilegedContexts: entry.contexts.filter((context) =>
|
|
127
|
+
PRIVILEGED_CONTEXTS.includes(context),
|
|
128
|
+
),
|
|
129
|
+
reference: `${GTFOBINS_REFERENCE_PREFIX}${encodeURIComponent(directMatch.canonicalName)}/`,
|
|
130
|
+
riskTags: deriveRiskTags(entry, directMatch.canonicalName),
|
|
131
|
+
source: GTFOBINS_INDEX.source,
|
|
132
|
+
sourceRef: GTFOBINS_INDEX.sourceRef,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const linkedMatch = resolveCandidateName(linkedName);
|
|
136
|
+
if (!linkedMatch) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const entry = GTFOBINS_INDEX.entries[linkedMatch.canonicalName];
|
|
140
|
+
return {
|
|
141
|
+
canonicalName: linkedMatch.canonicalName,
|
|
142
|
+
contexts: entry.contexts,
|
|
143
|
+
functions: entry.functions,
|
|
144
|
+
matchSource: "symlink",
|
|
145
|
+
mitreTechniques: entry.mitreTechniques,
|
|
146
|
+
privilegedContexts: entry.contexts.filter((context) =>
|
|
147
|
+
PRIVILEGED_CONTEXTS.includes(context),
|
|
148
|
+
),
|
|
149
|
+
reference: `${GTFOBINS_REFERENCE_PREFIX}${encodeURIComponent(linkedMatch.canonicalName)}/`,
|
|
150
|
+
riskTags: deriveRiskTags(entry, linkedMatch.canonicalName),
|
|
151
|
+
source: GTFOBINS_INDEX.source,
|
|
152
|
+
sourceRef: GTFOBINS_INDEX.sourceRef,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createGtfoBinsProperties(name, linkedName) {
|
|
157
|
+
const metadata = getGtfoBinsMetadata(name, linkedName);
|
|
158
|
+
if (!metadata) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const properties = [
|
|
162
|
+
{ name: "cdx:gtfobins:matched", value: "true" },
|
|
163
|
+
{ name: "cdx:gtfobins:name", value: metadata.canonicalName },
|
|
164
|
+
{ name: "cdx:gtfobins:matchSource", value: metadata.matchSource },
|
|
165
|
+
{ name: "cdx:gtfobins:functions", value: metadata.functions.join(",") },
|
|
166
|
+
{ name: "cdx:gtfobins:contexts", value: metadata.contexts.join(",") },
|
|
167
|
+
{ name: "cdx:gtfobins:reference", value: metadata.reference },
|
|
168
|
+
{ name: "cdx:gtfobins:sourceRef", value: metadata.sourceRef || "" },
|
|
169
|
+
];
|
|
170
|
+
if (metadata.mitreTechniques.length) {
|
|
171
|
+
properties.push({
|
|
172
|
+
name: "cdx:gtfobins:mitreTechniques",
|
|
173
|
+
value: metadata.mitreTechniques.join(","),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (metadata.privilegedContexts.length) {
|
|
177
|
+
properties.push({
|
|
178
|
+
name: "cdx:gtfobins:privilegedContexts",
|
|
179
|
+
value: metadata.privilegedContexts.join(","),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (metadata.riskTags.length) {
|
|
183
|
+
properties.push({
|
|
184
|
+
name: "cdx:gtfobins:riskTags",
|
|
185
|
+
value: metadata.riskTags.join(","),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return properties;
|
|
189
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
|
|
3
|
+
import { describe, it } from "poku";
|
|
4
|
+
|
|
5
|
+
import { createGtfoBinsProperties, getGtfoBinsMetadata } from "./gtfobins.js";
|
|
6
|
+
|
|
7
|
+
describe("gtfobins helpers", () => {
|
|
8
|
+
it("returns metadata for exact GTFOBins matches", () => {
|
|
9
|
+
const metadata = getGtfoBinsMetadata("bash");
|
|
10
|
+
assert.ok(metadata);
|
|
11
|
+
assert.strictEqual(metadata.canonicalName, "bash");
|
|
12
|
+
assert.ok(metadata.functions.includes("shell"));
|
|
13
|
+
assert.ok(metadata.contexts.includes("suid"));
|
|
14
|
+
assert.ok(metadata.riskTags.includes("privilege-escalation"));
|
|
15
|
+
assert.ok(metadata.riskTags.includes("lateral-movement"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("resolves versioned aliases conservatively", () => {
|
|
19
|
+
const metadata = getGtfoBinsMetadata("python3.12");
|
|
20
|
+
assert.ok(metadata);
|
|
21
|
+
assert.strictEqual(metadata.canonicalName, "python");
|
|
22
|
+
assert.strictEqual(metadata.matchSource, "alias");
|
|
23
|
+
assert.ok(metadata.functions.includes("shell"));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("resolves symlink targets when the basename is not indexed", () => {
|
|
27
|
+
const metadata = getGtfoBinsMetadata("sh", "busybox");
|
|
28
|
+
assert.ok(metadata);
|
|
29
|
+
assert.strictEqual(metadata.canonicalName, "busybox");
|
|
30
|
+
assert.strictEqual(metadata.matchSource, "symlink");
|
|
31
|
+
assert.ok(metadata.riskTags.includes("lateral-movement"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("emits stable CycloneDX properties for matched binaries", () => {
|
|
35
|
+
const properties = createGtfoBinsProperties("docker");
|
|
36
|
+
const propertyMap = Object.fromEntries(
|
|
37
|
+
properties.map((property) => [property.name, property.value]),
|
|
38
|
+
);
|
|
39
|
+
assert.strictEqual(propertyMap["cdx:gtfobins:matched"], "true");
|
|
40
|
+
assert.strictEqual(propertyMap["cdx:gtfobins:name"], "docker");
|
|
41
|
+
assert.ok(propertyMap["cdx:gtfobins:functions"].includes("shell"));
|
|
42
|
+
assert.ok(
|
|
43
|
+
propertyMap["cdx:gtfobins:riskTags"].includes("container-escape"),
|
|
44
|
+
);
|
|
45
|
+
assert.ok(
|
|
46
|
+
propertyMap["cdx:gtfobins:reference"].endsWith("/gtfobins/docker/"),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path, { basename } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const LOLBAS_INDEX_FILE = fileURLToPath(
|
|
6
|
+
new URL("../../data/lolbas-index.json", import.meta.url),
|
|
7
|
+
);
|
|
8
|
+
const LOLBAS_REFERENCE_PREFIX =
|
|
9
|
+
"https://lolbas-project.github.io/lolbas/Binaries/";
|
|
10
|
+
const DIRECT_ALIASES = new Map([
|
|
11
|
+
["bitsadmin", "bitsadmin.exe"],
|
|
12
|
+
["certutil", "certutil.exe"],
|
|
13
|
+
["cmd", "cmd.exe"],
|
|
14
|
+
["cmdkey", "cmdkey.exe"],
|
|
15
|
+
["cmstp", "cmstp.exe"],
|
|
16
|
+
["cscript", "cscript.exe"],
|
|
17
|
+
["ftp", "ftp.exe"],
|
|
18
|
+
["installutil", "installutil.exe"],
|
|
19
|
+
["msbuild", "msbuild.exe"],
|
|
20
|
+
["mshta", "mshta.exe"],
|
|
21
|
+
["msiexec", "msiexec.exe"],
|
|
22
|
+
["odbcconf", "odbcconf.exe"],
|
|
23
|
+
["powershell", "powershell.exe"],
|
|
24
|
+
["pwsh", "pwsh.exe"],
|
|
25
|
+
["regsvr32", "regsvr32.exe"],
|
|
26
|
+
["rundll32", "rundll32.exe"],
|
|
27
|
+
["wmic", "wmic.exe"],
|
|
28
|
+
["wscript", "wscript.exe"],
|
|
29
|
+
]);
|
|
30
|
+
const MATCH_FIELDS = [
|
|
31
|
+
"action",
|
|
32
|
+
"arguments",
|
|
33
|
+
"cmdline",
|
|
34
|
+
"command",
|
|
35
|
+
"command_line",
|
|
36
|
+
"command_line_template",
|
|
37
|
+
"description",
|
|
38
|
+
"display_name",
|
|
39
|
+
"executable",
|
|
40
|
+
"name",
|
|
41
|
+
"path",
|
|
42
|
+
"program",
|
|
43
|
+
"source",
|
|
44
|
+
];
|
|
45
|
+
const STANDALONE_COMMAND_PATTERN =
|
|
46
|
+
/\b(bitsadmin|certutil|cmd|cmdkey|cmstp|cscript|ftp|installutil|msbuild|mshta|msiexec|odbcconf|powershell|pwsh|regsvr32|rundll32|wmic|wscript)\b/gi;
|
|
47
|
+
const WINDOWS_EXECUTABLE_PATTERN =
|
|
48
|
+
/(?:[a-z]:\\[^\s"'`,;|]+|\\\\[^\s"'`,;|]+|[a-z0-9._-]+)\.(?:exe|cmd|bat|dll|hta|js|jse|ps1|vbs|vbe|wsf|wsh)\b/gi;
|
|
49
|
+
|
|
50
|
+
const LOLBAS_INDEX = loadLolbasIndex();
|
|
51
|
+
|
|
52
|
+
function loadLolbasIndex() {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(LOLBAS_INDEX_FILE, "utf8"));
|
|
55
|
+
} catch {
|
|
56
|
+
return { entries: {}, source: "", sourceRef: "" };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeCandidate(candidate) {
|
|
61
|
+
if (!candidate || typeof candidate !== "string") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const trimmed = candidate
|
|
65
|
+
.trim()
|
|
66
|
+
.replace(/^["']|["']$/g, "")
|
|
67
|
+
.replace(/\\/g, "/");
|
|
68
|
+
if (!trimmed) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return basename(trimmed).toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function uniqueSortedStrings(values) {
|
|
75
|
+
return Array.from(
|
|
76
|
+
new Set(
|
|
77
|
+
values.filter(
|
|
78
|
+
(value) => typeof value === "string" && value.trim().length,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
).sort();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveLolbasCandidate(candidate) {
|
|
85
|
+
const normalized = normalizeCandidate(candidate);
|
|
86
|
+
if (!normalized) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (LOLBAS_INDEX.entries?.[normalized]) {
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
const alias = DIRECT_ALIASES.get(normalized);
|
|
93
|
+
if (alias && LOLBAS_INDEX.entries?.[alias]) {
|
|
94
|
+
return alias;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function deriveRiskTags(entry) {
|
|
100
|
+
const riskTags = new Set(entry?.riskTags || []);
|
|
101
|
+
const functions = new Set(entry?.functions || []);
|
|
102
|
+
const contexts = new Set(entry?.contexts || []);
|
|
103
|
+
if (
|
|
104
|
+
functions.has("proxy-execution") ||
|
|
105
|
+
functions.has("library-load") ||
|
|
106
|
+
functions.has("script-execution")
|
|
107
|
+
) {
|
|
108
|
+
riskTags.add("proxy-execution");
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
functions.has("download") ||
|
|
112
|
+
functions.has("upload") ||
|
|
113
|
+
functions.has("credential-access")
|
|
114
|
+
) {
|
|
115
|
+
riskTags.add("high-signal");
|
|
116
|
+
}
|
|
117
|
+
if (contexts.has("uac-bypass")) {
|
|
118
|
+
riskTags.add("uac-bypass");
|
|
119
|
+
}
|
|
120
|
+
if (functions.has("download") || functions.has("upload")) {
|
|
121
|
+
riskTags.add("network-transfer");
|
|
122
|
+
}
|
|
123
|
+
return Array.from(riskTags).sort();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function collectValueCandidates(value) {
|
|
127
|
+
if (!value || typeof value !== "string") {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const candidates = new Set();
|
|
131
|
+
for (const match of value.matchAll(WINDOWS_EXECUTABLE_PATTERN)) {
|
|
132
|
+
candidates.add(match[0]);
|
|
133
|
+
}
|
|
134
|
+
for (const match of value.matchAll(STANDALONE_COMMAND_PATTERN)) {
|
|
135
|
+
candidates.add(match[1]);
|
|
136
|
+
}
|
|
137
|
+
return Array.from(candidates);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolve LOLBAS metadata for a binary or script name.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} candidate Binary or script path/name
|
|
144
|
+
* @returns {object|undefined} Matched LOLBAS metadata
|
|
145
|
+
*/
|
|
146
|
+
export function getLolbasMetadata(candidate) {
|
|
147
|
+
const canonicalName = resolveLolbasCandidate(candidate);
|
|
148
|
+
if (!canonicalName) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const entry = LOLBAS_INDEX.entries[canonicalName];
|
|
152
|
+
return {
|
|
153
|
+
attackTactics: uniqueSortedStrings(entry.attackTactics || []),
|
|
154
|
+
attackTechniques: uniqueSortedStrings(entry.attackTechniques || []),
|
|
155
|
+
canonicalName,
|
|
156
|
+
contexts: uniqueSortedStrings(entry.contexts || []),
|
|
157
|
+
functions: uniqueSortedStrings(entry.functions || []),
|
|
158
|
+
reference:
|
|
159
|
+
entry.reference ||
|
|
160
|
+
`${LOLBAS_REFERENCE_PREFIX}${encodeURIComponent(path.parse(canonicalName).name)}/`,
|
|
161
|
+
riskTags: deriveRiskTags(entry),
|
|
162
|
+
source: LOLBAS_INDEX.source,
|
|
163
|
+
sourceRef: LOLBAS_INDEX.sourceRef,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve LOLBAS properties for an osquery row.
|
|
169
|
+
*
|
|
170
|
+
* @param {string} queryCategory Osquery query category
|
|
171
|
+
* @param {object} row Osquery row
|
|
172
|
+
* @returns {Array<object>} CycloneDX custom properties
|
|
173
|
+
*/
|
|
174
|
+
export function createLolbasProperties(queryCategory, row) {
|
|
175
|
+
const matches = new Map();
|
|
176
|
+
for (const field of MATCH_FIELDS) {
|
|
177
|
+
const fieldValue = row?.[field];
|
|
178
|
+
if (!fieldValue) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
for (const candidate of collectValueCandidates(String(fieldValue))) {
|
|
182
|
+
const metadata = getLolbasMetadata(candidate);
|
|
183
|
+
if (!metadata) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const existing = matches.get(metadata.canonicalName) || {
|
|
187
|
+
fields: new Set(),
|
|
188
|
+
metadata,
|
|
189
|
+
};
|
|
190
|
+
existing.fields.add(field);
|
|
191
|
+
matches.set(metadata.canonicalName, existing);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!matches.size) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
const attackTactics = uniqueSortedStrings(
|
|
198
|
+
Array.from(matches.values()).flatMap(
|
|
199
|
+
(match) => match.metadata.attackTactics,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
const attackTechniques = uniqueSortedStrings(
|
|
203
|
+
Array.from(matches.values()).flatMap(
|
|
204
|
+
(match) => match.metadata.attackTechniques,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
const contexts = uniqueSortedStrings(
|
|
208
|
+
Array.from(matches.values()).flatMap((match) => match.metadata.contexts),
|
|
209
|
+
);
|
|
210
|
+
const functions = uniqueSortedStrings(
|
|
211
|
+
Array.from(matches.values()).flatMap((match) => match.metadata.functions),
|
|
212
|
+
);
|
|
213
|
+
const references = uniqueSortedStrings(
|
|
214
|
+
Array.from(matches.values()).map((match) => match.metadata.reference),
|
|
215
|
+
);
|
|
216
|
+
const riskTags = uniqueSortedStrings(
|
|
217
|
+
Array.from(matches.values()).flatMap((match) => match.metadata.riskTags),
|
|
218
|
+
);
|
|
219
|
+
const matchFields = uniqueSortedStrings(
|
|
220
|
+
Array.from(matches.values()).flatMap((match) => Array.from(match.fields)),
|
|
221
|
+
);
|
|
222
|
+
const names = uniqueSortedStrings(Array.from(matches.keys()));
|
|
223
|
+
const properties = [
|
|
224
|
+
{ name: "cdx:lolbas:matched", value: "true" },
|
|
225
|
+
{ name: "cdx:lolbas:names", value: names.join(",") },
|
|
226
|
+
{ name: "cdx:lolbas:matchFields", value: matchFields.join(",") },
|
|
227
|
+
{ name: "cdx:lolbas:queryCategory", value: queryCategory },
|
|
228
|
+
{ name: "cdx:lolbas:sourceRef", value: LOLBAS_INDEX.sourceRef || "" },
|
|
229
|
+
];
|
|
230
|
+
if (functions.length) {
|
|
231
|
+
properties.push({
|
|
232
|
+
name: "cdx:lolbas:functions",
|
|
233
|
+
value: functions.join(","),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (contexts.length) {
|
|
237
|
+
properties.push({
|
|
238
|
+
name: "cdx:lolbas:contexts",
|
|
239
|
+
value: contexts.join(","),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (riskTags.length) {
|
|
243
|
+
properties.push({
|
|
244
|
+
name: "cdx:lolbas:riskTags",
|
|
245
|
+
value: riskTags.join(","),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (attackTactics.length) {
|
|
249
|
+
properties.push({
|
|
250
|
+
name: "cdx:lolbas:attackTactics",
|
|
251
|
+
value: attackTactics.join(","),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (attackTechniques.length) {
|
|
255
|
+
properties.push({
|
|
256
|
+
name: "cdx:lolbas:attackTechniques",
|
|
257
|
+
value: attackTechniques.join(","),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (references.length) {
|
|
261
|
+
properties.push({
|
|
262
|
+
name: "cdx:lolbas:references",
|
|
263
|
+
value: references.join(","),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return properties;
|
|
267
|
+
}
|