Threats tagged 'ghsa-cr65-55v9-xhvv'
1 threat
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| High | 7.5 | EPSS 0.4% | CVE-2026-103030cfug dio · Information Exposure High7.5EPSS 0.4%CVE Database V5CVE-2026-103030 ### Summary `dio_http2_adapter` follows HTTP `Location` redirects without stripping sensitive request headers when the redirect target is a different origin. An attacker who can cause any origin server to return a cross-origin `302` redirect receives the victim's `Authorization`, `Cookie`, and `Proxy-Authorization` headers verbatim. This vulnerability has a CVSS v3.1 base score of **7.5 (High)** because `followRedirects` is enabled by default, no authentication is required to exploit it, and the confidentiality impact is high. ### Details When `Dio` is configured to use `Http2Adapter`, the adapter's internal `_fetch` method constructs HTTP/2 request headers by unconditionally copying all entries from `options.headers`: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:111 headers.addAll( options.headers.entries.map((entry) { ... return Header.ascii(entry.key.toLowerCase(), v); }).toList(), ); ``` On receiving a `3xx` response, the adapter reads the `Location` header and resolves the redirect target URI: ```dart // plugins/http2_adapter/lib/src/http2_adapter.dart:254-263 final url = responseHeaders.value('location'); final uri = Uri.parse(url ?? ''); final String path = resolveRedirectUri(options.uri, uri).toString(); return _fetch(options.copyWith(path: path, maxRedirects: --options.maxRedirects), ...); ``` `options.copyWith` is called without an explicit `headers` argument; `dio/lib/src/options.dart:629` therefore preserves the full original header map via `headers ?? Map.from(this.headers)`. `resolveRedirectUri` (`http2_adapter.dart:290`) returns an attacker-controlled cross-origin URI without any origin comparison. The combination means that on every redirect, the full set of original sensitive headers — including `Authorization`, `Cookie`, and `Proxy-Authorization` — is forwarded to the new authority with no sanitization. **Data flow (source → sink):** 1. `dio/lib/src/dio_mixin.dart:383` — public `Dio.request<T>(String path, ...)` entry point. 2. `dio/lib/src/options.dart:331` — base and per-request headers merged into `RequestOptions`. 3. `dio/lib/src/dio_mixin.dart:596` — `httpClientAdapter.fetch(reqOpt, ...)` dispatches to `Http2Adapter`. 4. `plugins/http2_adapter/lib/src/http2_adapter.dart:254` — `Location` header is read from the network response (attacker-controlled source). 5. `plugins/http2_adapter/lib/src/http2_adapter.dart:258` — redirect target URI resolved; cross-origin check absent. 6. `plugins/http2_adapter/lib/src/http2_adapter.dart:260` — `options.copyWith(path: path, ...)` called without `headers` override. 7. `dio/lib/src/options.dart:629` — `copyWith` preserves `Map.from(this.headers)` — all sensitive headers retained. 8. `plugins/http2_adapter/lib/src/http2_adapter.dart:111` — preserved headers emitted verbatim to the cross-origin HTTP/2 request (sink). **Suggested patch** — strip sensitive headers before following a cross-origin redirect: ```diff --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart + static const _sensitiveRedirectHeaders = { + 'authorization', + 'cookie', + 'proxy-authorization', + }; + - final String path = resolveRedirectUri(options.uri, uri).toString(); + final redirectUri = resolveRedirectUri(options.uri, uri); + final redirectOptions = options.copyWith( + path: redirectUri.toString(), + maxRedirects: --options.maxRedirects, + ); + if (!_sameOrigin(options.uri, redirectUri)) { + redirectOptions.headers.removeWhere( + (name, _) => _sensitiveRedirectHeaders.contains(name.toLowerCase()), + ); + } return _fetch( - options.copyWith(path: path, maxRedirects: --options.maxRedirects), + redirectOptions, list != null ? Stream.fromIterable(list) : null, cancelFuture, redirects, ); + + static bool _sameOrigin(Uri a, Uri b) => + a.scheme == b.scheme && a.host == b.host && a.port == b.port; ``` ### PoC The following steps reproduce the vulnerability using two local HTTP/2 cleartext (h2c) servers inside an isolated Docker container (no external network required). **Prerequisites:** - Docker installed and accessible. - Repository checked out at the path used as the build context. **Build and run:** ```bash # From the repository root (github_web_81_cfug__dio/) docker build -f vuln-001/Dockerfile -t dio-vuln-001:poc . docker run --rm --network none dio-vuln-001:poc ``` **What the container does:** 1. Starts an *origin* h2c server on `127.0.0.1:18081` that immediately returns `302 Location: http://127.0.0.1:18082/capture`. 2. Starts an *attacker* h2c server on `127.0.0.1:18082` that logs every received header. 3. Executes the following Dart exploit: ```dart final dio = Dio() ..httpClientAdapter = Http2Adapter(ConnectionManager()); await dio.get( 'http://127.0.0.1:18081/start', options: Options(headers: { 'Authorization': 'Bearer SECRET | CVE Database V5 |
Page 1 of 1