Skip to content

[Security] Add SSRF protection for nextcloud_host validation #412

@cbcoutinho

Description

@cbcoutinho

Context

From PR #401 code review - Medium priority security hardening.

Problem

The NEXTCLOUD_HOST environment variable is used to construct URLs for API calls, but there's no validation to prevent Server-Side Request Forgery (SSRF) attacks.

Potential attack scenario:

  • Malicious user sets NEXTCLOUD_HOST=http://169.254.169.254 (AWS metadata endpoint)
  • MCP server makes requests to internal infrastructure

Current Validation

Basic format checking exists, but no allowlist or blocklist for sensitive endpoints.

Proposed Solution

Implement multi-layer SSRF protection:

1. Blocklist Private IP Ranges:

import ipaddress

BLOCKED_RANGES = [
    ipaddress.ip_network("127.0.0.0/8"),      # Loopback
    ipaddress.ip_network("10.0.0.0/8"),       # Private
    ipaddress.ip_network("172.16.0.0/12"),    # Private
    ipaddress.ip_network("192.168.0.0/16"),   # Private
    ipaddress.ip_network("169.254.0.0/16"),   # Link-local (AWS metadata)
]

2. Optional Allowlist:

# Environment variable for production deployments
ALLOWED_NEXTCLOUD_HOSTS=nextcloud.example.com,cloud.company.com

3. DNS Resolution Check:

  • Resolve hostname to IP before connecting
  • Verify IP is not in blocked ranges
  • Prevent DNS rebinding attacks

Example Implementation:

def validate_nextcloud_host(host: str) -> None:
    """Validate that nextcloud_host is not targeting internal infrastructure."""
    parsed = urlparse(host)
    
    # Check allowlist if configured
    allowed = os.getenv("ALLOWED_NEXTCLOUD_HOSTS", "").split(",")
    if allowed and parsed.hostname not in allowed:
        raise ValueError(f"Host {parsed.hostname} not in allowlist")
    
    # Resolve and check IP ranges
    try:
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
        for blocked in BLOCKED_RANGES:
            if ip in blocked:
                raise ValueError(f"Host resolves to blocked IP range: {ip}")
    except socket.gaierror:
        raise ValueError(f"Cannot resolve hostname: {parsed.hostname}")

References

Priority

Medium - Defense in depth, requires environment variable access for exploitation

Notes

  • May need to allow 127.0.0.1/localhost for development/testing
  • Consider making blocklist configurable via environment variable

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions