@patrickdk77/script-defuse 0.0.0-stage → 0.2.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 -0
- package/README.md +232 -2
- package/dist/analyze.d.ts +22 -0
- package/dist/analyze.js +770 -0
- package/dist/api.d.ts +82 -0
- package/dist/api.js +698 -0
- package/dist/engine.d.ts +12 -0
- package/dist/engine.js +22 -0
- package/dist/findings.d.ts +19 -0
- package/dist/findings.js +89 -0
- package/dist/formcalc.d.ts +10 -0
- package/dist/formcalc.js +792 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +34 -0
- package/dist/index.mjs +3 -0
- package/dist/plugins.d.ts +28 -0
- package/dist/plugins.js +29 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +51 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.js +80 -0
- package/dist/vba.d.ts +11 -0
- package/dist/vba.js +728 -0
- package/package.json +84 -4
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScriptContext, ScriptInspection, ScriptOptions } from './types';
|
|
2
|
+
export interface InspectScriptOptions extends ScriptOptions {
|
|
3
|
+
/** Where the script runs, for the findings' locations. */
|
|
4
|
+
context?: ScriptContext;
|
|
5
|
+
/** Global names scripts that ran before this one defined and kept, which this one may use. */
|
|
6
|
+
knownGlobals?: Iterable<string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Inspect one script, in JavaScript or, when the context says so, in FormCalc or VBA. The verdict is 'form' when no finding strips it: every call and property a form
|
|
10
|
+
* script uses formats, checks or calculates form fields, or a VBA module calls nothing known to reach outside its document, and nothing in it hides what it does.
|
|
11
|
+
*/
|
|
12
|
+
export declare function inspectScript(text: string, options?: InspectScriptOptions): ScriptInspection;
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inspectScript = inspectScript;
|
|
4
|
+
const analyze_1 = require("./analyze");
|
|
5
|
+
const formcalc_1 = require("./formcalc");
|
|
6
|
+
const score_1 = require("./score");
|
|
7
|
+
const vba_1 = require("./vba");
|
|
8
|
+
/**
|
|
9
|
+
* Inspect one script, in JavaScript or, when the context says so, in FormCalc or VBA. The verdict is 'form' when no finding strips it: every call and property a form
|
|
10
|
+
* script uses formats, checks or calculates form fields, or a VBA module calls nothing known to reach outside its document, and nothing in it hides what it does.
|
|
11
|
+
*/
|
|
12
|
+
function inspectScript(text, options = {}) {
|
|
13
|
+
const language = options.context?.language;
|
|
14
|
+
const { findings, definedGlobals } = language === 'formcalc'
|
|
15
|
+
? (0, formcalc_1.analyzeFormCalc)({ text, context: options.context, options })
|
|
16
|
+
: language === 'vba'
|
|
17
|
+
? (0, vba_1.analyzeVba)({ text, context: options.context, options })
|
|
18
|
+
: (0, analyze_1.analyzeScript)({ text, context: options.context, options, known: new Set(options.knownGlobals ?? []) });
|
|
19
|
+
const counted = findings.filter(f => f.action === 'strip');
|
|
20
|
+
const { score, risk } = (0, score_1.scoreFindings)(counted, options);
|
|
21
|
+
return { verdict: counted.length ? 'unsafe' : 'form', score, risk, findings, definedGlobals };
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ScriptCategory as C, ScriptDetail as D, type ScriptActionOverride, type ScriptFinding, type ScriptFindingAction } from './types';
|
|
2
|
+
interface Spec {
|
|
3
|
+
action: ScriptFindingAction;
|
|
4
|
+
/** May contain <key> placeholders filled from data. A filled value has control characters replaced by "?" and is cut to 60 characters. */
|
|
5
|
+
description: string;
|
|
6
|
+
weight: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function findingSpec(c: string, d: string): Spec | undefined;
|
|
9
|
+
/** A finding with its default action, after the caller's overrides. */
|
|
10
|
+
export declare function makeFinding(c: C, d: D, overrides?: ScriptActionOverride[], location?: string, data?: Record<string, string | number>): ScriptFinding;
|
|
11
|
+
/** Every finding kind this package can report, with its default action, weight and description. */
|
|
12
|
+
export declare function allFindingSpecs(): Array<{
|
|
13
|
+
category: C;
|
|
14
|
+
detail: D;
|
|
15
|
+
action: ScriptFindingAction;
|
|
16
|
+
weight: number;
|
|
17
|
+
description: string;
|
|
18
|
+
}>;
|
|
19
|
+
export {};
|
package/dist/findings.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findingSpec = findingSpec;
|
|
4
|
+
exports.makeFinding = makeFinding;
|
|
5
|
+
exports.allFindingSpecs = allFindingSpecs;
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const specs = new Map();
|
|
8
|
+
function def(c, d, action, weight, description) {
|
|
9
|
+
specs.set(`${c}/${d}`, { action, weight, description });
|
|
10
|
+
}
|
|
11
|
+
// FORM
|
|
12
|
+
def(types_1.ScriptCategory.Form, types_1.ScriptDetail.Computation, 'info', 0, 'The script only formats, checks or calculates form fields');
|
|
13
|
+
def(types_1.ScriptCategory.Form, types_1.ScriptDetail.DocumentMacro, 'info', 0, 'The macro calls nothing script-defuse knows to reach outside its document');
|
|
14
|
+
// EXTERNAL
|
|
15
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Url, 'strip', 60, '<api> opens a web address');
|
|
16
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Submit, 'strip', 50, '<api> sends the form data somewhere');
|
|
17
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Mail, 'strip', 50, '<api> sends mail');
|
|
18
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.OpenDocument, 'strip', 60, '<api> opens or creates another document');
|
|
19
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.DataObject, 'strip', 70, '<api> reads, writes or opens a file attached to the document');
|
|
20
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.ImportExport, 'strip', 50, '<api> reads or writes form data or files outside the document');
|
|
21
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Network, 'strip', 70, '<api> talks to a network service');
|
|
22
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.MenuCommand, 'strip', 60, '<api> runs a viewer menu command');
|
|
23
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Print, 'strip', 20, '<api> prints');
|
|
24
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Save, 'strip', 30, '<api> saves or closes the document');
|
|
25
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.FileSystem, 'strip', 70, '<api> reads, writes or lists files and folders');
|
|
26
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Process, 'strip', 90, '<api> starts a program or sends keystrokes to one');
|
|
27
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Registry, 'strip', 60, '<api> reads or writes the registry or settings files');
|
|
28
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Dde, 'strip', 80, '<api> talks to another program over DDE');
|
|
29
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.ComObject, 'strip', 80, '<api> creates or gets an object from outside the document, such as a shell or a web request');
|
|
30
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.NativeCode, 'strip', 90, '<api> calls native code in a library');
|
|
31
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Environment, 'strip', 30, '<api> reads the environment of the computer it runs on');
|
|
32
|
+
def(types_1.ScriptCategory.External, types_1.ScriptDetail.Reference, 'strip', 60, 'The project refers to <api>, a library or project from outside the document');
|
|
33
|
+
// DYNAMIC_CODE
|
|
34
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Eval, 'strip', 80, '<api> runs code built from a string');
|
|
35
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Timer, 'strip', 70, '<api> runs code later, from a string');
|
|
36
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.InstallScript, 'strip', 70, '<api> installs another script or action');
|
|
37
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Privilege, 'strip', 80, '<api> asks for privileged execution');
|
|
38
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.ComputedAccess, 'strip', 40, 'A property is read by a name computed at run time, so what it reaches cannot be told');
|
|
39
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.Prototype, 'strip', 60, '<api> reaches the language machinery behind objects and functions');
|
|
40
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.With, 'strip', 40, 'A with statement changes what names refer to');
|
|
41
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.SelfReference, 'strip', 60, '<api> reads the source of the running code, the way self-decrypting scripts do');
|
|
42
|
+
def(types_1.ScriptCategory.DynamicCode, types_1.ScriptDetail.LegacyMacro, 'strip', 80, '<api> runs a command of an older macro language, which reaches outside the document');
|
|
43
|
+
// OBFUSCATION
|
|
44
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.CharCodes, 'strip', 50, 'Text is built from character codes');
|
|
45
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Decode, 'strip', 60, '<api> decodes hidden text');
|
|
46
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Concatenation, 'strip', 40, 'A string is joined from many short pieces');
|
|
47
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.Escapes, 'strip', 40, 'A string writes ordinary letters as escape sequences');
|
|
48
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.EncodedString, 'strip', 50, 'A string holds encoded data');
|
|
49
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.LongString, 'strip', 40, 'A string far longer than form text');
|
|
50
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.NumberExpression, 'strip', 30, 'A number is written as arithmetic on hex constants');
|
|
51
|
+
def(types_1.ScriptCategory.Obfuscation, types_1.ScriptDetail.BracketName, 'strip', 30, '<api> is reached by a quoted name where a form script names it plainly');
|
|
52
|
+
// API
|
|
53
|
+
def(types_1.ScriptCategory.Api, types_1.ScriptDetail.Exploited, 'strip', 80, '<api> is a viewer API known from exploits');
|
|
54
|
+
def(types_1.ScriptCategory.Api, types_1.ScriptDetail.Unknown, 'strip', 30, '<api> is not a form calculation, format or check');
|
|
55
|
+
def(types_1.ScriptCategory.Api, types_1.ScriptDetail.PasswordPrompt, 'strip', 50, '<api> asks for a password in a prompt, as a fake sign-in does. A form takes a password in a field of its own');
|
|
56
|
+
// SYNTAX
|
|
57
|
+
def(types_1.ScriptCategory.Syntax, types_1.ScriptDetail.Unparseable, 'strip', 50, 'The script does not parse, so what it does cannot be told');
|
|
58
|
+
// LIMIT
|
|
59
|
+
def(types_1.ScriptCategory.Limit, types_1.ScriptDetail.Size, 'strip', 30, 'The script is longer than limits.scriptSize');
|
|
60
|
+
def(types_1.ScriptCategory.Limit, types_1.ScriptDetail.Depth, 'strip', 30, 'The script nests deeper than limits.depth');
|
|
61
|
+
function findingSpec(c, d) {
|
|
62
|
+
return specs.get(`${c}/${d}`);
|
|
63
|
+
}
|
|
64
|
+
const clip = (v) => String(v)
|
|
65
|
+
.replace(/[\p{Cc}\p{Cf}]/gu, '?')
|
|
66
|
+
.slice(0, 60);
|
|
67
|
+
/** A finding with its default action, after the caller's overrides. */
|
|
68
|
+
function makeFinding(c, d, overrides = [], location, data) {
|
|
69
|
+
const spec = specs.get(`${c}/${d}`);
|
|
70
|
+
if (!spec)
|
|
71
|
+
throw new Error(`no finding spec for ${c}/${d}`);
|
|
72
|
+
const exact = overrides.find(o => o.category === c && o.detail === d);
|
|
73
|
+
const byCategory = overrides.find(o => o.category === c && o.detail === undefined);
|
|
74
|
+
const action = exact?.action ?? byCategory?.action ?? spec.action;
|
|
75
|
+
const description = spec.description.replace(/<(\w+)>/g, (_m, key) => (data?.[key] === undefined ? key : clip(data[key])));
|
|
76
|
+
const f = { category: c, detail: d, description, action, weight: spec.weight };
|
|
77
|
+
if (location)
|
|
78
|
+
f.location = location;
|
|
79
|
+
if (data)
|
|
80
|
+
f.data = data;
|
|
81
|
+
return f;
|
|
82
|
+
}
|
|
83
|
+
/** Every finding kind this package can report, with its default action, weight and description. */
|
|
84
|
+
function allFindingSpecs() {
|
|
85
|
+
return [...specs].map(([key, spec]) => {
|
|
86
|
+
const [category, detail] = key.split('/');
|
|
87
|
+
return { category, detail, ...spec };
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ScriptContext, type ScriptFinding, type ScriptOptions } from './types';
|
|
2
|
+
export interface FormCalcInput {
|
|
3
|
+
text: string;
|
|
4
|
+
context?: ScriptContext;
|
|
5
|
+
options?: ScriptOptions;
|
|
6
|
+
}
|
|
7
|
+
export declare function analyzeFormCalc(input: FormCalcInput): {
|
|
8
|
+
findings: ScriptFinding[];
|
|
9
|
+
definedGlobals: string[];
|
|
10
|
+
};
|