Back to Blog
critical SEVERITY6 min read

How Server-Side Request Forgery (SSRF) happens in Python requests.get() and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `models/common.py` where `requests.get()` fetched images from arbitrary URLs without validating whether the target resolved to internal infrastructure. An attacker could supply URLs targeting AWS metadata endpoints (169.254.169.254), private networks, or localhost services through the Flask REST API. The fix introduces DNS-resolution-based validation using Python's `socket.getaddrinfo()` and `ipaddress` module to block

O
By Orbis AppSec
Published June 11, 2026Reviewed June 11, 2026

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in Python's `requests.get()` call within `models/common.py` of the YOLOv5 object detection framework. The code fetched images from arbitrary user-supplied URLs without validating whether the hostname resolved to internal/private IP addresses. The fix adds a `_validate_ssrf_url()` function that resolves the hostname via `socket.getaddrinfo()` and checks each resolved IP against private, loopback, link-local, reserved, and multicast ranges using Python's `ipaddress` module before allowing the request.

Vulnerability at a Glance

cweCWE-918
fixAdded `_validate_ssrf_url()` that resolves hostnames and blocks private/internal IP addresses before fetching
riskAttackers can probe internal infrastructure, access cloud metadata, and exfiltrate credentials via crafted image URLs
languagePython
root cause`requests.get()` called on user-supplied URLs without IP address validation
vulnerabilityServer-Side Request Forgery (SSRF)

How Server-Side Request Forgery (SSRF) happens in Python requests.get() and how to fix it

Introduction

The models/common.py file in the YOLOv5 object detection framework handles model inference including loading images from various sources. At line 882, when the input string starts with 'http', the code calls requests.get() to fetch the image — but without any validation of where that URL actually points. This created a critical SSRF vulnerability that could allow attackers interacting with the Flask REST API to probe internal infrastructure, access cloud metadata endpoints, and potentially exfiltrate sensitive credentials.

The vulnerability is particularly dangerous because YOLOv5 is widely deployed as a service behind REST APIs, meaning user-supplied image URLs are a standard input vector. An attacker doesn't need any special privileges — they just need to supply a crafted URL where an image URL is expected.

The Vulnerability Explained

Server-Side Request Forgery occurs when a server-side application fetches a resource from a URL that an attacker can control, and the application doesn't validate whether that URL targets internal resources.

In models/common.py, the vulnerable pattern looked like this:

# Vulnerable code - fetches from any URL without validation
if str(im).startswith('http'):
    response = requests.get(im)
    # Process image from response...

The problem is straightforward: if im is "http://169.254.169.254/latest/meta-data/iam/security-credentials/", the server dutifully makes that request from within the cloud environment — where the metadata endpoint is accessible. The response containing IAM credentials gets returned to the attacker.

Concrete attack scenarios against this code:

  1. AWS credential theft: An attacker sends http://169.254.169.254/latest/meta-data/iam/security-credentials/ as an image URL to the YOLOv5 inference API. The server fetches IAM temporary credentials and returns them (or an error containing the response body).

  2. Internal port scanning: By sending URLs like http://192.168.1.1:8080/, http://192.168.1.1:3306/, etc., an attacker can map internal services by observing response times and error messages.

  3. Accessing internal services: URLs like http://localhost:6379/ could interact with internal Redis instances, potentially allowing data exfiltration or command execution.

Additionally, the diff reveals a secondary issue on line 514 where eval(meta["names"]) was used to parse model metadata — an arbitrary code execution risk if an attacker can control model files:

# Also vulnerable - arbitrary code execution via eval()
stride, names = int(meta["stride"]), eval(meta["names"])

The Fix

The fix introduces two new functions and applies multiple security improvements:

1. DNS-resolution-based URL validation (_validate_ssrf_url)

