F-10/F-11 + Verwaltungsskript
- F-10 Open Redirect: is_safe_url()-Pruefung des next-Parameters beim Login; login_required gibt next weiter, Login-Formular traegt es als Hidden-Feld. Externe/protokoll-relative Ziele werden ignoriert -> Dashboard. - F-11 Info-Leak: eigene Fehlerseiten (400/403/404/405/500) ohne Framework- Hinweis oder Stacktrace (templates/error.html). - manage.sh: 'unlock' (Brute-Force-Sperren aufheben) und 'reset-password' (Admin-Passwort setzen/zufaellig erzeugen) via docker-compose run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env sh
|
||||
# Verwaltungsskript für den DynDNS Manager:
|
||||
# - Login-/DynDNS-Sperren aufheben (Brute-Force-Lockout)
|
||||
# - Admin-Passwort zurücksetzen
|
||||
#
|
||||
# Läuft über einen kurzlebigen Container (docker-compose run --rm) und greift
|
||||
# damit auf dieselbe SQLite-DB (./data) zu — funktioniert auch, wenn der
|
||||
# laufende Container gerade gestoppt ist.
|
||||
|
||||
set -eu
|
||||
|
||||
SERVICE=dyndns
|
||||
|
||||
# In das Skriptverzeichnis wechseln (dort liegt die docker-compose.yml)
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# docker compose v2 ("docker compose") oder v1 ("docker-compose")?
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
DC="docker compose"
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
DC="docker-compose"
|
||||
else
|
||||
echo "Fehler: weder 'docker compose' noch 'docker-compose' gefunden." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Verwaltung DynDNS Manager
|
||||
|
||||
$0 unlock [IP]
|
||||
Hebt Login-/DynDNS-Sperren auf. Ohne IP werden ALLE Sperren entfernt,
|
||||
mit IP nur die Einträge dieser Client-IP (Login und DynDNS).
|
||||
|
||||
$0 reset-password [BENUTZER] [PASSWORT]
|
||||
Setzt das Admin-Passwort. BENUTZER ist standardmäßig "admin".
|
||||
Ohne PASSWORT wird ein sicheres Zufallspasswort erzeugt und angezeigt.
|
||||
|
||||
Beispiele:
|
||||
$0 unlock
|
||||
$0 unlock 203.0.113.7
|
||||
$0 reset-password
|
||||
$0 reset-password admin MeinNeuesPW
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
unlock)
|
||||
IP="${2:-}"
|
||||
$DC run --rm -T -e IP="$IP" "$SERVICE" python - <<'PY'
|
||||
import os
|
||||
from database import get_db
|
||||
ip = os.environ.get('IP', '').strip()
|
||||
db = get_db()
|
||||
if ip:
|
||||
cur = db.execute("DELETE FROM login_attempts WHERE ip = ? OR ip = ?",
|
||||
('login:' + ip, 'dyndns:' + ip))
|
||||
else:
|
||||
cur = db.execute("DELETE FROM login_attempts")
|
||||
db.commit()
|
||||
n = cur.rowcount
|
||||
db.close()
|
||||
print(f"{n} Sperr-Eintrag/Einträge entfernt" + (f" für {ip}." if ip else " (alle)."))
|
||||
PY
|
||||
;;
|
||||
|
||||
reset-password)
|
||||
RESET_USER="${2:-admin}"
|
||||
RESET_PW="${3:-}"
|
||||
$DC run --rm -T -e RESET_USER="$RESET_USER" -e RESET_PW="$RESET_PW" "$SERVICE" python - <<'PY'
|
||||
import os
|
||||
import secrets
|
||||
from database import get_db
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
user = os.environ.get('RESET_USER') or 'admin'
|
||||
pw = os.environ.get('RESET_PW') or ''
|
||||
generated = False
|
||||
if not pw:
|
||||
pw = secrets.token_urlsafe(12)
|
||||
generated = True
|
||||
|
||||
db = get_db()
|
||||
cur = db.execute("UPDATE admin_users SET password_hash = ? WHERE username = ?",
|
||||
(generate_password_hash(pw), user))
|
||||
db.commit()
|
||||
n = cur.rowcount
|
||||
db.close()
|
||||
|
||||
if n == 0:
|
||||
raise SystemExit(f"FEHLER: Admin-Benutzer '{user}' nicht gefunden.")
|
||||
print(f"Passwort für '{user}' gesetzt.")
|
||||
if generated:
|
||||
print(f"Neues Passwort: {pw}")
|
||||
PY
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user