diff --git a/Dockerfile b/Dockerfile index d63a8ca3b..ba605fd71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,7 @@ RUN npm run build ######################################### # We are using Github Actions: redhat-actions/buildah-build@v2 which needs manual selection of arch in base image # Remove TARGETARCH if docker buildx is supported in the CI release as --platform=$TARGETPLATFORM will be automatically set -ARG TARGETARCH ARG TARGETPLATFORM -FROM ${TARGETARCH}/nginx:stable-alpine AS cyberchef +FROM --platform=${TARGETPLATFORM} nginx:stable-alpine AS cyberchef COPY --from=builder /app/build/prod /usr/share/nginx/html/ diff --git a/README.md b/README.md index 5549bda2a..89f0371d5 100755 --- a/README.md +++ b/README.md @@ -20,21 +20,36 @@ Cryptographic operations in CyberChef should not be relied upon to provide secur [A live demo can be found here][1] - have fun! -## Containers +## Running Locally with Docker -If you would like to try out CyberChef locally you can either build it yourself: +**Prerequisites** +- [Docker](hhttps://www.docker.com/products/docker-desktop/) + - Docker Desktop must be open and running on your machine + + +#### Option 1: Build the Docker Image Yourself + +1. Build the docker image ```bash docker build --tag cyberchef --ulimit nofile=10000 . +``` +2. Run the docker container +```bash docker run -it -p 8080:80 cyberchef ``` +3. Navigate to `http://localhost:8080` in your browser -Or you can use our image directly: +#### Option 2: Use the pre-built Docker Image + +If you prefer to skip the build process, you can use the pre-built image ```bash docker run -it -p 8080:80 ghcr.io/gchq/cyberchef:latest ``` +Just like before, navigate to `http://localhost:8080` in your browser. + This image is built and published through our [GitHub Workflows](.github/workflows/releases.yml) ## How it works diff --git a/package-lock.json b/package-lock.json index 8dd38b7a0..b374df4bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,8 @@ "file-saver": "^2.0.5", "flat": "^6.0.1", "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", "highlight.js": "^11.9.0", "ieee754": "^1.2.1", "jimp": "^0.22.12", @@ -95,6 +97,7 @@ "ua-parser-js": "^1.0.38", "unorm": "^1.6.0", "utf8": "^3.0.0", + "uuid": "^11.1.0", "vkbeautify": "^0.99.3", "xpath": "0.0.34", "xregexp": "^5.1.1", @@ -10843,6 +10846,27 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -10941,6 +10965,11 @@ "node": ">= 0.10" } }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==" + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -13663,7 +13692,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/netmask": { @@ -13885,6 +13913,15 @@ "node": ">=8" } }, + "node_modules/nightwatch/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/nightwatch/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -16828,6 +16865,15 @@ "node": ">=0.8.0" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -16868,7 +16914,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -18115,13 +18160,15 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8flags": { @@ -18886,6 +18933,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/worker-loader": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", diff --git a/package.json b/package.json index feb18bc8d..9191ab6f0 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,8 @@ "file-saver": "^2.0.5", "flat": "^6.0.1", "geodesy": "1.1.3", + "handlebars": "^4.7.8", + "hash-wasm": "^4.12.0", "highlight.js": "^11.9.0", "ieee754": "^1.2.1", "jimp": "^0.22.12", @@ -181,6 +183,7 @@ "ua-parser-js": "^1.0.38", "unorm": "^1.6.0", "utf8": "^3.0.0", + "uuid": "^11.1.0", "vkbeautify": "^0.99.3", "xpath": "0.0.34", "xregexp": "^5.1.1", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 0226e33bb..d26ffaa5c 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -376,7 +376,8 @@ "Extract EXIF", "Extract ID3", "Extract Files", - "RAKE" + "RAKE", + "Template" ] }, { @@ -426,6 +427,7 @@ "Snefru", "BLAKE2b", "BLAKE2s", + "BLAKE3", "GOST Hash", "Streebog", "SSDEEP", @@ -550,6 +552,7 @@ "Pseudo-Random Number Generator", "Generate De Bruijn Sequence", "Generate UUID", + "Analyse UUID", "Generate TOTP", "Generate HOTP", "Generate QR Code", diff --git a/src/core/operations/AnalyseUUID.mjs b/src/core/operations/AnalyseUUID.mjs new file mode 100644 index 000000000..b35060179 --- /dev/null +++ b/src/core/operations/AnalyseUUID.mjs @@ -0,0 +1,48 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import * as uuid from "uuid"; + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Analyse UUID operation + */ +class AnalyseUUID extends Operation { + + /** + * AnalyseUUID constructor + */ + constructor() { + super(); + + this.name = "Analyse UUID"; + this.module = "Crypto"; + this.description = "Tries to determine information about a given UUID and suggests which version may have been used to generate it"; + this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + try { + const uuidVersion = uuid.version(input); + return "UUID version: " + uuidVersion; + } catch (error) { + throw new OperationError("Invalid UUID"); + } + } + +} + +export default AnalyseUUID; diff --git a/src/core/operations/BLAKE3.mjs b/src/core/operations/BLAKE3.mjs new file mode 100644 index 000000000..0f686120a --- /dev/null +++ b/src/core/operations/BLAKE3.mjs @@ -0,0 +1,58 @@ +/** + * @author xumptex [xumptex@outlook.fr] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { blake3 } from "hash-wasm"; +/** + * BLAKE3 operation + */ +class BLAKE3 extends Operation { + + /** + * BLAKE3 constructor + */ + constructor() { + super(); + + this.name = "BLAKE3"; + this.module = "Hashing"; + this.description = "Hashes the input using BLAKE3 (UTF-8 encoded), with an optional key (also UTF-8), and outputs the result in hexadecimal format."; + this.infoURL = "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Size (bytes)", + "type": "number" + }, { + "name": "Key", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = args[1]; + const size = args[0]; + // Check if the user want a key hash or not + if (key === "") { + return blake3(input, size*8); + } if (key.length !== 32) { + throw new OperationError("The key must be exactly 32 bytes long"); + } + return blake3(input, size*8, key); + } + +} + +export default BLAKE3; diff --git a/src/core/operations/ExtractEmailAddresses.mjs b/src/core/operations/ExtractEmailAddresses.mjs index f50e1aaf5..34b838ab3 100644 --- a/src/core/operations/ExtractEmailAddresses.mjs +++ b/src/core/operations/ExtractEmailAddresses.mjs @@ -51,7 +51,7 @@ class ExtractEmailAddresses extends Operation { run(input, args) { const [displayTotal, sort, unique] = args, // email regex from: https://www.regextester.com/98066 - regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig; + regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig; const results = search( input, diff --git a/src/core/operations/ExtractIPAddresses.mjs b/src/core/operations/ExtractIPAddresses.mjs index 97b52478d..b74ec8fe2 100644 --- a/src/core/operations/ExtractIPAddresses.mjs +++ b/src/core/operations/ExtractIPAddresses.mjs @@ -21,7 +21,7 @@ class ExtractIPAddresses extends Operation { this.name = "Extract IP addresses"; this.module = "Regex"; - this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 710.65.0.456, this will match 10.65.0.45 so always check the original input!"; + this.description = "Extracts all IPv4 and IPv6 addresses.