def _validate_ssrf_url(url: str) -> None:
    """Raise ValueError if url resolves to any private/internal address."""
    hostname = urlparse(url).hostname or ""
    try:
        results = socket.getaddrinfo(hostname, None)
    except socket.gaierror as e:
        raise ValueError(f"Could not resolve hostname '{hostname}': {e}") from e
    for _family, _type, _proto, _canonname, sockaddr in results:
        addr = ipaddress.ip_address(sockaddr[0])
        if addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_reserved or addr.is_multicast:
            raise ValueError(f"Blocked request to internal address: {addr}")

This function resolves the hostname to actual IP addresses using socket.getaddrinfo(), then checks every resolved address against Python's ipaddress module properties. This approach is superior to regex-based hostname matching because:

  • It catches DNS names that resolve to private IPs (e.g., internal.attacker.com → 169.254.169.254)
  • It handles all IP representations (IPv4, IPv6, mapped addresses)
  • It validates the actual network destination, not just the URL string

2. Redirect-safe fetching (_request_ssrf_url)

def _request_ssrf_url(url: str, max_redirects: int = 5):
    """Fetch a URL after validating against SSRF..."""

This wrapper handles HTTP redirects safely — each redirect target is re-validated before following it. This prevents DNS rebinding attacks where the initial URL passes validation but redirects to an internal address.

3. Replacing eval() with ast.literal_eval()

# Before (dangerous):
stride, names = int(meta["stride"]), eval(meta["names"])

# After (safe):
stride, names = int(meta["stride"]), ast.literal_eval(meta["names"])

This change ensures that model metadata parsing only accepts Python literals (strings, numbers, tuples, lists, dicts, booleans, None) and cannot execute arbitrary code.

New imports added:

import ipaddress
import socket
from urllib.parse import urljoin, urlparse  # urljoin added

Prevention & Best Practices

1. Always validate URLs at the network level, not the string level

String-based validation (regex on hostnames) is consistently bypassed. Resolve the hostname and validate the IP:

import socket, ipaddress

def is_safe_url(url):
    hostname = urlparse(url).hostname
    for result in socket.getaddrinfo(hostname, None):
        addr = ipaddress.ip_address(result[4][0])
        if addr.is_private or addr.is_loopback or addr.is_link_local:
            return False
    return True

2. Validate after every redirect

HTTP 301/302 redirects can point to internal addresses. Disable automatic redirect following and validate each hop:

response = requests.get(url, allow_redirects=False)
while response.is_redirect:
    redirect_url = response.headers['Location']
    _validate_ssrf_url(redirect_url)  # Validate before following
    response = requests.get(redirect_url, allow_redirects=False)

3. Never use eval() on external data

Replace eval() with ast.literal_eval() for parsing data structures, or use proper serialization formats (JSON, YAML with safe loading).

4. Apply network-level controls

Even with code-level validation, defense in depth matters:
- Use network policies to restrict outbound traffic from application servers
- Block metadata endpoint access via instance firewall rules
- Use IMDSv2 (token-required) on AWS to mitigate metadata theft

Key Takeaways

  • requests.get() on user-supplied URLs is an SSRF sink — any code path where external input reaches an HTTP client must validate the resolved IP addresses, not just the URL string.
  • DNS resolution is the correct validation layersocket.getaddrinfo() + ipaddress module catches bypasses that string-matching misses, including DNS rebinding and alternative IP representations.
  • Redirect following must re-validate each hop — the _request_ssrf_url() function with max_redirects prevents attackers from using open redirects to reach internal services.
  • eval() on model metadata was a hidden RCE — the ast.literal_eval() replacement eliminates arbitrary code execution from malicious model files, a supply-chain attack vector.
  • ML inference APIs are high-value SSRF targets — because they naturally accept URLs as input (for images, models, datasets), they require explicit SSRF protection that general web frameworks don't provide by default.

