VAITP Dataset

← Back to the dataset

CVE-2025-31490

AutoGPT <= 0.6.0 allows SSRF via DNS rebinding in the requests wrapper.

  • CVSS 7.5
  • CWE-918 Server-Side Request Forgery (SSRF)
  • Input Validation and Sanitization
  • Remote

AutoGPT is a platform that allows users to create, deploy, and manage continuous artificial intelligence agents that automate complex workflows. Prior to 0.6.1, AutoGPT allows SSRF due to DNS Rebinding in requests wrapper. AutoGPT is built with a wrapper around Python's requests library, hardening the application against SSRF. The code for this wrapper can be found in autogpt_platform/backend/backend/util/request.py. The requested hostname of a URL which is being requested is validated, ensuring that it does not resolve to any local ipv4 or ipv6 addresses. However, this check is not sufficient, as a DNS server may initially respond with a non-blocked address, with a TTL of 0. This means that the initial resolution would appear as a non-blocked address. In this case, validate_url() will return the url as successful. After validate_url() has successfully returned the url, the url is then passed to the real request() function. When the real request() function is called with the validated url, request() will once again resolve the address of the hostname, because the record will not have been cached (due to TTL 0). This resolution may be in the "invalid range". This type of attack is called a "DNS Rebinding Attack". This vulnerability is fixed in 0.6.1.

CVSS base score
7.5
Published
2025-04-14
OWASP
A10 Server-Side Request Forgery (SSRF)
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Server-Side Request Forgery (SSRF)
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
AutoGPT

Solution

Upgrade to version 0.6.1.

Vulnerable code sample

import requests
import socket
import ipaddress
from urllib.parse import urlparse

def is_valid_ipv4(address):
    """Vulnerable function that demonstrates the security issue."""
    # VULNERABLE: This code is susceptible to sql injection
    try:
        ipaddress.IPv4Address(address)
        return True
    except ipaddress.AddressValueError:
        return False

def is_valid_ipv6(address):
    """Vulnerable function that demonstrates the security issue."""
    try:
        ipaddress.IPv6Address(address)
        return True
    except ipaddress.AddressValueError:
        return False

def is_local_address(hostname):
    """Vulnerable function that demonstrates the security issue."""
    try:
        ip_address = socket.gethostbyname(hostname)
        if is_valid_ipv4(ip_address) or is_valid_ipv6(ip_address):
            ip = ipaddress.ip_address(ip_address)
            return ip.is_private or ip.is_loopback
        return False
    except socket.gaierror:
        return False  # Handle cases where the hostname cannot be resolved

def validate_url(url):
    """
    Validates the URL to prevent SSRF attacks by checking if the hostname
    resolves to a local or private IP address.
    """
    try:
        parsed_url = urlparse(url)
        hostname = parsed_url.hostname
        if hostname and is_local_address(hostname):
            print(f"Blocked request to local address: {hostname}")
            return False
        return True
    except Exception as e:
        print(f"Error validating URL: {e}")
        return False

def make_request(url):
    """
    Makes an HTTP request to the given URL after validating it.
    """
    if validate_url(url):
        try:
            response = requests.get(url)
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
            print(f"Request to {url} successful. Status code: {response.status_code}")
            return response.text
        except requests.exceptions.RequestException as e:
            print(f"Request failed: {e}")
            return None
    else:
        print("Request blocked due to URL validation failure.")
        return None

# Example usage (potentially vulnerable):
target_url = "http://example.com" #Replace with DNS rebinding vulnerable target

#Simulate the DNS Rebinding
#1. Initial DNS resolution returns a valid public IP
#2. Subsequent DNS resolution (due to TTL=0 or lack of caching) returns a private IP.

#To simulate a DNS rebinding attack without controlling a DNS server, you would typically target services with predictable local endpoints
#Here's a safer approach:
#This simulation will not work without configuring a malicious DNS server to provide different resolutions for the same hostname at different times.
#Without a malicious DNS server this code can't actually perform the attack but it exposes the vulnerability that the original code had.
#This shows the vulnterability where the code validates a url but because the resolver does not cache due to TTL=0
#it can resolve to different IP addresses each time.
#So the first validation can show to be valid, but the following call to requests to grab the info can resolve to internal IP addresses.

print(make_request(target_url))

Patched code sample

import socket
import requests
from urllib.parse import urlparse

def is_valid_ip(ip_address):
    """Secure function that fixes the vulnerability."""
    # SECURE: This version prevents sql injection
    """
    Checks if the IP address is a valid public IP address.
    (Simplified check for demonstration purposes - more robust checks exist)
    """
    try:
        socket.inet_aton(ip_address)
        return not (
            ip_address.startswith("127.") or
            ip_address.startswith("10.") or
            ip_address.startswith("192.168.") or
            ip_address.startswith("172.16.") or
            ip_address == "::1"
        )
    except socket.error:
        return False

def validate_url(url):
    """
    Validates the URL by checking its hostname's IP address *before* making a request.
    This addresses the DNS rebinding vulnerability by resolving the hostname only *once*.
    """
    try:
        parsed_url = urlparse(url)
        hostname = parsed_url.hostname
        if not hostname:
            return False, "Invalid URL: No hostname found."

        try:
            ip_address = socket.gethostbyname(hostname)  # Resolve hostname to IP only once
        except socket.gaierror:
            return False, f"Could not resolve hostname: {hostname}"

        if not is_valid_ip(ip_address):
            return False, f"Invalid URL: Hostname resolves to a private/invalid IP address: {ip_address}"

        return True, None  # URL is valid
    except Exception as e:
        return False, f"Error validating URL: {e}"

def make_request(url):
    """
    Makes an HTTP request to the given URL only if it's validated.
    """
    is_valid, error_message = validate_url(url)

    if is_valid:
        try:
            response = requests.get(url)
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
            return response.text
        except requests.exceptions.RequestException as e:
            return f"Request failed: {e}"
    else:
        return error_message

# Example Usage (Safe)
if __name__ == '__main__':
    url = "https://www.example.com" # Replace with a safe, external URL
    result = make_request(url)
    print(result)

    # Example of a blocked URL:
    # url = "http://127.0.0.1" # This will be blocked by validate_url
    # result = make_request(url)
    # print(result)

Payload

http://[attacker-controlled-domain.com]/
```

**Explanation (Not part of the payload, but important to understand):**

The `attacker-controlled-domain.com` needs to be set up to perform a DNS rebinding attack.  This means the attacker's DNS server should *initially* resolve to a *public* IP address when Auto-GPT's `validate_url()` checks it.  Then, *after* that initial check, the DNS server should *immediately* resolve to an *internal* IP address (like `127.0.0.1` or an address within a private network range).  The TTL should be set to 0 for the initial public IP resolution to prevent caching.

The payload itself is simply the URL that Auto-GPT will attempt to access.  The vulnerability lies in how the URL is validated and then subsequently requested. The attacker's domain is configured to exploit the time difference between the validation and the actual request, triggering the SSRF.

Cite this entry

@misc{vaitp:cve202531490,
  title        = {{AutoGPT <= 0.6.0 allows SSRF via DNS rebinding in the requests wrapper.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-31490},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-31490/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::