fix(ShowBase64Offsets): escape staticSection on showVariable=false (#2344)
Closes #2344. `ShowBase64Offsets` writes the per-offset `staticSection` directly into HTML output. On the highlighted branches (`showVariable=true`) the static text was wrapped in an attribute-escaped tooltip and was fine, but on the `showVariable=false` branches at lines 98, 128, and 158 the raw Base64 string was assigned back to the offset variable and emitted unescaped. Combined with the explicit `<script>` eval in OutputWaiter.mjs (line 373), a crafted `alphabet` argument whose toBase64 image starts with `<script>...` produced a working XSS payload in the rendered output. The reporter's repro: https://gchq.github.io/CyberChef/#recipe=Show_Base64_offsets('%3Cscript%3Eale(1)/.ABCDEFGHIJKLMNOPQRSTUVWXYZbdfghjkmnoquvwxyz023456',false)&input=ABCDEFGHIJKDGLMNAOBCDEFGHPPP Fix: route all three `if (!showVariable)` branches through `Utils.escapeHtml`, mirroring what the highlighted branches already do for the surrounding tooltip values. The visible string is the same; the dangerous-character shape is sanitised. Tests - New `tests/operations/tests/ShowBase64Offsets.mjs` registered in `tests/operations/index.mjs`: - 'HTML escapes static output (#2344)' — alphabet whose toBase64 image starts with `<script>` must not appear verbatim in the output; the encoded form `<script>` should appear instead. - 'benign alphabet still renders Offset 0' — sanity that the fix is escape-only and the happy-path output still renders. I couldn't run the test suite locally because the CyberChef test runner crashes on Node 25 trying to fetch the argon2-browser WASM asset (unrelated infrastructure issue); the tests follow the same shape as the surrounding suite (BitwiseOp.mjs etc.) and CI on ubuntu-latest with the project's pinned Node will exercise them. Signed-off-by: SAY-5 <say.apm35@gmail.com>
This commit is contained in:
parent
864afa85aa
commit
109f5064cc
@ -95,7 +95,11 @@ class ShowBase64Offsets extends Operation {
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset0 = staticSection;
|
||||
// staticSection is derived from the input via toBase64 with a
|
||||
// user-controlled alphabet; without escaping, a crafted alphabet
|
||||
// (e.g. one beginning "<script>...") could land directly in the
|
||||
// HTML output (#2344). Escape before reuse on this branch.
|
||||
offset0 = Utils.escapeHtml(staticSection);
|
||||
}
|
||||
|
||||
|
||||
@ -125,7 +129,7 @@ class ShowBase64Offsets extends Operation {
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset1 = staticSection;
|
||||
offset1 = Utils.escapeHtml(staticSection);
|
||||
}
|
||||
|
||||
// Highlight offset 2
|
||||
@ -154,7 +158,7 @@ class ShowBase64Offsets extends Operation {
|
||||
}
|
||||
|
||||
if (!showVariable) {
|
||||
offset2 = staticSection;
|
||||
offset2 = Utils.escapeHtml(staticSection);
|
||||
}
|
||||
|
||||
return (showVariable ? "Characters highlighted in <span class='hl5'>green</span> could change if the input is surrounded by more data." +
|
||||
|
||||
@ -157,6 +157,7 @@ import "./tests/SeqUtils.mjs";
|
||||
import "./tests/SetDifference.mjs";
|
||||
import "./tests/SetIntersection.mjs";
|
||||
import "./tests/SetUnion.mjs";
|
||||
import "./tests/ShowBase64Offsets.mjs";
|
||||
import "./tests/Shuffle.mjs";
|
||||
import "./tests/SIGABA.mjs";
|
||||
import "./tests/SM2.mjs";
|
||||
|
||||
57
tests/operations/tests/ShowBase64Offsets.mjs
Normal file
57
tests/operations/tests/ShowBase64Offsets.mjs
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* ShowBase64Offsets tests
|
||||
*
|
||||
* @author SAY-5
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
// Regression test for #2344. With showVariable=false the
|
||||
// staticSection (toBase64 output, alphabet-derived from a
|
||||
// user-controlled argument) was inserted into the HTML output
|
||||
// unescaped. A crafted alphabet whose Base64 image starts with
|
||||
// `<script>...` could land directly in the rendered output and
|
||||
// be eval'd by OutputWaiter.mjs. The fix runs the unescaped
|
||||
// path through Utils.escapeHtml; this test pins that the raw
|
||||
// `<script>` substring no longer appears verbatim and instead
|
||||
// appears as the HTML-encoded `<script>`.
|
||||
name: "Show Base64 offsets: HTML escapes static output (#2344)",
|
||||
input: "ABCDEFGHIJKDGLMNAOBCDEFGHPPP",
|
||||
// alphabet chosen so toBase64(input, alphabet) starts with
|
||||
// "<script>"; recipe also passes showVariable=false so the
|
||||
// staticSection is the bare offset0/1/2 reused as raw text.
|
||||
expectedMatch: /<script>|<\/script>/,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Show Base64 offsets",
|
||||
args: [
|
||||
"<script>ale(1)/.ABCDEFGHIJKLMNOPQRSTUVWXYZbdfghjkmnoquvwxyz023456",
|
||||
false,
|
||||
"Raw",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// Sanity: the showVariable=false output for a benign alphabet
|
||||
// still produces something that contains the expected offsets
|
||||
// header. This guards against the fix accidentally breaking
|
||||
// the happy path.
|
||||
name: "Show Base64 offsets: benign alphabet still renders Offset 0",
|
||||
input: "Hello, world!",
|
||||
expectedMatch: /Offset 0:/,
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Show Base64 offsets",
|
||||
args: [
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
false,
|
||||
"Raw",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user