Malcolm's nginx Lua RBAC check (check_rbac in nginx_auth_helpers.lua) matches role-restricted path patterns (/htadmin, /auth, /admin_login, /arkime/api/esadmin, /netbox, /upload, etc.) against the raw, undecoded ngx.var.request_uri. nginx's own location dispatch, however, percent-decodes and normalizes the URI before matching, and the htadmin/admin_login location uses a case-insensitive regex (~*).
Because the RBAC layer's view of the path and nginx's view of the path can disagree, an authenticated low-privilege user can reach a role-restricted location by:
- percent-encoding a character in the restricted path (e.g.
/%68tadmin.php)
- varying case (e.g.
/HTADMIN.php)
- slash-doubling, if
merge_slashes isn't guaranteed on for the relevant location
nginx routes the request to the restricted backend location in all three cases; check_rbac's pattern match fails to recognize the path as restricted and grants access (path_role_envs is fail-open on non-match — line 437).
Verified end-to-end: authenticated as a low-privilege Keycloak user, /htadmin.php correctly returns 403; /%68tadmin.php with the identical session returns 200 with real htadmin backend content, confirmed via the backend's own access log.
Every entry in path_role_envs is affected, not just htadmin — same bypass applies to all role-restricted prefixes.
Fix: normalize_uri_for_rbac() updated to percent-decode, collapse repeated slashes, collapse traversal sequences, and lowercase — in that order — before matching, so the RBAC layer's normalization agrees with nginx's own decode/case-fold behavior. Verified against malformed escapes, encoded traversal, double-encoding, and overlong-UTF-8 edge cases in a live OpenResty shell; no regressions or new fail-open paths introduced.
See GHSA-jr6p-63pg-hr6g
Malcolm's nginx Lua RBAC check (
check_rbacinnginx_auth_helpers.lua) matches role-restricted path patterns (/htadmin,/auth,/admin_login,/arkime/api/esadmin,/netbox,/upload, etc.) against the raw, undecodedngx.var.request_uri. nginx's own location dispatch, however, percent-decodes and normalizes the URI before matching, and the htadmin/admin_login location uses a case-insensitive regex (~*).Because the RBAC layer's view of the path and nginx's view of the path can disagree, an authenticated low-privilege user can reach a role-restricted location by:
/%68tadmin.php)/HTADMIN.php)merge_slashesisn't guaranteed on for the relevant locationnginx routes the request to the restricted backend location in all three cases;
check_rbac's pattern match fails to recognize the path as restricted and grants access (path_role_envsis fail-open on non-match — line 437).Verified end-to-end: authenticated as a low-privilege Keycloak user,
/htadmin.phpcorrectly returns 403;/%68tadmin.phpwith the identical session returns 200 with real htadmin backend content, confirmed via the backend's own access log.Every entry in
path_role_envsis affected, not just htadmin — same bypass applies to all role-restricted prefixes.Fix:
normalize_uri_for_rbac()updated to percent-decode, collapse repeated slashes, collapse traversal sequences, and lowercase — in that order — before matching, so the RBAC layer's normalization agrees with nginx's own decode/case-fold behavior. Verified against malformed escapes, encoded traversal, double-encoding, and overlong-UTF-8 edge cases in a live OpenResty shell; no regressions or new fail-open paths introduced.See GHSA-jr6p-63pg-hr6g