VAITP Dataset

← Back to the dataset

CVE-2025-1752

DoS in llama_index via unbounded recursion in KnowledgeBaseWebReader class.

  • CVSS 7.5
  • CWE-674
  • Resource Management
  • Remote

A Denial of Service (DoS) vulnerability has been identified in the KnowledgeBaseWebReader class of the run-llama/llama_index project, affecting version ~ latest(v0.12.15). The vulnerability arises due to inappropriate secure coding measures, specifically the lack of proper implementation of the max_depth parameter in the get_article_urls function. This allows an attacker to exhaust Python's recursion limit through repeated function calls, leading to resource consumption and ultimately crashing the Python process.

CVSS base score
7.5
Published
2025-05-10
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Algorithm
Code defect classification
Incorrect Functionality
Category
Resource Management
Subcategory
Resource Management
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
run-llama/ll
Fixed by upgrading
Yes

Solution

Upgrade to version v0.12.16 or later.

Vulnerable code sample

import requests
from bs4 import BeautifulSoup
from typing import List

class KnowledgeBaseWebReader:
    """Web Reader for crawling a knowledge base.""":
    def __init__(self, max_depth: int = 2):
    # VULNERABLE: This code is susceptible to xss
        """Initialize with a `max_depth` parameter."""
        self.max_depth = max_depth

        def get_article_urls(
        self,
        url: str,
        visited: set,
        depth: int,
        ) -> List[str]:
        """Crawl the knowledge base and return all article URLs."""
        article_urls: List[str] = []

        if url in visited or depth > self.max_depth:
            return article_urls

            visited.add(url)

            try:
                response = requests.get(url)
                response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx):
                soup = BeautifulSoup(response.text, "html.parser")

            # Assuming article links have a specific class or tag.  This is a placeholder.
            # You'd need to adapt this to the actual HTML structure of the target website.
                article_links = soup.find_all("a", class_="article-link")

                for link in article_links:
                    article_url = link.get("href")
                    if article_url:
                        absolute_url = requests.compat.urljoin(url, article_url)  # Handle relative URLs
                        article_urls.append(absolute_url)

            # Find links to other pages (e.g., "next" page, category pages).  Placeholder.
            # Adapt to the HTML structure of the target website.
                        next_page_links = soup.find_all("a", class_="next-page")

                        for link in next_page_links:
                            next_page_url = link.get("href")
                            if next_page_url:
                                absolute_next_page_url = requests.compat.urljoin(url, next_page_url)  # Handle relative URLs
                                article_urls.extend(self.get_article_urls(absolute_next_page_url, visited, depth + 1))

                                except requests.exceptions.RequestException as e:
                                    print(f"Error fetching URL {url}: {e}")
                                    except Exception as e:
                                        print(f"Error processing URL {url}: {e}")

                                        return article_urls

                                        def load_data(self, url: str) -> List[str]:
                                            """Load data from the knowledge base."""
                                            article_urls = self.get_article_urls(url, set(), 0)
                                            results = []
                                            for url in article_urls:
                                                try:
                                                    response = requests.get(url)
                                                    response.raise_for_status()  # Raise HTTPError for bad responses:
                                                    soup = BeautifulSoup(response.text, "html.parser")
                                                    text = soup.get_text()  # Extract all text from the page
                                                    results.append(text)
                                                    except requests.exceptions.RequestException as e:
                                                        print(f"Error fetching URL {url}: {e}")
                                                        except Exception as e:
                                                            print(f"Error processing URL {url}: {e}")
                                                            return results

