{"title":"Open Redirect via urllib.parse.urljoin on User-Controlled Path","language":"Python","severity":"Medium","cwe":"CWE-601","source_lines":[9],"flow_lines":[9,11,12],"sink_lines":[12],"vulnerable_code":"from flask import Flask, request, redirect\nfrom urllib.parse import urljoin\n\napp = Flask(__name__)\n\n@app.route('/iot/device/reboot')\ndef device_reboot_handler():\n    device_id = request.args.get('device_id')\n    callback_endpoint = request.args.get('return_to', '/')\n    base_dashboard = request.host_url\n    redirect_target = urljoin(base_dashboard, callback_endpoint)\n    perform_device_reboot(device_id)\n    return redirect(redirect_target, code=302)\n\ndef perform_device_reboot(dev_id):\n    pass","explanation":"The application accepts user-controlled input from the 'return_to' parameter and uses it with urljoin() to construct a redirect URL. An attacker can provide an absolute URL (e.g., http://evil.com) which urljoin will use as-is, bypassing the base_dashboard and redirecting victims to a malicious site.","remediation":"The fix validates the constructed redirect URL by parsing both the base dashboard URL and the redirect target, then comparing their netloc (host) components. If the redirect target points to a different host than the application's own host, the redirect defaults to the base dashboard URL, preventing open redirect attacks.","secure_code":"from flask import Flask, request, redirect\nfrom urllib.parse import urljoin, urlparse\n\napp = Flask(__name__)\n\n@app.route('/iot/device/reboot')\ndef device_reboot_handler():\n    device_id = request.args.get('device_id')\n    callback_endpoint = request.args.get('return_to', '/')\n    base_dashboard = request.host_url\n    redirect_target = urljoin(base_dashboard, callback_endpoint)\n    \n    # Validate that the redirect target stays on the same host\n    parsed_base = urlparse(base_dashboard)\n    parsed_target = urlparse(redirect_target)\n    if parsed_target.netloc != parsed_base.netloc:\n        redirect_target = base_dashboard\n    \n    perform_device_reboot(device_id)\n    return redirect(redirect_target, code=302)\n\ndef perform_device_reboot(dev_id):\n    pass"}