This is an automated email from the ASF dual-hosted git repository.
lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new e51163532 KNOX-3409: Validate knoxauth theme name to Harden Branding
Support (Reported by Quinn Nguyen) (#1341)
e51163532 is described below
commit e51163532f705b1370540f9ce76f68008ff6bd5b
Author: lmccay <[email protected]>
AuthorDate: Tue Aug 11 00:14:31 2026 -0400
KNOX-3409: Validate knoxauth theme name to Harden Branding Support
(Reported by Quinn Nguyen) (#1341)
* KNOX-3409: Validate knoxauth theme name to prevent DOM-based XSS
The knoxauth login page read the "theme" query parameter and wrote it into
a <link> tag via document.write() with no validation, allowing arbitrary
markup injection on the page that collects user credentials. The value was
also persisted to localStorage and replayed on later visits, so a single
malicious link kept executing on subsequent visits from a clean URL.
Theme names are now validated against ^[a-zA-Z0-9_-]{1,64}$ on every path
(URL parameter, localStorage, and the configured default), which rejects
quotes, angle brackets, dots and path separators. The stylesheet element is
built with DOM APIs instead of string concatenation, so a theme name can
never be parsed as markup. A stored value that fails validation, or that
does not resolve to an installed theme, is discarded rather than replayed.
Because the only URL the loader can produce is
styles/themes/<name>/theme.css,
the themes actually installed on the server remain the effective allowlist
and
no new configuration is required. The README and deployment guide are
updated
to describe the validation, replacing two security claims that were
inaccurate
as written.
Reported by Quinn Nguyen. Not present in any released version - the theming
feature (KNOX-3283) has only ever been on master.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* KNOX-3409: Discard an unresolvable theme preference on the same visit
Addresses review feedback. The cleanup flag tracked whether the theme had
been read from localStorage rather than whether it is currently held there.
A theme supplied via ?theme= is persisted immediately, so it is equally
eligible for cleanup, but the flag stayed false and no onerror handler was
attached. A name that passed validation without resolving to an installed
theme was therefore saved and replayed once before being cleared.
The flag is renamed to themeIsPersisted and set after a successful
setItem, so it reflects the invariant the cleanup actually depends on.
Setting it inside the try means a failed write - localStorage disabled -
correctly leaves nothing to clean up. Behaviour for an admin-configured
theme is unchanged: a deployment error does not discard a user preference.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../resources/applications/knoxauth/app/login.html | 72 ++++++++++++++++++----
.../knoxauth/app/styles/themes/DEPLOYMENT.md | 18 +++++-
.../knoxauth/app/styles/themes/README.md | 15 ++++-
3 files changed, 88 insertions(+), 17 deletions(-)
diff --git
a/gateway-applications/src/main/resources/applications/knoxauth/app/login.html
b/gateway-applications/src/main/resources/applications/knoxauth/app/login.html
index 8c69ec9c2..6a30f6106 100644
---
a/gateway-applications/src/main/resources/applications/knoxauth/app/login.html
+++
b/gateway-applications/src/main/resources/applications/knoxauth/app/login.html
@@ -36,40 +36,86 @@
<!-- Knox Theme Loader (Inline & Blocking to prevent FOUC) -->
<script type="text/javascript">
(function() {
+ // Theme names are attacker-influenced (URL parameter and
localStorage), so
+ // every candidate is validated before it is stored or used to
build a URL.
+ // Rejecting quotes, angle brackets, dots and path separators
means the only
+ // URL this can ever produce is styles/themes/<name>/theme.css
- so the set
+ // of themes actually installed on the server is the effective
allowlist,
+ // and an unknown name simply 404s and leaves the base styles
in place.
+ var THEME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
+
+ function sanitize(candidate) {
+ return (candidate && THEME_PATTERN.test(candidate)) ?
candidate : null;
+ }
+
// Load theme based on: deployment lock > URL parameter >
localStorage > deployment default
- var urlParams = new URLSearchParams(window.location.search);
var theme;
+ // True when the theme about to load is also the value held in
localStorage,
+ // which is what makes it eligible for cleanup if the
stylesheet fails.
+ var themeIsPersisted = false;
// Check if theme is locked (admin-enforced theme)
if (typeof KNOX_THEME_LOCKED !== 'undefined' &&
KNOX_THEME_LOCKED === true) {
// Theme is locked, use deployment default only
- theme = KNOX_DEFAULT_THEME || 'default';
+ theme = sanitize(KNOX_DEFAULT_THEME) || 'default';
} else {
// Normal mode: URL parameter > localStorage > deployment
default
- theme = urlParams.get('theme');
+ var urlParams = new URLSearchParams(window.location.search);
+ var requested = sanitize(urlParams.get('theme'));
- // If theme parameter exists, save to localStorage for
persistence
- if (theme) {
+ if (requested) {
+ // Only a validated theme is persisted for future visits
try {
- localStorage.setItem('knox-auth-theme', theme);
+ localStorage.setItem('knox-auth-theme', requested);
+ themeIsPersisted = true;
} catch (e) {
// LocalStorage may be disabled, continue without
saving
}
+ theme = requested;
} else {
- // Try to load from localStorage, fallback to
deployment default
+ // Try to load from localStorage, fallback to
deployment default.
+ // Stored values are re-validated so an entry saved
before this
+ // validation existed cannot take effect, and is
discarded.
try {
- theme = localStorage.getItem('knox-auth-theme') ||
KNOX_DEFAULT_THEME || 'default';
+ var saved = localStorage.getItem('knox-auth-theme');
+ theme = sanitize(saved);
+ if (saved && !theme) {
+ localStorage.removeItem('knox-auth-theme');
+ }
+ themeIsPersisted = theme !== null;
} catch (e) {
// LocalStorage may be disabled, use deployment
default
- theme = KNOX_DEFAULT_THEME || 'default';
+ theme = null;
}
+ theme = theme || sanitize(KNOX_DEFAULT_THEME) ||
'default';
}
}
- // Load theme CSS if specified and not default
- // Use document.write to load synchronously and prevent FOUC
- if (theme && theme !== 'default') {
- document.write('<link rel="stylesheet" type="text/css"
href="styles/themes/' + theme + '/theme.css" id="knox-theme">');
+ // Load theme CSS if specified and not default.
+ // Built with DOM APIs rather than document.write so the theme
name can
+ // never be interpreted as markup; appending to head during
head parsing
+ // is still render-blocking, which is what prevents FOUC.
+ if (theme !== 'default') {
+ var link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.type = 'text/css';
+ link.id = 'knox-theme';
+ // The server is the authority on which themes exist. If
the theme held
+ // in localStorage fails to load, it is not installed here,
so forget it
+ // straight away rather than replaying it on the next
visit. A failure of
+ // an admin-configured theme is left alone - that is a
deployment problem
+ // to fix, not the user's preference to discard.
+ if (themeIsPersisted) {
+ link.onerror = function() {
+ try {
+ localStorage.removeItem('knox-auth-theme');
+ } catch (e) {
+ // LocalStorage may be disabled, nothing to
clean up
+ }
+ };
+ }
+ link.href = 'styles/themes/' + theme + '/theme.css';
+ document.head.appendChild(link);
}
})();
</script>
diff --git
a/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/DEPLOYMENT.md
b/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/DEPLOYMENT.md
index 220c3177a..e746fdd33 100644
---
a/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/DEPLOYMENT.md
+++
b/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/DEPLOYMENT.md
@@ -332,7 +332,18 @@ Organizations with branding requirements or compliance
needs should use `KNOX_TH
### Theme Name Validation
-The theme loader only loads files from `styles/themes/THEME_NAME/theme.css`.
Path traversal attacks (e.g., `?theme=../../etc/passwd`) are prevented by the
URL structure.
+The `?theme=` parameter and the saved localStorage preference are
attacker-influenceable,
+so the theme loader validates every candidate against `^[a-zA-Z0-9_-]{1,64}$`
before it is
+stored or used. This rejects quotes, angle brackets, dots and path separators,
which
+blocks both markup injection and path traversal (e.g.
`?theme=../../etc/passwd`).
+Validation is applied to values read back from localStorage as well as to the
URL
+parameter, so a value saved by an earlier visit cannot bypass it, and the
stylesheet
+element is built with DOM APIs rather than string concatenation.
+
+Because the only URL the loader can produce is
`styles/themes/THEME_NAME/theme.css`, the
+themes actually installed on the server are the effective allowlist. A name
that does not
+match an installed theme fails to load, the base Knox styles remain in effect,
and the
+saved preference is discarded.
### Content Security Policy
@@ -340,6 +351,11 @@ If you have strict CSP, ensure it allows:
- Loading CSS from same origin
- Loading fonts from Google Fonts (if using modern theme)
+A policy can be applied to the knoxauth route with the WebAppSec provider's
+`SecurityHeaderFilter`, which emits arbitrary response headers from its init
+parameters. Note that `login.html` currently uses inline scripts and inline
event
+handlers, so a policy for this page needs `'unsafe-inline'` for `script-src`.
+
Example CSP:
```
Content-Security-Policy: style-src 'self' https://fonts.googleapis.com;
diff --git
a/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/README.md
b/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/README.md
index e15aa7721..4bbfd5872 100644
---
a/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/README.md
+++
b/gateway-applications/src/main/resources/applications/knoxauth/app/styles/themes/README.md
@@ -441,9 +441,18 @@ Modern CSS features used:
## Security Considerations
-1. **XSS Protection**: Theme names are not executed as code, only used to
construct file paths
-2. **Path Traversal**: Theme loader only loads files from `styles/themes/`
directory
-3. **Content Security Policy**: Ensure CSP allows loading external fonts if
using Google Fonts
+1. **Theme Name Validation**: Theme names arrive from untrusted sources (the
`?theme=`
+ URL parameter and the saved localStorage preference), so each candidate
must match
+ `^[a-zA-Z0-9_-]{1,64}$` before it is stored or used. Validation is applied
on the
+ localStorage read path as well as the URL, and a value that fails is
discarded.
+2. **XSS Protection**: The stylesheet element is created with DOM APIs
+ (`document.createElement`) rather than by concatenating markup, so a theme
name can
+ never be parsed as HTML.
+3. **Path Traversal**: The validation pattern rejects dots and path
separators, so the
+ only URL the loader can produce is `styles/themes/THEME_NAME/theme.css`. A
name that
+ does not correspond to an installed theme simply fails to load and the base
styles
+ remain in effect - the themes present on the server are the effective
allowlist.
+4. **Content Security Policy**: Ensure CSP allows loading external fonts if
using Google Fonts
## Troubleshooting