91993eb487
- 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>
103 lines
2.6 KiB
Bash
Executable File
103 lines
2.6 KiB
Bash
Executable File
#!/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
|