# Open Redirect via urllib.parse.urljoin on User-Controlled Path

Language: Python
Severity: Medium
CWE: CWE-601

## Source
9

## Flow
9-11-12

## Sink
12

## Vulnerable Code
```python
from flask import Flask, request, redirect
from urllib.parse import urljoin

app = Flask(__name__)

@app.route('/iot/device/reboot')
def device_reboot_handler():
    device_id = request.args.get('device_id')
    callback_endpoint = request.args.get('return_to', '/')
    base_dashboard = request.host_url
    redirect_target = urljoin(base_dashboard, callback_endpoint)
    perform_device_reboot(device_id)
    return redirect(redirect_target, code=302)

def perform_device_reboot(dev_id):
    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
```python
from flask import Flask, request, redirect
from urllib.parse import urljoin, urlparse

app = Flask(__name__)

@app.route('/iot/device/reboot')
def device_reboot_handler():
    device_id = request.args.get('device_id')
    callback_endpoint = request.args.get('return_to', '/')
    base_dashboard = request.host_url
    redirect_target = urljoin(base_dashboard, callback_endpoint)
    
    # Validate that the redirect target stays on the same host
    parsed_base = urlparse(base_dashboard)
    parsed_target = urlparse(redirect_target)
    if parsed_target.netloc != parsed_base.netloc:
        redirect_target = base_dashboard
    
    perform_device_reboot(device_id)
    return redirect(redirect_target, code=302)

def perform_device_reboot(dev_id):
    pass
```
