Reject protocol-relative URLs in next redirect validation
This commit is contained in:
@@ -23,6 +23,15 @@ def _parse_date(val: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _safe_next(default_url):
|
||||
"""Return the form's `next` URL only if it is a local path (rejects
|
||||
protocol-relative `//host` and `/\\host` redirects)."""
|
||||
nxt = request.form.get("next", "")
|
||||
if nxt.startswith("/") and not nxt.startswith("//") and not nxt.startswith("/\\"):
|
||||
return nxt
|
||||
return default_url
|
||||
|
||||
|
||||
def _record_charge(db, battery, date_val, increment, notes):
|
||||
"""Apply one charge event to battery. Caller must call db.commit()."""
|
||||
if increment:
|
||||
@@ -452,8 +461,7 @@ def create_app(config_object="config"):
|
||||
battery.device_id = None
|
||||
db.commit()
|
||||
flash(f"{battery.label} unassigned and marked available.", "success")
|
||||
next_url = request.form.get("next", "")
|
||||
return redirect(next_url if next_url.startswith("/") else url_for("dashboard"))
|
||||
return redirect(_safe_next(url_for("dashboard")))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — retire
|
||||
@@ -1082,10 +1090,7 @@ def create_app(config_object="config"):
|
||||
f"Unassigned {count} batter{'y' if count == 1 else 'ies'} from {device.name}.",
|
||||
"success",
|
||||
)
|
||||
nxt = request.form.get("next", "")
|
||||
if nxt.startswith("/"):
|
||||
return redirect(nxt)
|
||||
return redirect(url_for("device_list"))
|
||||
return redirect(_safe_next(url_for("device_list")))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Devices — batch install specific batteries
|
||||
|
||||
Reference in New Issue
Block a user