Warning: Given a string 1.2.3.4.5.6.7.8, this will match 1.2.3.4 and 5.6.7.8 so always check the original input!"; this.inputType = "string"; this.outputType = "string"; this.args = [ @@ -65,7 +65,21 @@ class ExtractIPAddresses extends Operation { */ run(input, args) { const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args, - ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?", + + // IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused: + ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)", + ipv4OctalByte = "(?:0[1-3]?[0-7]{1,2})", + + // Look behind and ahead will be used to exclude matches with additional decimal digits left and right of IP address + lookBehind = "(?
A version 4 UUID relies on random numbers, in this case generated using window.crypto if available and falling back to Math.random if not."; + this.description = + "Generates an RFC 9562 (formerly RFC 4122) compliant Universally Unique Identifier (UUID), " + + "also known as a Globally Unique Identifier (GUID).
" + + "
" + + "We currently support generating the following UUID versions:
" + + "" + + "UUIDs are generated using the uuid package.
"; this.infoURL = "https://wikipedia.org/wiki/Universally_unique_identifier"; this.inputType = "string"; this.outputType = "string"; - this.args = []; + this.args = [ + { + name: "Version", + hint: "UUID version", + type: "option", + value: ["v1", "v3", "v4", "v5", "v6", "v7"], + defaultIndex: 2, + }, + { + name: "Namespace", + hint: "UUID namespace (UUID; valid for v3 and v5)", + type: "string", + value: "1b671a64-40d5-491e-99b0-da01ff1f3341" + } + ]; } /** @@ -33,16 +60,17 @@ class GenerateUUID extends Operation { * @returns {string} */ run(input, args) { - const buf = new Uint32Array(4).map(() => { - return crypto.randomBytes(4).readUInt32BE(0, true); - }); - let i = 0; - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - const r = (buf[i >> 3] >> ((i % 8) * 4)) & 0xf, - v = c === "x" ? r : (r & 0x3 | 0x8); - i++; - return v.toString(16); - }); + const [version, namespace] = args; + const hasDesiredVersion = typeof uuid[version] === "function"; + if (!hasDesiredVersion) throw new OperationError("Invalid UUID version"); + + const requiresNamespace = ["v3", "v5"].includes(version); + if (!requiresNamespace) return uuid[version](); + + const hasValidNamespace = typeof namespace === "string" && uuid.validate(namespace); + if (!hasValidNamespace) throw new OperationError("Invalid UUID namespace"); + + return uuid[version](input, namespace); } } diff --git a/src/core/operations/RailFenceCipherDecode.mjs b/src/core/operations/RailFenceCipherDecode.mjs index be54ee124..39795f215 100644 --- a/src/core/operations/RailFenceCipherDecode.mjs +++ b/src/core/operations/RailFenceCipherDecode.mjs @@ -72,7 +72,7 @@ class RailFenceCipherDecode extends Operation { } } - return plaintext.join("").trim(); + return plaintext.join(""); } } diff --git a/src/core/operations/RailFenceCipherEncode.mjs b/src/core/operations/RailFenceCipherEncode.mjs index 03651f857..89eddde75 100644 --- a/src/core/operations/RailFenceCipherEncode.mjs +++ b/src/core/operations/RailFenceCipherEncode.mjs @@ -66,7 +66,7 @@ class RailFenceCipherEncode extends Operation { rows[rowIdx] += plaintext[pos]; } - return rows.join("").trim(); + return rows.join(""); } } diff --git a/src/core/operations/ShowOnMap.mjs b/src/core/operations/ShowOnMap.mjs index c2ac1c6e3..d75c2aa6a 100644 --- a/src/core/operations/ShowOnMap.mjs +++ b/src/core/operations/ShowOnMap.mjs @@ -1,6 +1,7 @@ /** * @author j433866 [j433866@gmail.com] - * @copyright Crown Copyright 2019 + * @author 0xff1ce [github.com/0xff1ce] + * @copyright Crown Copyright 2024 * @license Apache-2.0 */ @@ -22,7 +23,7 @@ class ShowOnMap extends Operation { this.name = "Show on map"; this.module = "Hashing"; this.description = "Displays co-ordinates on a slippy map.

Co-ordinates will be converted to decimal degrees before being shown on the map.

Supported formats:
This operation will not work offline."; - this.infoURL = "https://foundation.wikimedia.org/wiki/Maps_Terms_of_Use"; + this.infoURL = "https://osmfoundation.org/wiki/Terms_of_Use"; this.inputType = "string"; this.outputType = "string"; this.presentType = "html"; @@ -85,10 +86,10 @@ class ShowOnMap extends Operation { data = "0, 0"; } const zoomLevel = args[0]; - const tileUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png", - tileAttribution = "
Wikimedia maps | © OpenStreetMap contributors", - leafletUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.js", - leafletCssUrl = "https://unpkg.com/leaflet@1.5.0/dist/leaflet.css"; + const tileUrl = "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + tileAttribution = "© OpenStreetMap contributors", + leafletUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js", + leafletCssUrl = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"; return `