{"title":"SSRF via requests.get() on Untrusted URL","language":"Python","severity":"High","cwe":"CWE-918","source_lines":[8],"flow_lines":[8,11],"sink_lines":[11],"vulnerable_code":"import requests\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/iot/device-status', methods=['POST'])\ndef fetch_device_telemetry():\n    device_endpoint = request.json.get('telemetry_url')\n    auth_token = request.json.get('token', 'default-key')\n    headers = {'Authorization': f'Bearer {auth_token}'}\n    response = requests.get(device_endpoint, headers=headers, timeout=5)\n    return jsonify({'status': 'success', 'data': response.json(), 'code': response.status_code})","explanation":"The application accepts a user-controlled 'telemetry_url' parameter without validation and directly passes it to requests.get(). This enables Server-Side Request Forgery (SSRF) attacks where attackers can force the server to make requests to internal resources, cloud metadata services (e.g., http://169.254.169.254/latest/meta-data/), or other restricted endpoints that should not be accessible externally.","remediation":"The fix introduces a URL validation function that enforces an allowlist of permitted IoT device domains, blocks requests to private/internal IP ranges (including cloud metadata endpoints like 169.254.169.254), only permits HTTP/HTTPS schemes, performs DNS resolution to detect DNS rebinding attacks targeting internal IPs, and disables redirects to prevent redirect-based SSRF bypasses.","secure_code":"import requests\nfrom flask import Flask, request, jsonify\nfrom urllib.parse import urlparse\nimport ipaddress\nimport socket\n\napp = Flask(__name__)\n\n# Allowlist of permitted device endpoint hostnames/domains\nALLOWED_DOMAINS = [\n    '.device.iot.internal',\n    '.iot-telemetry.example.com',\n]\n\n# Blocked IP ranges (internal/private networks and link-local)\nBLOCKED_NETWORKS = [\n    ipaddress.ip_network('10.0.0.0/8'),\n    ipaddress.ip_network('172.16.0.0/12'),\n    ipaddress.ip_network('192.168.0.0/16'),\n    ipaddress.ip_network('127.0.0.0/8'),\n    ipaddress.ip_network('169.254.0.0/16'),\n    ipaddress.ip_network('0.0.0.0/8'),\n    ipaddress.ip_network('::1/128'),\n    ipaddress.ip_network('fc00::/7'),\n    ipaddress.ip_network('fe80::/10'),\n]\n\n\ndef is_valid_telemetry_url(url):\n    \"\"\"Validate that the URL is safe to request (not targeting internal resources).\"\"\"\n    if not url:\n        return False, \"Missing URL\"\n\n    try:\n        parsed = urlparse(url)\n    except Exception:\n        return False, \"Invalid URL format\"\n\n    # Only allow http and https schemes\n    if parsed.scheme not in ('http', 'https'):\n        return False, \"Only HTTP and HTTPS schemes are allowed\"\n\n    # Ensure hostname is present\n    hostname = parsed.hostname\n    if not hostname:\n        return False, \"Missing hostname\"\n\n    # Check against allowed domains\n    domain_allowed = any(hostname.endswith(domain) for domain in ALLOWED_DOMAINS)\n    if not domain_allowed:\n        return False, f\"Domain '{hostname}' is not in the allowlist of permitted IoT device endpoints\"\n\n    # Resolve the hostname and check against blocked IP ranges\n    try:\n        resolved_ips = socket.getaddrinfo(hostname, parsed.port or 443, proto=socket.IPPROTO_TCP)\n    except socket.gaierror:\n        return False, \"Unable to resolve hostname\"\n\n    for result in resolved_ips:\n        ip_str = result[4][0]\n        try:\n            ip = ipaddress.ip_address(ip_str)\n            for blocked_net in BLOCKED_NETWORKS:\n                if ip in blocked_net:\n                    return False, f\"Resolved IP {ip_str} is in a blocked network range\"\n        except ValueError:\n            return False, \"Invalid resolved IP address\"\n\n    return True, None\n\n\n@app.route('/api/iot/device-status', methods=['POST'])\ndef fetch_device_telemetry():\n    if not request.json:\n        return jsonify({'status': 'error', 'message': 'Request body must be JSON'}), 400\n\n    device_endpoint = request.json.get('telemetry_url')\n    auth_token = request.json.get('token', 'default-key')\n\n    # Validate the URL before making the request\n    is_valid, error_message = is_valid_telemetry_url(device_endpoint)\n    if not is_valid:\n        return jsonify({'status': 'error', 'message': f'Invalid telemetry URL: {error_message}'}), 400\n\n    headers = {'Authorization': f'Bearer {auth_token}'}\n\n    try:\n        response = requests.get(device_endpoint, headers=headers, timeout=5, allow_redirects=False)\n        return jsonify({'status': 'success', 'data': response.json(), 'code': response.status_code})\n    except requests.exceptions.RequestException as e:\n        return jsonify({'status': 'error', 'message': f'Failed to reach device: {str(e)}'}), 502"}