How Orbis AppSec Detected This

  • Source: User-supplied image URL string passed through the Flask REST API inference endpoint, flowing into the im parameter in models/common.py
  • Sink: requests.get(im) at models/common.py:882 — an unvalidated HTTP request to an attacker-controlled destination
  • Missing control: No DNS resolution validation, no IP address range checking, no redirect validation before fetching arbitrary URLs
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Added _validate_ssrf_url() function that resolves hostnames via socket.getaddrinfo() and blocks requests to any private, loopback, link-local, reserved, or multicast IP addresses

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This SSRF vulnerability in YOLOv5's models/common.py demonstrates a pattern common in ML/AI services: image processing pipelines that accept URLs as input without considering that the server's network position grants access to internal resources an external attacker shouldn't reach. The fix — resolving hostnames to IPs and validating each address against private ranges — is the gold standard for SSRF prevention in Python. Combined with redirect validation and the bonus eval()ast.literal_eval() hardening, this patch closes both a network-level and code-execution attack surface.

If your application fetches resources from user-supplied URLs, implement DNS-resolution-based validation before every request. Don't trust the URL string — trust the resolved IP address.

References

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is a vulnerability where an attacker tricks a server into making HTTP requests to unintended destinations, typically internal services or cloud metadata endpoints that are not directly accessible from the internet.

How do you prevent SSRF in Python?

Resolve the hostname to IP addresses using `socket.getaddrinfo()`, then validate each resolved address against private/internal ranges using Python's `ipaddress` module before making any HTTP request. Also enforce redirect validation to prevent DNS rebinding.

What CWE is SSRF?

CWE-918: Server-Side Request Forgery (SSRF). It falls under OWASP Top 10 2021 category A10:2021 – Server-Side Request Forgery.

Is URL pattern matching enough to prevent SSRF?

No. Hostname-based blocklists can be bypassed using DNS rebinding, IPv6 representations, decimal IP notation, or URL encoding tricks. You must resolve the hostname and validate the actual IP addresses.

Can static analysis detect SSRF?

Yes. Static analysis tools can flag patterns where user-controlled input flows into HTTP request functions like `requests.get()` without intervening validation. Taint analysis traces data from API inputs to network call sinks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #13787

Related Articles

critical

How API key exposure in configuration files happens in TOML config and how to fix it

A critical security vulnerability in `commands/webperf.toml` allowed API keys to be hardcoded directly in configuration files, creating a credential exposure risk. The documentation on line 11 suggested developers could provide `CRUX_API_KEY` or `GOOGLE_API_KEY` directly in the config, which could lead to these sensitive credentials being committed to version control or exposed in logs. The fix updated the documentation to explicitly require environment variables and warn against hardcoding cred

critical

How integer overflow in buffer size calculation happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

critical

How weak cryptographic randomness happens in C CSPRNG fallback paths and how to fix it

A critical vulnerability in `lib/sp_crypto.c` allowed the CSPRNG function to fall back to predictable randomness based on `time(NULL)` XORed with a counter when `/dev/urandom` was unavailable. An attacker who knew the approximate generation time could brute-force the output. The fix removes the unsafe fallback entirely, failing fast instead of silently degrading to weak randomness.

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.

critical

How shell command injection happens in Python subprocess and how to fix it

A critical shell command injection vulnerability was discovered in the radare2 build system's `meson.py` file, where `os.system()` was used with an f-string to execute git commands. An attacker who could control the `remote` variable could inject arbitrary shell commands. The fix replaces `os.system()` with `subprocess.call()` using a list of arguments, eliminating shell interpretation entirely.

critical

How buffer overflow happens in C display_controller.cpp and how to fix it

A critical buffer overflow vulnerability was discovered in `playground/GpsBasics/display_controller.cpp` where `sprintf` was used without bounds checking on fixed-size stack buffers. An attacker supplying malicious GPS data with extreme field values (such as a year value of `99999`) could produce a formatted string longer than the declared buffer, leading to stack corruption and potential code execution. The fix introduces proper buffer-length enforcement, ensuring formatted GPS strings can neve