Security-Seite: Reichweiten-/Access-Control-Check (nur Booleans+Anzahl, keine Werte) - Profil-Privinfelder, Foto-IDOR, LV-Plan, Directory, anonyme iCal, Klassenliste
This commit is contained in:
+85
@@ -420,6 +420,91 @@ class CISClient:
|
||||
self._profile_cache[uid] = (time.time(), prof)
|
||||
return prof
|
||||
|
||||
# ---------- Reichweiten-Check (Security) ----------
|
||||
PRIVATE_MARKERS = ("Geburtsdatum:", "Geburtsort:", "Hauptwohnsitz:",
|
||||
"Telefonnummer", "FH-Ausweis Status")
|
||||
|
||||
def privacy_probe(self, uid: str, own_uid: str, semester: dict) -> list[dict]:
|
||||
"""Access-Control-Check gegen EINE fremde Person.
|
||||
Returns nur Booleans/Anzahlen - niemals Feldwerte."""
|
||||
checks: list[dict] = []
|
||||
|
||||
def add(name: str, ok, info: str = ""):
|
||||
checks.append({"name": name, "ok": bool(ok), "info": info})
|
||||
|
||||
users = self.benutzer_all()
|
||||
add("Verzeichnis Autocomplete", len(users) > 500,
|
||||
f"{len(users)} Personen (Name+UID) mit 5 Requests")
|
||||
|
||||
prof = self.profile(uid) or {}
|
||||
nachname = prof.get("nachname") or ""
|
||||
html = ""
|
||||
try:
|
||||
html = self._get(f"{self.base}/private/profile/index.php?uid={uid}")
|
||||
except requests.HTTPError:
|
||||
pass
|
||||
t = re.sub(r"<[^>]+>", " ", html) if html else ""
|
||||
fremd = bool(nachname) and nachname in t and uid != own_uid
|
||||
priv = [m.rstrip(":") for m in self.PRIVATE_MARKERS if m in t] if fremd else []
|
||||
add("Fremde Privinfelder im Profil", priv,
|
||||
("LEAK: " + ", ".join(priv)) if priv else
|
||||
"gefiltert (nur oeffentliche Felder)" if fremd else "Ziel nicht ladbar")
|
||||
|
||||
pid = self.person_id_for(uid)
|
||||
photo_ok, photo_info = False, "kein person_id"
|
||||
if pid:
|
||||
try:
|
||||
r = self.s.get(f"{self.base}/public/bild.php?src=person&person_id={pid}",
|
||||
timeout=30)
|
||||
ct = (r.headers.get("Content-Type") or "").split(";")[0]
|
||||
photo_ok = r.status_code == 200 and ct.startswith("image/")
|
||||
photo_info = f"HTTP {r.status_code}, {ct}, {len(r.content)} Bytes"
|
||||
except requests.RequestException as e:
|
||||
photo_info = str(e)
|
||||
add("Profilfoto via person_id (IDOR)", photo_ok, photo_info)
|
||||
|
||||
trap_ok, trap_info = False, "-"
|
||||
if pid:
|
||||
try:
|
||||
h2 = self._get(f"{self.base}/private/profile/index.php?person_id={pid}")
|
||||
t2 = re.sub(r"<[^>]+>", " ", h2)
|
||||
trap_ok = bool(nachname) and nachname in t2 and own_uid not in t2
|
||||
trap_info = ("LEAK: fremdes Profil" if trap_ok else
|
||||
"Parameter ignoriert -> eigenes Profil")
|
||||
except requests.HTTPError as e:
|
||||
trap_info = f"HTTP {e}"
|
||||
add("Falle: person_id statt uid uebergeben", trap_ok, trap_info)
|
||||
|
||||
b, e = semester["begin"], semester["ende"]
|
||||
try:
|
||||
evs = self.student_events(uid, b, e)
|
||||
except Exception:
|
||||
evs = []
|
||||
add("Fremder LV-Plan (HTML/iCal)", len(evs) > 0,
|
||||
f"{len(evs)} Termine im aktuellen Semester")
|
||||
|
||||
try:
|
||||
r = requests.get(self._ical_url(self._ical_student_params(uid, b, e)),
|
||||
timeout=20, allow_redirects=False)
|
||||
body = r.text if r.status_code == 200 else ""
|
||||
anon = r.status_code == 200 and "BEGIN:VCALENDAR" in body
|
||||
add("iCal OHNE Login (anonym)", anon,
|
||||
f"HTTP {r.status_code} {'-> VCALENDAR' if anon else '(Auth noetig)'}")
|
||||
except requests.RequestException as ex:
|
||||
add("iCal OHNE Login (anonym)", False, str(ex))
|
||||
|
||||
sem, ver = prof.get("semester", ""), prof.get("verband", "")
|
||||
kz = next((x["kz"] for x in self.meta()["stgs"] if x["name"] == prof.get("stg")), "")
|
||||
if kz and sem and ver:
|
||||
try:
|
||||
n = len(self.namensliste(kz, sem, ver))
|
||||
except Exception:
|
||||
n = 0
|
||||
add("Klassenliste Name+Mail", n > 0, f"{n} Personen (1 Request)")
|
||||
else:
|
||||
add("Klassenliste Name+Mail", False, "n/a (Ziel ohne StG-Badaten)")
|
||||
return checks
|
||||
|
||||
# ---------- Verbände pro StG/Semester ----------
|
||||
def verbands_for(self, kz: str, sem: str) -> list[str]:
|
||||
fallback = ["V", "B", "G", "A", "C", "D", "E", "F"]
|
||||
|
||||
Reference in New Issue
Block a user