2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-29 14:43:43 +00:00

Compare commits

..

25 Commits

Author SHA1 Message Date
n1474335
4563c86acd 9.24.5 2021-02-09 14:24:08 +00:00
n1474335
0a59f8068e Merge branch 'aussieklutz-master' 2021-02-09 14:23:18 +00:00
n1474335
24548e3a48 Tidied up JWT tests 2021-02-09 14:23:02 +00:00
n1474335
f4784d49e7 Merge branch 'master' of https://github.com/aussieklutz/CyberChef into aussieklutz-master 2021-02-09 14:16:36 +00:00
n1474335
14d5069c6e Merge branch 'mt3571-1073-jwt-verify' 2021-02-09 14:15:12 +00:00
n1474335
9fdd55c5c6 Tidied up JWT ops 2021-02-09 14:14:59 +00:00
n1474335
5bc523aeff Merge branch '1073-jwt-verify' of https://github.com/mt3571/CyberChef into mt3571-1073-jwt-verify 2021-02-09 14:02:21 +00:00
n1474335
3ae2e2e2c8 Fixed highlighting of op names where only the description has hit 2021-02-09 11:50:20 +00:00
n1474335
83e49da7f6 Fixed description hiighlighting issue 2021-02-09 11:37:25 +00:00
n1474335
fe6df8778f Fixed year in CHANGELOG records. Closes #1168 2021-02-09 11:26:00 +00:00
aussieklutz
d5a0adea0c Update JWTVerify.mjs 2021-02-06 18:35:46 +10:00
aussieklutz
1bcb8e433d Update JWTVerify.mjs 2021-02-06 18:10:54 +10:00
aussieklutz
fa05cf1d78 Update JWTVerify.mjs
Enabled ESRSA verification.
2021-02-06 17:58:49 +10:00
aussieklutz
63dff0d34d Update JWTVerify.mjs
Enabled validation of ECSHA256 JWT tokens in the tests
2021-02-06 17:55:44 +10:00
aussieklutz
e228b197f9 Update JWTVerify.mjs 2021-02-06 17:45:42 +10:00
aussieklutz
4bbeb6caa3 Update JWTVerify.mjs
Add expectation for working RSASHA256 test, and comment out unused privatekey.
2021-02-06 17:42:42 +10:00
aussieklutz
139d25dff9 Update JWTVerify.mjs
Update RSASHA256 test with the public key derived from the pre-existing private key, and expect a working testcase.
2021-02-06 17:40:04 +10:00
aussieklutz
6984258404 Update JWTVerify.mjs
Enable verification of RSASHA256 and 512 tokens
2021-02-06 17:27:54 +10:00
n1474335
ba66fd6546 Fixed recursive matching arguments 2021-02-05 19:04:27 +00:00
n1474335
47bbefd81f Fixed recursive scoring results in fuzzy match lib 2021-02-05 18:24:15 +00:00
n1474335
50f796049c Fixed search test 2021-02-05 18:07:20 +00:00
n1474335
618da545b1 9.24.4 2021-02-05 17:55:10 +00:00
n1474335
21236f1938 Added fuzzy search for operations 2021-02-05 17:54:57 +00:00
mt3571
887ea0cf06 Changed an incorrect name 2020-12-01 13:49:34 +00:00
mt3571
3e0525ee9e Added in a new file to store the list of JWT algorithms that can be used, as this error was occurring due to a mismatch between what you could sign and what you could verify 2020-12-01 13:38:01 +00:00
11 changed files with 327 additions and 58 deletions

View File