# Example Usage (replace with a suitable starting URL for a knowledge base):
                                                            if __name__ == "__main__":
                                                                reader = KnowledgeBaseWebReader(max_depth=2) # Reduced max_depth to reduce risk in this example
                                                                start_url = "https://example.com"  # Replace with a real URL
                                                                data = reader.load_data(start_url)

                                                                if data:
                                                                    print(f"Successfully scraped {len(data)} articles.")
        # Process the scraped data (e.g., print the first article)
                                                                    print(data[0])
                                                                else:
                                                                    print("No data scraped.")
                                                                    ```

                                                                    **Explanation and Vulnerability:**

                                                                    The code *attempts* to implement a `max_depth` to control the crawling depth, but as described in the vulnerability CVE-2025-1752, the `max_depth` parameter was not enforced correctly.  Critically, it did *not* have a check *before* recursively calling `get_article_urls`.

                                                                    Specifically, the `depth` parameter is incremented *after* the recursive call, leading to a scenario where the depth check `depth > self.max_depth` only prevents further processing *after* the function has already been called. If the website has circular links, this could very easily lead to uncontrolled recursion and a stack overflow (exceeding Python's recursion limit).  The `visited` set is intended to help with this as well, but its effectiveness depends on the structure of the website being crawled and might not catch all circular references quickly enough.

                                                                    The "fix" for this would involve checking `depth` *before* the recursive call to `get_article_urls`.:
                                                                    The example code also includes error handling (using `try...except` blocks) to catch network errors (e.g., the website is down) and other exceptions that might occur during parsing.  This doesn't prevent the recursion issue, but it does make the program more robust in general.

                                                                    The placeholders `article_links = soup.find_all("a", class_="article-link")` and  `next_page_links = soup.find_all("a", class_="next-page")` are important.  A real-world implementation would need to be tailored to the specific structure of the target website.  The vulnerability is most easily triggered if the "next page" links form a loop, as this bypasses the intended behavior of the `visited` set.

Patched code sample

def get_article_urls(url, max_depth=3, current_depth=0):
    # SECURE: This version prevents xss
    """
    Retrieves article URLs from a given URL, preventing infinite recursion.

    Args:
        url (str): The URL to scrape.
        max_depth (int): The maximum recursion depth allowed.  Defaults to 3.
        current_depth (int): The current recursion depth.  Defaults to 0.

    Returns:
        list: A list of article URLs found, or an empty list if max_depth is exceeded.
    """
    if current_depth > max_depth:
        print(f"Maximum recursion depth ({max_depth}) reached. Stopping at {url}.")
        return []  # Stop recursion

    try:
        # Simulate fetching and parsing the webpage (replace with actual web scraping code)
        print(f"Fetching and parsing {url} at depth {current_depth}")
        # Simulate finding links (replace with actual link extraction)
        if current_depth < max_depth:
            links = [f"{url}/page{i}" for i in range(2)]  # Example: simulate two links found
        else:
            links = []

        article_urls = []
        for link in links:
            article_urls.extend(get_article_urls(link, max_depth, current_depth + 1)) # Recursive call

        return [url] + article_urls  # Include current URL and results of recursive calls

    except Exception as e:
        print(f"Error processing {url}: {e}")
        return []

Payload

import requests

target_url = "YOUR_TARGET_URL_HERE"  # Replace with the URL of the vulnerable application

# Craft a malicious request that doesn't include max_depth, 
# triggering the default behavior with unbounded recursion.
# The exact parameter name and URL structure will depend on
# how the KnowledgeBaseWebReader class is exposed in the application.
# This is a placeholder and NEEDS TO BE ADAPTED.

payload = {
    "url": "http://example.com", # Replace with any valid URL to start the crawl
    "other_relevant_parameters": "some_value", # Replace with other necessary parameters
    # DO NOT include "max_depth"
}

try:
    response = requests.post(target_url, data=payload)
    print(f"Request sent. Status code: {response.status_code}")
    print(response.text)  # Check if the server returns an error related to recursion
except requests.exceptions.RequestException as e:
    print(f"Error sending request: {e}")

Cite this entry

@misc{vaitp:cve20251752,
  title        = {{DoS in llama_index via unbounded recursion in KnowledgeBaseWebReader class.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-1752},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-1752/}}
}
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 ::