Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
7830bac871
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -25,12 +25,8 @@ updates:
|
||||
# see issue #2214 for rationale for each of these
|
||||
- dependency-name: '@xmldom/xmldom'
|
||||
versions: [ '>=0.9.0' ]
|
||||
- dependency-name: 'bcryptjs'
|
||||
versions: [ '>=3.0.0' ]
|
||||
- dependency-name: 'bootstrap'
|
||||
versions: [ '>=5.0.0' ]
|
||||
- dependency-name: 'bson'
|
||||
versions: [ '>=5.0.0' ]
|
||||
- dependency-name: 'cbor'
|
||||
versions: [ '>=10.0.0' ]
|
||||
- dependency-name: 'eslint'
|
||||
|
||||
8
.github/workflows/master.yml
vendored
8
.github/workflows/master.yml
vendored
@ -44,9 +44,17 @@ jobs:
|
||||
- name: Generate sitemap
|
||||
run: npx grunt exec:sitemap
|
||||
|
||||
- name: Setup Chrome
|
||||
id: setup-chrome
|
||||
if: success()
|
||||
run: |
|
||||
npx @puppeteer/browsers install chrome@148
|
||||
echo chromedir=$(dirname $(find `pwd`/chrome/* -name chrome -print -quit)) >> $GITHUB_OUTPUT
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: |
|
||||
export PATH=${{ steps.setup-chrome.outputs.chromedir }}:$PATH
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
|
||||
10
.github/workflows/pull_requests.yml
vendored
10
.github/workflows/pull_requests.yml
vendored
@ -45,9 +45,17 @@ jobs:
|
||||
path: build/prod/*.zip
|
||||
retention-days: 5
|
||||
|
||||
- name: Setup Chrome
|
||||
id: setup-chrome
|
||||
if: success()
|
||||
run: |
|
||||
npx @puppeteer/browsers install chrome@148
|
||||
echo chromedir=$(dirname $(find `pwd`/chrome/* -name chrome -print -quit)) >> $GITHUB_OUTPUT
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: |
|
||||
export PATH=${{ steps.setup-chrome.outputs.chromedir }}:$PATH
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
@ -61,6 +69,6 @@ jobs:
|
||||
- name: Production Image Build
|
||||
if: success()
|
||||
id: build-image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
13
.github/workflows/releases.yml
vendored
13
.github/workflows/releases.yml
vendored
@ -46,8 +46,17 @@ jobs:
|
||||
- name: Production Build
|
||||
run: npx grunt prod
|
||||
|
||||
- name: UI Tests
|
||||
- name: Setup Chrome
|
||||
id: setup-chrome
|
||||
if: success()
|
||||
run: |
|
||||
npx @puppeteer/browsers install chrome@148
|
||||
echo chromedir=$(dirname $(find `pwd`/chrome/* -name chrome -print -quit)) >> $GITHUB_OUTPUT
|
||||
|
||||
- name: UI Tests
|
||||
if: success()
|
||||
run: |
|
||||
export PATH=${{ steps.setup-chrome.outputs.chromedir }}:$PATH
|
||||
sudo apt-get install xvfb
|
||||
xvfb-run --server-args="-screen 0 1200x800x24" npx grunt testui
|
||||
|
||||
@ -75,7 +84,7 @@ jobs:
|
||||
password: ${{ env.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Publish to GHCR
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ src/node/index.mjs
|
||||
**/*.DS_Store
|
||||
tests/browser/output/*
|
||||
.node-version
|
||||
chrome
|
||||
|
||||
@ -27,7 +27,7 @@ RUN npm run build
|
||||
#########################################
|
||||
# Package static build files into nginx #
|
||||
#########################################
|
||||
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:808f7846d21a9c94cf53833e8807a00a33fd0b65cc47fb05b79efe366c2d201f AS cyberchef
|
||||
FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:b9f7ba14f1f7bd3d40d7753584048f92c3aef9ccf5fab14efe4451a8d4c04d63 AS cyberchef
|
||||
|
||||
LABEL maintainer="GCHQ <oss@gchq.gov.uk>"
|
||||
|
||||
|
||||
12
Gruntfile.js
12
Gruntfile.js
@ -89,6 +89,8 @@ module.exports = function (grunt) {
|
||||
const compileYear = grunt.template.today("UTC:yyyy"),
|
||||
compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
|
||||
pkg = grunt.file.readJSON("package.json"),
|
||||
version = process.env.GITHUB_SHA || `v${pkg.version}`,
|
||||
downloadZipFilename = `CyberChef_${version}.zip`,
|
||||
webpackConfig = require("./webpack.config.js"),
|
||||
BUILD_CONSTANTS = {
|
||||
COMPILE_YEAR: JSON.stringify(compileYear),
|
||||
@ -129,7 +131,9 @@ module.exports = function (grunt) {
|
||||
chunks: ["main"],
|
||||
compileYear: compileYear,
|
||||
compileTime: compileTime,
|
||||
version: pkg.version,
|
||||
version: version,
|
||||
latestReleaseVersion: pkg.version,
|
||||
downloadZipFilename: downloadZipFilename,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
@ -245,7 +249,7 @@ module.exports = function (grunt) {
|
||||
"!build/prod/index.html",
|
||||
"!build/prod/BundleAnalyzerReport.html",
|
||||
],
|
||||
dest: `build/prod/CyberChef_v${pkg.version}.zip`
|
||||
dest: `build/prod/${downloadZipFilename}`
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
@ -333,12 +337,12 @@ module.exports = function (grunt) {
|
||||
switch (process.platform) {
|
||||
case "darwin":
|
||||
return chainCommands([
|
||||
`shasum -a 256 build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`shasum -a 256 build/prod/${downloadZipFilename} | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`sed -i '' -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||
]);
|
||||
default:
|
||||
return chainCommands([
|
||||
`sha256sum build/prod/CyberChef_v${pkg.version}.zip | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`sha256sum build/prod/${downloadZipFilename} | awk '{print $1;}' > build/prod/sha256digest.txt`,
|
||||
`sed -i -e "s/DOWNLOAD_HASH_PLACEHOLDER/$(cat build/prod/sha256digest.txt)/" build/prod/index.html`
|
||||
]);
|
||||
}
|
||||
|
||||
808
package-lock.json
generated
808
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -49,10 +49,11 @@
|
||||
"@codemirror/search": "^6.7.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.43.0",
|
||||
"@puppeteer/browsers": "3.0.3",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"babel-loader": "^10.1.1",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^146.0.6",
|
||||
"chromedriver": "^148.0.3",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression-webpack-plugin": "^12.0.0",
|
||||
@ -86,7 +87,7 @@
|
||||
"prompt": "^1.3.0",
|
||||
"sitemap": "^8.0.3",
|
||||
"terser": "^5.46.2",
|
||||
"webpack": "^5.106.2",
|
||||
"webpack": "^5.107.1",
|
||||
"webpack-bundle-analyzer": "^5.3.0",
|
||||
"webpack-dev-server": "^5.0.4",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
@ -110,7 +111,7 @@
|
||||
"bootstrap-colorpicker": "^3.4.0",
|
||||
"bootstrap-material-design": "^4.1.3",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"bson": "^4.7.2",
|
||||
"bson": "^7.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cbor": "9.0.2",
|
||||
"chi-squared": "^1.1.0",
|
||||
@ -168,7 +169,7 @@
|
||||
"path": "^0.12.7",
|
||||
"popper.js": "^1.16.1",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs": "^7.5.8",
|
||||
"protobufjs": "^7.6.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"qr-image": "^3.2.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
@ -177,7 +178,7 @@
|
||||
"snackbarjs": "^1.1.0",
|
||||
"sortablejs": "^1.15.7",
|
||||
"split.js": "^1.6.5",
|
||||
"sql-formatter": "^15.7.4",
|
||||
"sql-formatter": "^15.8.0",
|
||||
"ssdeep.js": "0.0.3",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"tesseract.js": "^6.0.1",
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import bson from "bson";
|
||||
import { deserialize } from "bson";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
@ -37,7 +37,7 @@ class BSONDeserialise extends Operation {
|
||||
if (!input.byteLength) return "";
|
||||
|
||||
try {
|
||||
const data = bson.deserialize(new Buffer(input));
|
||||
const data = deserialize(new Uint8Array(input));
|
||||
return JSON.stringify(data, null, 2);
|
||||
} catch (err) {
|
||||
throw new OperationError(err.toString());
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import bson from "bson";
|
||||
import { serialize } from "bson";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
@ -38,7 +38,8 @@ class BSONSerialise extends Operation {
|
||||
|
||||
try {
|
||||
const data = JSON.parse(input);
|
||||
return bson.serialize(data).buffer;
|
||||
const result = serialize(data);
|
||||
return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
|
||||
} catch (err) {
|
||||
throw new OperationError(err.toString());
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ class ParseEthernetFrame extends Operation {
|
||||
const packetData = input.slice(offset);
|
||||
|
||||
if (outputFormat === "Packet data") {
|
||||
return Utils.byteArrayToChars(packetData);
|
||||
return Utils.escapeHtml(Utils.byteArrayToChars(packetData));
|
||||
} else if (outputFormat === "Packet data (hex)") {
|
||||
return toHex(packetData);
|
||||
} else if (outputFormat === "Text output") {
|
||||
|
||||
@ -138,7 +138,7 @@ class ParseIPv4Header extends Operation {
|
||||
} else if (outputFormat === "Data (hex)") {
|
||||
return toHex(data);
|
||||
} else if (outputFormat === "Data (raw)") {
|
||||
return Utils.byteArrayToChars(data);
|
||||
return Utils.escapeHtml(Utils.byteArrayToChars(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import BSON from "bson";
|
||||
import { ObjectId } from "bson";
|
||||
|
||||
/**
|
||||
* Parse ObjectID timestamp operation
|
||||
@ -35,7 +35,7 @@ class ParseObjectIDTimestamp extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
try {
|
||||
const objectId = new BSON.ObjectID(input);
|
||||
const objectId = new ObjectId(input);
|
||||
return objectId.getTimestamp().toISOString();
|
||||
} catch (err) {
|
||||
throw new OperationError(err);
|
||||
|
||||
@ -15,6 +15,20 @@ import Utils from "../Utils.mjs";
|
||||
const d3 = d3temp.default ? d3temp.default : d3temp;
|
||||
const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
|
||||
|
||||
/**
|
||||
* Removes D3's internal bound data from a nodom tree before serialization.
|
||||
* nodom serializes enumerable expando properties such as __data__ as attributes,
|
||||
* so leaving them on attacker-controlled values can create executable markup.
|
||||
*
|
||||
* @param {Object} node
|
||||
*/
|
||||
function clearD3BoundData(node) {
|
||||
delete node.__data__;
|
||||
|
||||
if (!node.childNodes) return;
|
||||
node.childNodes.forEach(clearD3BoundData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Series chart operation
|
||||
*/
|
||||
@ -222,6 +236,8 @@ class SeriesChart extends Operation {
|
||||
.text(serie.name);
|
||||
});
|
||||
|
||||
clearD3BoundData(svg.node());
|
||||
|
||||
return svg._groups[0][0].outerHTML;
|
||||
}
|
||||
|
||||
|
||||
@ -42,6 +42,14 @@ class App {
|
||||
this.progress = 0;
|
||||
this.ingId = 0;
|
||||
|
||||
// stateChangeId increments on every statechange dispatch; bakeStateId records
|
||||
// the stateChangeId captured when the most recent bake started. autoBake uses
|
||||
// these to decide whether the output is genuinely stale, so a debounced
|
||||
// autoBake firing shortly after a manual bake doesn't clobber the bake's
|
||||
// hideStaleIndicator with a redundant showStaleIndicator.
|
||||
this.stateChangeId = 0;
|
||||
this.bakeStateId = -1;
|
||||
|
||||
this.appLoaded = false;
|
||||
this.workerLoaded = false;
|
||||
this.waitersLoaded = false;
|
||||
@ -136,6 +144,9 @@ class App {
|
||||
bake(step=false) {
|
||||
if (this.baking) return;
|
||||
|
||||
// Record which state version this bake is covering.
|
||||
this.bakeStateId = this.stateChangeId;
|
||||
|
||||
// Reset attemptHighlight flag
|
||||
this.options.attemptHighlight = true;
|
||||
|
||||
@ -166,7 +177,10 @@ class App {
|
||||
nums: [this.manager.tabs.getActiveTab("input")],
|
||||
step: false
|
||||
});
|
||||
} else {
|
||||
} else if (this.bakeStateId < this.stateChangeId) {
|
||||
// Only show stale-indicator if the most recent bake didn't cover the
|
||||
// current state. Without this guard, a debounced autoBake firing after
|
||||
// a manual bake completed would re-show the indicator on fresh output.
|
||||
this.manager.controls.showStaleIndicator();
|
||||
}
|
||||
}
|
||||
@ -768,6 +782,10 @@ class App {
|
||||
* @param {event} e
|
||||
*/
|
||||
stateChange(e) {
|
||||
// Bump the state-change counter synchronously so a manual bake invoked between
|
||||
// here and the debounced autoBake firing can record it via bakeStateId.
|
||||
this.stateChangeId++;
|
||||
|
||||
debounce(function() {
|
||||
this.progress = 0;
|
||||
this.autoBake();
|
||||
|
||||
@ -868,15 +868,15 @@
|
||||
Be aware that the standalone version will never update itself, meaning it will not receive bug fixes or new features until you re-download newer versions manually.
|
||||
</p>
|
||||
|
||||
<h6>CyberChef v<%= htmlWebpackPlugin.options.version %></h6>
|
||||
<h6>CyberChef <%= htmlWebpackPlugin.options.version %></h6>
|
||||
<ul>
|
||||
<li>Build time: <%= htmlWebpackPlugin.options.compileTime %></li>
|
||||
<li>The changelog for this version can be viewed <a href="https://github.com/gchq/CyberChef/blob/v<%= htmlWebpackPlugin.options.version %>/CHANGELOG.md">here</a></li>
|
||||
<li>The changelog for this version can be viewed <a href="https://github.com/gchq/CyberChef/blob/v<%= htmlWebpackPlugin.options.latestReleaseVersion %>/CHANGELOG.md">here</a></li>
|
||||
<li>© Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %></li>
|
||||
<li>Released under the Apache Licence, Version 2.0</li>
|
||||
<li>SHA256 hash: DOWNLOAD_HASH_PLACEHOLDER</li>
|
||||
</ul>
|
||||
<a href="CyberChef_v<%= htmlWebpackPlugin.options.version %>.zip" download class="btn btn-outline-primary">Download ZIP file</a>
|
||||
<a href="<%= htmlWebpackPlugin.options.downloadZipFilename %>" download class="btn btn-outline-primary">Download ZIP file</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Ok</button>
|
||||
|
||||
@ -51,14 +51,17 @@ function setInput(browser, input, type=true) {
|
||||
*/
|
||||
function bake(browser) {
|
||||
browser
|
||||
// Let any pending debounced inputChange/stateChange (~20ms each) fire so the
|
||||
// worker has the latest input buffer before we ask it to bake.
|
||||
.pause(50)
|
||||
// Ensure we're not currently busy
|
||||
.waitForElementNotVisible("#output-loader", 5000)
|
||||
.waitForElementNotVisible("#output-loader", 10000)
|
||||
.expect.element("#bake span").text.to.equal("BAKE!");
|
||||
|
||||
browser
|
||||
.click("#bake")
|
||||
.waitForElementNotVisible("#stale-indicator", 5000)
|
||||
.waitForElementNotVisible("#output-loader", 5000);
|
||||
.waitForElementNotVisible("#stale-indicator", 10000)
|
||||
.waitForElementNotVisible("#output-loader", 10000);
|
||||
}
|
||||
|
||||
/** @function
|
||||
|
||||
@ -126,6 +126,7 @@ import "./tests/NormaliseUnicode.mjs";
|
||||
import "./tests/NTLM.mjs";
|
||||
import "./tests/OTP.mjs";
|
||||
import "./tests/ParseEthernetFrame.mjs";
|
||||
import "./tests/ParseIPv4Header.mjs";
|
||||
import "./tests/ParseIPRange.mjs";
|
||||
import "./tests/ParseObjectIDTimestamp.mjs";
|
||||
import "./tests/ParseQRCode.mjs";
|
||||
|
||||
@ -41,6 +41,17 @@ TestRegister.addTests([
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Series chart escapes x-axis values in serialized SVG",
|
||||
input: `s,x"><script>globalThis.seriesChartInjected=1</script><g a=",1`,
|
||||
unexpectedMatch: /<script>|__data__=/,
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Series chart",
|
||||
"args": ["Line feed", "Comma", "", 1, "red"]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Heatmap chart",
|
||||
input: "100 100\n200 200\n300 300\n400 400\n500 500",
|
||||
|
||||
@ -41,5 +41,16 @@ TestRegister.addTests([
|
||||
"args": ["Hex", "Text output"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Parse Ethernet frame escapes packet data HTML",
|
||||
input: "000000000000ffffffffffff08003c696d67207372633d78206f6e6572726f723d616c6572742831293e3c7363726970743e616c6572742832293c2f7363726970743e",
|
||||
expectedOutput: "<img src=x onerror=alert(1)><script>alert(2)</script>",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse Ethernet frame",
|
||||
"args": ["Hex", "Packet data"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
23
tests/operations/tests/ParseIPv4Header.mjs
Normal file
23
tests/operations/tests/ParseIPv4Header.mjs
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Parse IPv4 header tests.
|
||||
*
|
||||
* @author C85297 [95289555+C85297@users.noreply.github.com]
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Parse IPv4 header: Correctly formatted HTML output",
|
||||
input: "45 00 00 3c 1c 46 40 00 40 06 b1 e6 c0 a8 00 01 c0 a8 00 02 3c 73 63 72 69 70 74 3e 61 6c 65 72 74 28 31 33 33 37 29 3c 2f 73 63 72 69 70 74 3e",
|
||||
expectedOutput: "<script>alert(1337)</script>",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Parse IPv4 header",
|
||||
args: ["Hex", "Data (raw)"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user