Merge remote-tracking branch 'origin/master' into leon/node-updates
This commit is contained in:
commit
8986d3d812
4
package-lock.json
generated
4
package-lock.json
generated
@ -112,12 +112,12 @@
|
||||
"@babel/plugin-syntax-import-assertions": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/language": "^6.12.2",
|
||||
"@codemirror/search": "^6.6.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.39.17",
|
||||
"@codemirror/view": "^6.40.0",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"babel-loader": "^10.1.1",
|
||||
"base64-loader": "^1.0.0",
|
||||
|
||||
@ -43,12 +43,12 @@
|
||||
"@babel/plugin-syntax-import-assertions": "^7.28.6",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/language": "^6.12.2",
|
||||
"@codemirror/search": "^6.6.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.39.17",
|
||||
"@codemirror/view": "^6.40.0",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"babel-loader": "^10.1.1",
|
||||
"base64-loader": "^1.0.0",
|
||||
|
||||
@ -55,8 +55,15 @@ class Chef {
|
||||
progress = await recipe.execute(this.dish, progress);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
|
||||
let displayStr;
|
||||
if ("displayStr" in err) {
|
||||
displayStr = err.displayStr;
|
||||
} else {
|
||||
displayStr = err.toString();
|
||||
}
|
||||
error = {
|
||||
displayStr: err.displayStr,
|
||||
displayStr: displayStr,
|
||||
};
|
||||
progress = err.progress;
|
||||
}
|
||||
|
||||
5
src/core/Ingredient.mjs
Executable file → Normal file
5
src/core/Ingredient.mjs
Executable file → Normal file
@ -6,6 +6,7 @@
|
||||
|
||||
import Utils from "./Utils.mjs";
|
||||
import { fromHex } from "./lib/Hex.mjs";
|
||||
import OperationError from "./errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* The arguments to operations.
|
||||
@ -119,7 +120,9 @@ class Ingredient {
|
||||
number = parseFloat(data);
|
||||
if (isNaN(number)) {
|
||||
const sample = Utils.truncate(data.toString(), 10);
|
||||
throw "Invalid ingredient value. Not a number: " + sample;
|
||||
throw new OperationError(
|
||||
"Invalid ingredient value. Not a number: " + sample,
|
||||
);
|
||||
}
|
||||
return number;
|
||||
default:
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import Dish from "./Dish.mjs";
|
||||
import OperationError from "./errors/OperationError.mjs";
|
||||
import Ingredient from "./Ingredient.mjs";
|
||||
|
||||
/**
|
||||
@ -223,7 +224,11 @@ class Operation {
|
||||
*/
|
||||
set ingValues(ingValues) {
|
||||
ingValues.forEach((val, i) => {
|
||||
try {
|
||||
this._ingList[i].value = val;
|
||||
} catch (err) {
|
||||
throw new OperationError(`Failed to set value of ingredient '${this._ingList[i].name}': ${err}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -70,11 +70,15 @@ class Recipe {
|
||||
if (o instanceof Operation) {
|
||||
return o;
|
||||
} else {
|
||||
try {
|
||||
const op = new modules[o.module][o.name]();
|
||||
op.ingValues = o.ingValues;
|
||||
op.breakpoint = o.breakpoint;
|
||||
op.disabled = o.disabled;
|
||||
return op;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to hydrate operation '${o.name}': ${err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -249,6 +249,7 @@
|
||||
"DNS over HTTPS",
|
||||
"Strip HTTP headers",
|
||||
"Dechunk HTTP response",
|
||||
"Parse Ethernet frame",
|
||||
"Parse User Agent",
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
|
||||
@ -30,7 +30,12 @@ class Jq extends Operation {
|
||||
name: "Query",
|
||||
type: "string",
|
||||
value: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Raw",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -40,7 +45,7 @@ class Jq extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [query] = args;
|
||||
const [query, raw] = args;
|
||||
let result;
|
||||
|
||||
try {
|
||||
@ -48,9 +53,12 @@ class Jq extends Operation {
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid jq expression: ${err.message}`);
|
||||
}
|
||||
|
||||
if (raw && typeof result === "string") {
|
||||
return result;
|
||||
} else {
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
115
src/core/operations/ParseEthernetFrame.mjs
Normal file
115
src/core/operations/ParseEthernetFrame.mjs
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @author tedk [tedk@ted.do]
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {fromHex, toHex} from "../lib/Hex.mjs";
|
||||
|
||||
/**
|
||||
* Parse Ethernet frame operation
|
||||
*/
|
||||
class ParseEthernetFrame extends Operation {
|
||||
|
||||
/**
|
||||
* ParseEthernetFrame constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse Ethernet frame";
|
||||
this.module = "Default";
|
||||
this.description = "Parses an Ethernet frame and either shows the deduced values (Source and destination MAC, VLANs) or returns the packet data.<br /><br />Good for use in conjunction with the Parse IPv4, and Parse TCP/UDP recipes.";
|
||||
this.infoURL = "https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer";
|
||||
this.inputType = "string";
|
||||
this.outputType = "html";
|
||||
this.args = [
|
||||
{
|
||||
name: "Input type",
|
||||
type: "option",
|
||||
value: [
|
||||
"Raw", "Hex"
|
||||
],
|
||||
defaultIndex: 0,
|
||||
},
|
||||
{
|
||||
name: "Return type",
|
||||
type: "option",
|
||||
value: [
|
||||
"Text output", "Packet data", "Packet data (hex)",
|
||||
],
|
||||
defaultIndex: 0,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {html}
|
||||
*/
|
||||
run(input, args) {
|
||||
const format = args[0];
|
||||
const outputFormat = args[1];
|
||||
|
||||
if (format === "Hex") {
|
||||
input = fromHex(input);
|
||||
} else if (format === "Raw") {
|
||||
input = new Uint8Array(Utils.strToArrayBuffer(input));
|
||||
} else {
|
||||
throw new OperationError("Invalid input format selected.");
|
||||
}
|
||||
|
||||
const destinationMac = input.slice(0, 6);
|
||||
const sourceMac = input.slice(6, 12);
|
||||
|
||||
let offset = 12;
|
||||
const vlans = [];
|
||||
|
||||
while (offset < input.length) {
|
||||
const ethType = Utils.byteArrayToChars(input.slice(offset, offset+2));
|
||||
offset += 2;
|
||||
|
||||
|
||||
if (ethType === "\x08\x00") {
|
||||
break;
|
||||
} else if (ethType === "\x81\x00" || ethType === "\x88\xA8") {
|
||||
// Parse the VLAN tag:
|
||||
// [0000] 0000 0000 0000
|
||||
// ^^^ PRIO - Ignored
|
||||
// ^ DEI - Ignored
|
||||
// ^^^^ ^^^^ ^^^^ VLAN ID
|
||||
const vlanTag = input.slice(offset+2, offset+4);
|
||||
vlans.push((vlanTag[0] & 0b00001111) << 4 | vlanTag[1]);
|
||||
|
||||
offset += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const packetData = input.slice(offset);
|
||||
|
||||
if (outputFormat === "Packet data") {
|
||||
return Utils.byteArrayToChars(packetData);
|
||||
} else if (outputFormat === "Packet data (hex)") {
|
||||
return toHex(packetData);
|
||||
} else if (outputFormat === "Text output") {
|
||||
let retval = `Source MAC: ${toHex(sourceMac, ":")}\nDestination MAC: ${toHex(destinationMac, ":")}\n`;
|
||||
if (vlans.length > 0) {
|
||||
retval += `VLAN: ${vlans.join(", ")}\n`;
|
||||
}
|
||||
retval += `Data:\n${toHex(packetData)}`;
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default ParseEthernetFrame;
|
||||
@ -33,6 +33,12 @@ class ParseIPv4Header extends Operation {
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["Hex", "Raw"]
|
||||
},
|
||||
{
|
||||
"name": "Output format",
|
||||
"type": "option",
|
||||
"value": ["Table", "Data (hex)", "Data (raw)"],
|
||||
defaultIndex: 0,
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -44,6 +50,8 @@ class ParseIPv4Header extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const format = args[0];
|
||||
const outputFormat = args[1];
|
||||
|
||||
let output;
|
||||
|
||||
if (format === "Hex") {
|
||||
@ -98,6 +106,9 @@ class ParseIPv4Header extends Operation {
|
||||
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
|
||||
}
|
||||
|
||||
const data = input.slice(ihl * 4);
|
||||
|
||||
if (outputFormat === "Table") {
|
||||
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
|
||||
<tr><td>Version</td><td>${version}</td></tr>
|
||||
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
|
||||
@ -116,13 +127,19 @@ class ParseIPv4Header extends Operation {
|
||||
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
|
||||
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
|
||||
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
|
||||
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;
|
||||
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>
|
||||
<tr><td>Data (hex)</td><td>${toHex(data)}</td></tr>`;
|
||||
|
||||
if (ihl > 5) {
|
||||
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
|
||||
}
|
||||
|
||||
return output + "</table>";
|
||||
} else if (outputFormat === "Data (hex)") {
|
||||
return toHex(data);
|
||||
} else if (outputFormat === "Data (raw)") {
|
||||
return Utils.byteArrayToChars(data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -130,6 +130,7 @@ class Manager {
|
||||
// Controls
|
||||
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
|
||||
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
|
||||
document.getElementById("auto-bake").addEventListener("keydown", this.controls.autoBakeKeyboardHandler.bind(this.controls));
|
||||
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
|
||||
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
|
||||
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
|
||||
|
||||
@ -57,6 +57,18 @@ class ControlsWaiter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks or unchecks the Auto Bake checkbox with "Enter"
|
||||
* @param {Event} ev
|
||||
*/
|
||||
autoBakeKeyboardHandler(ev) {
|
||||
const checkBox = document.getElementById("auto-bake");
|
||||
ev.preventDefault();
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
checkBox.checked = !checkBox.checked;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler to trigger baking.
|
||||
|
||||
@ -268,7 +268,7 @@ module.exports = {
|
||||
testOpHtml(browser, "Parse colour code", "#000", ".colorpicker-preview", "rgb(0, 0, 0)");
|
||||
testOpHtml(browser, "Parse DateTime", "01/12/2000 13:00:00", "", /Date: Friday 1st December 2000/);
|
||||
// testOp(browser, "Parse IP range", "test input", "test_output");
|
||||
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:last-child td:last-child", "192.168.12.2");
|
||||
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:nth-last-child(2) td:last-child", "192.168.12.2");
|
||||
// testOp(browser, "Parse IPv6 address", "test input", "test_output");
|
||||
// testOp(browser, "Parse ObjectID timestamp", "test input", "test_output");
|
||||
// testOp(browser, "Parse QR Code", "test input", "test_output");
|
||||
|
||||
@ -123,6 +123,7 @@ import "./tests/NetBIOS.mjs";
|
||||
import "./tests/NormaliseUnicode.mjs";
|
||||
import "./tests/NTLM.mjs";
|
||||
import "./tests/OTP.mjs";
|
||||
import "./tests/ParseEthernetFrame.mjs";
|
||||
import "./tests/ParseIPRange.mjs";
|
||||
import "./tests/ParseObjectIDTimestamp.mjs";
|
||||
import "./tests/ParseQRCode.mjs";
|
||||
|
||||
32
tests/operations/tests/Jq.mjs
Normal file
32
tests/operations/tests/Jq.mjs
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Jq tests.
|
||||
*
|
||||
* @author rtpt-romankarwacik [roman.karwacik@redteam-pentesting.de]
|
||||
*
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Get raw JSON Property",
|
||||
input: '{"data": "testString\\u0000"}',
|
||||
expectedOutput: "testString\u0000",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Jq",
|
||||
args: [".data", true],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Get JSON Property",
|
||||
input: '{"data": "testString\\u0000"}',
|
||||
expectedOutput: "\"testString\\u0000\"",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Jq",
|
||||
args: [".data", false],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
45
tests/operations/tests/ParseEthernetFrame.mjs
Normal file
45
tests/operations/tests/ParseEthernetFrame.mjs
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Parse Ethernet frame tests.
|
||||
*
|
||||
* @author tedk [tedk@ted.do]
|
||||
* @copyright Crown Copyright 2017
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Parse plain Ethernet frame",
|
||||
input: "000000000000ffffffffffff08004500",
|
||||
expectedOutput: "Source MAC: ff:ff:ff:ff:ff:ff\nDestination MAC: 00:00:00:00:00:00\nData:\n45 00",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse Ethernet frame",
|
||||
"args": ["Hex", "Text output"]
|
||||
}
|
||||
]
|
||||
},
|
||||
// Example PCAP data from: https://packetlife.net/captures/protocol/vlan/
|
||||
{
|
||||
name: "Parse Ethernet frame with one VLAN tag (802.1q)",
|
||||
input: "01000ccdcdd00013c3dfae188100a0760165aaaa",
|
||||
expectedOutput: "Source MAC: 00:13:c3:df:ae:18\nDestination MAC: 01:00:0c:cd:cd:d0\nVLAN: 117\nData:\naa aa",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse Ethernet frame",
|
||||
"args": ["Hex", "Text output"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Parse Ethernet frame with two VLAN tags (802.1ad)",
|
||||
input: "0019aa7de688002155c8f13c810000d18100001408004500",
|
||||
expectedOutput: "Source MAC: 00:21:55:c8:f1:3c\nDestination MAC: 00:19:aa:7d:e6:88\nVLAN: 16, 128\nData:\n45 00",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse Ethernet frame",
|
||||
"args": ["Hex", "Text output"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user