|
| 1 | +--- |
| 2 | +layout: advisory |
| 3 | +title: 'CVE-2026-54163 (secure_headers): CSP directive injection via sandbox, plugin_types, |
| 4 | + and report_to when given untrusted input' |
| 5 | +comments: false |
| 6 | +categories: |
| 7 | +- secure_headers |
| 8 | +advisory: |
| 9 | + gem: secure_headers |
| 10 | + cve: 2026-54163 |
| 11 | + ghsa: rqq5-2gf9-4w4q |
| 12 | + url: https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54163 |
| 13 | + title: CSP directive injection via sandbox, plugin_types, and report_to when given |
| 14 | + untrusted input |
| 15 | + date: 2026-06-03 |
| 16 | + description: |- |
| 17 | + ## Summary |
| 18 | +
|
| 19 | + secure_headers builds the Content-Security-Policy value by stitching |
| 20 | + every configured directive together with ; separators. Three directive |
| 21 | + builders (build_sandbox_list_directive, build_media_type_list_directive, |
| 22 | + build_report_to_directive) interpolate caller-supplied strings into |
| 23 | + that value without scrubbing ;, \r, or \n. |
| 24 | +
|
| 25 | + When an application forwards untrusted input into |
| 26 | + SecureHeaders.override_content_security_policy_directives (or append_…) |
| 27 | + for :sandbox, :plugin_types, or :report_to, an attacker can embed a |
| 28 | + literal ; and inject an arbitrary CSP directive into the header value. |
| 29 | + Because :sandbox and :plugin_types both sort alphabetically before |
| 30 | + :script_src in BODY_DIRECTIVES, the injected script-src lands earlier |
| 31 | + in the header and wins under the CSP first-occurrence rule, defeating |
| 32 | + the application's real script-src. End result: an 'unsafe-inline' * policy |
| 33 | + is forced for inline <script> despite the configured strict CSP, giving |
| 34 | + full XSS reachability anywhere reflected or stored content meets one of |
| 35 | + these three sinks. |
| 36 | +
|
| 37 | + An existing ;/\n scrub is already present in the source-list builder |
| 38 | + (build_source_list_directive), but the three sibling builders here |
| 39 | + never received the same treatment and still emit caller bytes verbatim |
| 40 | + into the CSP value. |
| 41 | +
|
| 42 | + ## Impact |
| 43 | +
|
| 44 | + Although piping untrusted input into CSP directives is generally |
| 45 | + discouraged, applications that do so for one of the three uncovered |
| 46 | + directives turn that endpoint into an XSS sink with an effective * |
| 47 | + 'unsafe-inline' script-src, even though the global config says |
| 48 | + script_src: %w('self'). The same primitive can also be used to point |
| 49 | + report-to / report-uri at attacker infrastructure to silently siphon |
| 50 | + CSP violation reports — which include the violated URL, blocked-uri, |
| 51 | + source-file, line-number and a sample-snippet, useful for |
| 52 | + fingerprinting and for harvesting victim-internal URLs. |
| 53 | +
|
| 54 | + The global default CSP set in Configuration.default is supposed to |
| 55 | + be a backstop: even if a controller appends a single risky value, |
| 56 | + the strict script-src should remain the first match. This bug breaks |
| 57 | + that property by letting the appended value redefine the policy header |
| 58 | + upstream of the legitimate script-src. |
| 59 | + cvss_v3: 4.7 |
| 60 | + patched_versions: |
| 61 | + - ">= 7.3.0" |
| 62 | + related: |
| 63 | + url: |
| 64 | + - https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54163 |
| 65 | + - https://rubygems.org/gems/secure_headers/versions/7.3.0 |
| 66 | + - https://github.com/github/secure_headers/releases/tag/v7.3.0 |
| 67 | + - https://github.com/github/secure_headers/commit/286a79dea80c6a9be4ca93e0f284c923cf77e539 |
| 68 | + - https://github.com/github/secure_headers/security/advisories/GHSA-rqq5-2gf9-4w4q |
| 69 | + notes: | |
| 70 | + - CVE is reserved, but not published so no cvss_v2 or cvss_v4 values. |
| 71 | + - CHANGELOG only goes to 6.5.0. |
| 72 | +--- |
0 commit comments