@@ -13,10 +13,10 @@ All major and minor version changes will be documented in this file. Details of
## Details
### [9.24.0] - 2020-02-02
### [9.24.0] - 2021-02-02
- 'SM3' hashing function added along with more configuration options for other hashing operations [@n1073645] [@n1474335] | [#1022]
### [9.23.0] - 2020-02-01
### [9.23.0] - 2021-02-01
- Various RSA operations added to encrypt, decrypt, sign, verify and generate keys [@mattnotmitt] [@GCHQ77703] | [#652]
### [9.22.0] - 2021-02-01

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.24.3",
"version": "9.24.5",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.24.3",
"version": "9.24.5",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",

View File

@@ -0,0 +1,219 @@
/**
* LICENSE
*
* This software is dual-licensed to the public domain and under the following
* license: you are granted a perpetual, irrevocable license to copy, modify,
* publish, and distribute this file as you see fit.
*
* VERSION
* 0.1.0 (2016-03-28) Initial release
*
* AUTHOR
* Forrest Smith
*
* CONTRIBUTORS
* J<>rgen Tjern<72> - async helper
* Anurag Awasthi - updated to 0.2.0
*/
const SEQUENTIAL_BONUS = 15; // bonus for adjacent matches
const SEPARATOR_BONUS = 30; // bonus if match occurs after a separator
const CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower
const FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched
const LEADING_LETTER_PENALTY = -5; // penalty applied for every letter in str before the first match
const MAX_LEADING_LETTER_PENALTY = -15; // maximum penalty for leading letters
const UNMATCHED_LETTER_PENALTY = -1;
/**
* Does a fuzzy search to find pattern inside a string.
* @param {*} pattern string pattern to search for
* @param {*} str string string which is being searched
* @returns [boolean, number] a boolean which tells if pattern was
* found or not and a search score
*/
export function fuzzyMatch(pattern, str) {
const recursionCount = 0;
const recursionLimit = 10;
const matches = [];
const maxMatches = 256;
return fuzzyMatchRecursive(
pattern,
str,
0 /* patternCurIndex */,
0 /* strCurrIndex */,
null /* srcMatces */,
matches,
maxMatches,
0 /* nextMatch */,
recursionCount,
recursionLimit
);
}
/**
* Recursive helper function
*/
function fuzzyMatchRecursive(
pattern,
str,
patternCurIndex,
strCurrIndex,
srcMatches,
matches,
maxMatches,
nextMatch,
recursionCount,
recursionLimit
) {
let outScore = 0;
// Return if recursion limit is reached.
if (++recursionCount >= recursionLimit) {
return [false, outScore, []];
}
// Return if we reached ends of strings.
if (patternCurIndex === pattern.length || strCurrIndex === str.length) {
return [false, outScore, []];
}
// Recursion params
let recursiveMatch = false;
let bestRecursiveMatches = [];
let bestRecursiveScore = 0;
// Loop through pattern and str looking for a match.
let firstMatch = true;
while (patternCurIndex < pattern.length && strCurrIndex < str.length) {
// Match found.
if (
pattern[patternCurIndex].toLowerCase() === str[strCurrIndex].toLowerCase()
) {
if (nextMatch >= maxMatches) {
return [false, outScore, []];
}
if (firstMatch && srcMatches) {
matches = [...srcMatches];
firstMatch = false;
}
const [matched, recursiveScore, recursiveMatches] = fuzzyMatchRecursive(
pattern,
str,
patternCurIndex,
strCurrIndex + 1,
matches,
recursiveMatches,
maxMatches,
nextMatch,
recursionCount,
recursionLimit
);
if (matched) {
// Pick best recursive score.
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
bestRecursiveMatches = [...recursiveMatches];
bestRecursiveScore = recursiveScore;
}
recursiveMatch = true;
}
matches[nextMatch++] = strCurrIndex;
++patternCurIndex;
}
++strCurrIndex;
}
const matched = patternCurIndex === pattern.length;
if (matched) {
outScore = 100;
// Apply leading letter penalty
let penalty = LEADING_LETTER_PENALTY * matches[0];
penalty =
penalty < MAX_LEADING_LETTER_PENALTY ?
MAX_LEADING_LETTER_PENALTY :
penalty;
outScore += penalty;
// Apply unmatched penalty
const unmatched = str.length - nextMatch;
outScore += UNMATCHED_LETTER_PENALTY * unmatched;
// Apply ordering bonuses
for (let i = 0; i < nextMatch; i++) {
const currIdx = matches[i];
if (i > 0) {
const prevIdx = matches[i - 1];
if (currIdx === prevIdx + 1) {
outScore += SEQUENTIAL_BONUS;
}
}
// Check for bonuses based on neighbor character value.
if (currIdx > 0) {
// Camel case
const neighbor = str[currIdx - 1];
const curr = str[currIdx];
if (
neighbor !== neighbor.toUpperCase() &&
curr !== curr.toLowerCase()
) {
outScore += CAMEL_BONUS;
}
const isNeighbourSeparator = neighbor === "_" || neighbor === " ";
if (isNeighbourSeparator) {
outScore += SEPARATOR_BONUS;
}
} else {
// First letter
outScore += FIRST_LETTER_BONUS;
}
}
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
// Recursive score is better than "this"
matches = bestRecursiveMatches;
outScore = bestRecursiveScore;
return [true, outScore, matches];
} else if (matched) {
// "this" score is better than recursive
return [true, outScore, matches];
} else {
return [false, outScore, matches];
}
}
return [false, outScore, matches];
}
/**
* Turns a list of match indexes into a list of match ranges
*
* @author n1474335 [n1474335@gmail.com]
* @param [number] matches
* @returns [[number]]
*/
export function calcMatchRanges(matches) {
const ranges = [];
let start = matches[0],
curr = start;
matches.forEach(m => {
if (m === curr || m === curr + 1) curr = m;
else {
ranges.push([start, curr - start + 1]);
start = m;
curr = m;
}
});
ranges.push([start, curr - start + 1]);
return ranges;
}

