CVE-2024-31461 (Python, published 2024-04-10T18:15Z)
Description: value: Plane, an open-source project management tool, has a Server-Side Request Forgery (SSRF) vulnerability in versions prior to 0.17-dev. This issue may allow an attacker to send arbitrary requests from the server hosting the application, potentially leading to unauthorized access to internal systems. The impact of this vulnerability includes, but is not limited to, unauthorized access to internal services accessible from the server, potential leakage of sensitive information from internal services, manipulation of internal systems by interacting with internal APIs. Version 0.17-dev contains a patch for this issue. Those who are unable to update immediately may mitigate the issue by restricting outgoing network connections from servers hosting the application to essential services only and/or implementing strict input validation on URLs or parameters that are used to generate server-side requests.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/makeplane/plane
Files changed:
apiserver/plane/utils/importers/jira.py
Commit message: fix: update jira summary endpoints (#3333)

* dev: update jira summary endpoints

* dev: update jira project key validations

* dev: updated key length

Vulnerable code:
from urllib.parse import urlparse
    allowed_lists = ["atl-paas.net", "atlassian.com", "atlassian.net", "jira.com"]
    # Extract the base domain from the hostname
    parsed_uri = urlparse(f"https://{hostname}")  # Add scheme for urlparse to work properly
    domain = parsed_uri.netloc.split(":")[0]  # Removes port number if included
    base_domain = ".".join(domain.split(".")[-2:])  # Extract base domain
    # Check if the base domain is in the allowed list
    return base_domain in allowed_lists
        

            print("Errored Hostname")
        issue_url = f"https://{hostname}/rest/api/3/search?jql=project={project_key} AND issuetype!=Epic"
        module_url = f"https://{hostname}/rest/api/3/search?jql=project={project_key} AND issuetype=Epic"
        status_url = f"https://{hostname}/rest/api/3/project/${project_key}/statuses"
        labels_url = f"https://{hostname}/rest/api/3/label/?jql=project={project_key}"
        users_url = (
            f"https://{hostname}/rest/api/3/users/search?jql=project={project_key}"

Fixed code:
import re
from urllib.parse import urlparse, urljoin
    allowed_domains = ["atl-paas.net", "atlassian.com", "atlassian.net", "jira.com"]
    parsed_uri = urlparse(f"https://{hostname}")
    domain = parsed_uri.netloc.split(":")[0]  # Ensures no port is included
    base_domain = ".".join(domain.split(".")[-2:])
    return base_domain in allowed_domains

def is_valid_project_key(project_key):
    if project_key:
        project_key = project_key.strip().upper()
        # Adjust the regular expression as needed based on your specific requirements.
        if len(project_key) > 30:
            return False
        # Check the validity of the key as well
        pattern = re.compile(r'^[A-Z0-9]{1,10}$')
        return pattern.match(project_key) is not None
    else:
        False

def generate_valid_project_key(project_key):
    return project_key.strip().upper()

def generate_url(hostname, path):
    if not is_allowed_hostname(hostname):
        raise ValueError("Invalid or unauthorized hostname")
    return urljoin(f"https://{hostname}", path)
        if not is_valid_project_key(project_key):
            return {"error": "Invalid project key"}

        
        # make the project key upper case
        project_key = generate_valid_project_key(project_key)
        # issues
        issue_url = generate_url(
            hostname,
            f"/rest/api/3/search?jql=project={project_key} AND issuetype!=Epic",
        )
        # modules
        module_url = generate_url(
            hostname, f"/rest/api/3/search?jql=project={project_key} AND issuetype=Epic"
        )
        # status
        status_url = generate_url(
            hostname, f"/rest/api/3/project/${project_key}/statuses"
        )
        # labels
        labels_url = generate_url(
            hostname, f"/rest/api/3/label/?jql=project={project_key}"
        )
        # users
        users_url = generate_url(
            hostname, f"/rest/api/3/users/search?jql=project={project_key}"