{"title":"SSRF via User-Controlled URL in requests.get()","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('device_token', 'default-token')\n    headers = {'Authorization': f'Bearer {auth_token}', 'User-Agent': 'IoT-Gateway/2.1'}\n    telemetry_response = requests.get(device_endpoint, headers=headers, timeout=10)\n    return jsonify({'status': 'success', 'data': telemetry_response.json(), 'code': telemetry_response.status_code})","explanation":"The application accepts a user-controlled 'telemetry_url' parameter without validation and directly passes it to requests.get(). This allows attackers to perform Server-Side Request Forgery (SSRF) attacks, potentially accessing internal services, cloud metadata endpoints, or performing port scanning on internal networks.","remediation":"The fix implements a comprehensive URL validation function that checks the scheme (only HTTPS allowed), restricts allowed ports, resolves the hostname to IP addresses, and verifies none of them belong to private/internal/link-local networks. Additionally, redirects are disabled to prevent redirect-based SSRF bypasses, and proper error handling is added.","secure_code":"import requests\nimport ipaddress\nimport socket\nfrom urllib.parse import urlparse\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_SCHEMES = {'https'}\nALLOWED_PORTS = {443, 8443, 8883}\nBLOCKED_NETWORKS = [\n    ipaddress.ip_network('127.0.0.0/8'),\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('169.254.0.0/16'),\n    ipaddress.ip_network('0.0.0.0/8'),\n    ipaddress.ip_network('100.64.0.0/10'),\n    ipaddress.ip_network('198.18.0.0/15'),\n    ipaddress.ip_network('::1/128'),\n    ipaddress.ip_network('fc00::/7'),\n    ipaddress.ip_network('fe80::/10'),\n]\n\n\ndef is_safe_url(url):\n    \"\"\"Validate that the URL is safe to request (not targeting internal resources).\"\"\"\n    if not url or not isinstance(url, str):\n        return False, \"Invalid URL provided\"\n\n    try:\n        parsed = urlparse(url)\n    except Exception:\n        return False, \"Malformed URL\"\n\n    if parsed.scheme not in ALLOWED_SCHEMES:\n        return False, f\"Scheme '{parsed.scheme}' not allowed. Only {ALLOWED_SCHEMES} permitted.\"\n\n    hostname = parsed.hostname\n    if not hostname:\n        return False, \"No hostname found in URL\"\n\n    port = parsed.port or (443 if parsed.scheme == 'https' else 80)\n    if port not in ALLOWED_PORTS:\n        return False, f\"Port {port} not allowed. Only {ALLOWED_PORTS} permitted.\"\n\n    try:\n        addrinfos = socket.getaddrinfo(hostname, port, proto=socket.IPPROTO_TCP)\n    except socket.gaierror:\n        return False, \"Unable to resolve hostname\"\n\n    for addrinfo in addrinfos:\n        ip = ipaddress.ip_address(addrinfo[4][0])\n        for blocked_network in BLOCKED_NETWORKS:\n            if ip in blocked_network:\n                return False, \"Access to internal/private network addresses is forbidden\"\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': 'JSON body required'}), 400\n\n    device_endpoint = request.json.get('telemetry_url')\n    auth_token = request.json.get('device_token', 'default-token')\n\n    is_safe, error_message = is_safe_url(device_endpoint)\n    if not is_safe:\n        return jsonify({'status': 'error', 'message': error_message}), 400\n\n    headers = {'Authorization': f'Bearer {auth_token}', 'User-Agent': 'IoT-Gateway/2.1'}\n\n    try:\n        telemetry_response = requests.get(\n            device_endpoint,\n            headers=headers,\n            timeout=10,\n            allow_redirects=False\n        )\n        return jsonify({\n            'status': 'success',\n            'data': telemetry_response.json(),\n            'code': telemetry_response.status_code\n        })\n    except requests.exceptions.RequestException as e:\n        return jsonify({'status': 'error', 'message': 'Failed to fetch device telemetry'}), 502"}