24
src/core/lib/JWT.mjs Normal file
View File

@@ -0,0 +1,24 @@
/**
* JWT resources
*
* @author mt3571 [mt3571@protonmail.com]
* @copyright Crown Copyright 2020
* @license Apache-2.0
*/
/**
* List of the JWT algorithms that can be used
*/
export const JWT_ALGORITHMS = [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"None"
];

View File

@@ -3,10 +3,11 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import jwt from "jsonwebtoken";
import OperationError from "../errors/OperationError.mjs";
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
/**
* JWT Sign operation
@@ -34,18 +35,7 @@ class JWTSign extends Operation {
{
name: "Signing algorithm",
type: "option",
value: [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"None"
]
value: JWT_ALGORITHMS
}
];
}

View File

@@ -3,10 +3,11 @@
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import jwt from "jsonwebtoken";
import OperationError from "../errors/OperationError.mjs";
import {JWT_ALGORITHMS} from "../lib/JWT.mjs";
/**
* JWT Verify operation
@@ -27,7 +28,7 @@ class JWTVerify extends Operation {
this.outputType = "JSON";
this.args = [
{
name: "Private/Secret Key",
name: "Public/Secret Key",
type: "text",
value: "secret"
},
@@ -41,14 +42,11 @@ class JWTVerify extends Operation {
*/
run(input, args) {
const [key] = args;
const algos = JWT_ALGORITHMS;
algos[algos.indexOf("None")] = "none";
try {
const verified = jwt.verify(input, key, { algorithms: [
"HS256",
"HS384",
"HS512",
"none"
]});
const verified = jwt.verify(input, key, { algorithms: algos });
if (Object.prototype.hasOwnProperty.call(verified, "name") && verified.name === "JsonWebTokenError") {
throw new OperationError(verified.message);

View File

@@ -91,32 +91,52 @@ class HTMLOperation {
/**
* Highlights the searched string in the name and description of the operation.
* Highlights searched strings in the name and description of the operation.
*
* @param {string} searchStr
* @param {number} namePos - The position of the search string in the operation name
* @param {number} descPos - The position of the search string in the operation description
* @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]]
* @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]]
*/
highlightSearchString(searchStr, namePos, descPos) {
if (namePos >= 0) {
this.name = this.name.slice(0, namePos) + "<b><u>" +
this.name.slice(namePos, namePos + searchStr.length) + "</u></b>" +
this.name.slice(namePos + searchStr.length);
highlightSearchStrings(nameIdxs, descIdxs) {
if (nameIdxs.length && typeof nameIdxs[0][0] === "number") {
let opName = "",
pos = 0;
nameIdxs.forEach(idxs => {
const [start, length] = idxs;
if (typeof start !== "number") return;
opName += this.name.slice(pos, start) + "<b>" +
this.name.slice(start, start + length) + "</b>";
pos = start + length;
});
opName += this.name.slice(pos, this.name.length);
this.name = opName;
}
if (this.description && descPos >= 0) {
if (this.description && descIdxs.length && descIdxs[0][0] >= 0) {
// Find HTML tag offsets
const re = /<[^>]+>/g;
let match;
while ((match = re.exec(this.description))) {
// If the search string occurs within an HTML tag, return without highlighting it.
if (descPos >= match.index && descPos <= (match.index + match[0].length))
return;
const inHTMLTag = descIdxs.reduce((acc, idxs) => {
const start = idxs[0];
return start >= match.index && start <= (match.index + match[0].length);
}, false);
if (inHTMLTag) return;
}
this.description = this.description.slice(0, descPos) + "<b><u>" +
this.description.slice(descPos, descPos + searchStr.length) + "</u></b>" +
this.description.slice(descPos + searchStr.length);
let desc = "",
pos = 0;
descIdxs.forEach(idxs => {
const [start, length] = idxs;
desc += this.description.slice(pos, start) + "<b><u>" +
this.description.slice(start, start + length) + "</u></b>";
pos = start + length;
});
desc += this.description.slice(pos, this.description.length);
this.description = desc;
}
}

View File

@@ -6,6 +6,7 @@
import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzySearch.mjs";
/**
@@ -108,28 +109,33 @@ class OperationsWaiter {
const matchedOps = [];
const matchedDescs = [];
const searchStr = inStr.toLowerCase();
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
const namePos = opName.toLowerCase().indexOf(searchStr);
const descPos = op.description.toLowerCase().indexOf(searchStr);
if (namePos >= 0 || descPos >= 0) {
// Match op name using fuzzy match
const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName);
// Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
if (nameMatch || descPos >= 0) {
const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
if (highlight) {
operation.highlightSearchString(searchStr, namePos, descPos);
operation.highlightSearchStrings(calcMatchRanges(idxs) || [], [[descPos, inStr.length]]);
}
if (namePos < 0) {
matchedOps.push(operation);
if (nameMatch) {
matchedOps.push([operation, score]);
} else {
matchedDescs.push(operation);
}
}
}
return matchedDescs.concat(matchedOps);
// Sort matched operations based on fuzzy score
matchedOps.sort((a, b) => b[1] - a[1]);
return matchedOps.map(a => a[0]).concat(matchedDescs);
}

View File

@@ -218,7 +218,7 @@ module.exports = {
.clearValue("#search")
.setValue("#search", "md5")
.useXpath()
.waitForElementVisible("//ul[@id='search-results']//u[text()='MD5']", 1000);
.waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000);
},
after: browser => {

View File

@@ -14,10 +14,9 @@ const outputObject = JSON.stringify({
iat: 1
}, null, 4);
const invalidAlgorithm = "JsonWebTokenError: invalid algorithm";
const hsKey = "secret_cat";
const rsKey = `-----BEGIN RSA PRIVATE KEY-----
/* Retaining private key as a comment
const rsPriv = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
@@ -32,11 +31,24 @@ fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
-----END RSA PRIVATE KEY-----`;
const esKey = `-----BEGIN PRIVATE KEY-----
*/
const rsPub = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
o2kQ+X5xK9cipRgEKwIDAQAB
-----END PUBLIC KEY-----`;
/* Retaining private key as a comment
const esPriv = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----`;
*/
const esPub = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----`;
TestRegister.addTests([
{
@@ -53,22 +65,22 @@ TestRegister.addTests([
{
name: "JWT Verify: RS",
input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y",
expectedOutput: invalidAlgorithm,
expectedOutput: outputObject,
recipeConfig: [
{
op: "JWT Verify",
args: [rsKey],
args: [rsPub],
}
],
},
{
name: "JWT Verify: ES",
input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA",
expectedOutput: invalidAlgorithm,
expectedOutput: outputObject,
recipeConfig: [
{
op: "JWT Verify",
args: [esKey],
args: [esPub],
}
],
}