GitHub Advisoryadvisory · RubyGems
GHSA-9pmc-p236-855h Ruby CSS Parser: SSRF and Local File Disclosure in `CssParser::Parser#read_remote_file`
HIGH
Official page ↗
- Aliases
- CVE-2026-53727
- Ecosystem
- RubyGems
- Packages
- css_parser
- Severity
- High
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N
- Weaknesses
- CWE-918
- Reviewed
- 2026-07-09
Affected packages
• RubyGems css_parser: < 3.0.0 → fixed in 3.0.0
Description
Summary
`CssParser::Parser#read_remote_file` (and therefore `load_uri!`, and the `@import`-following branch of `add_block!`) issues HTTP/HTTPS requests against any host, port and URI it is handed, with no scheme allowlist, no host / IP filtering, and no protection against link-local, loopback or RFC‑1918 addresses. `Location:` redirects are followed recursively back into the same function, which also services `file://` URIs, so a single attacker-controlled HTTP redirect upgrades the bug from SSRF to arbitrary local file disclosure.
In practice, any consumer of `css_parser` that hands it attacker‑influenced CSS together with a `base_uri:` option — Premailer being the canonical example — is exposed. The attacker only needs the ability to land one `@import url(...)` in the CSS that the host application parses.
Vulnerable code
`lib/css_parser/parser.rb#L613-L687`:
```ruby
def read_remote_file(uri) # :nodoc:
...
begin
uri = Addressable::URI.parse(uri.to_s)
if uri.scheme == 'file'
# local file
path = uri.path
path.gsub!(%r{^/}, '') if Gem.win_platform?
src = File.read(path, mode: 'rb') # = 300 and res.code.to_i `:
| File contents | Recoverable through `to_s` / `each_selector`? |
|---|---|
| `root:x:0:0:root:/root:/bin/bash\napi_key=…` (no braces) | nothing |
| `{"AccessKeyId":"AKIA…","Secret":"wJalr…"}` (JSON, leading `{`) | nothing via `to_s`; partial leak via `each_rule_set` (empty selector + one parsed declaration) |
| `SECRET=eyJhbGc… {}` (empty braces) | nothing (rule not added — raw decls empty) |
| `api_key=…\ndatabase{host:db.internal;pw:hunter2;}` | full leak: selector = `api_key=… database`, decls = `host: db.internal; pw: hunter2` |
| nginx / HCL-style config: `name { key: value; }` | full leak of both slots |
In practice this means high-value targets that aren't CSS-shaped — TLS keys, SSH keys, JWTs, `.env` files (`KEY=VALUE` lines), `/etc/passwd`, binary content — do not leak their bytes through the parser API. Configuration files written in block-style DSLs (nginx, HCL/Terraform, Caddy, etc.) leak heavily. The `File.read` itself always executes, which is enough to (a) act as a file-existence oracle and (b) cause resource exhaustion on large pseudo-files like `/dev/zero`.
The stronger recovery channel is the SSRF leg, not file disclosure: when the response comes from an internal HTTP service, Premailer-style consumers serialize the parsed CSS back into rendered HTML/email output, and any CSS-shaped bytes in the response surface there.
Is it "blind"?
It is *not* a classic blind SSRF. The bug has two recovery channels:
1. Application output. The library is most commonly driven by Premailer, which writes the parsed CSS back into the rendered HTML. Any response bytes that happen to form CSS rules are emitted into the output document and therefore visible to whoever receives the rendered output. Internal endpoints frequently return content that parses as CSS by accid…
Related
Alias · CVE (1)
- CVE-2026-53727 css_parser is a Ruby CSS parser. vulnerability
Weakness · CWE (1)
- CWE-918 Server-Side Request Forgery weakness