diff --git a/pentest/enumerate.py b/pentest/enumerate.py new file mode 100644 index 0000000..7878fed --- /dev/null +++ b/pentest/enumerate.py @@ -0,0 +1,187 @@ +import requests +import uuid +import time +import json +from datetime import datetime + +def enumerate_supabase_users(): + base_url = "http://localhost:3000/api/v1/public/user-player" + found_users = [] + total_requests = 0 + + print("🔍 STARTE USER ENUMERATION ÜBER SUPABASE USER IDS") + print("=" * 60) + print(f"Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Target: {base_url}") + print("=" * 60) + + # Teste verschiedene UUID-Patterns + test_uuids = [ + str(uuid.uuid4()) for _ in range(1000) # Zufällige UUIDs + ] + + print(f"📊 Teste {len(test_uuids)} UUIDs...") + print("-" * 60) + + for i, uuid_str in enumerate(test_uuids, 1): + try: + response = requests.get(f"{base_url}/{uuid_str}", timeout=5) + total_requests += 1 + + if response.status_code == 200: + user_data = response.json() + if user_data.get("success"): + found_users.append(user_data["data"]) + user = user_data["data"] + print(f"✅ [{i:4d}] USER GEFUNDEN!") + print(f" UUID: {uuid_str}") + print(f" Name: {user['firstname']} {user['lastname']}") + print(f" ID: {user['id']}") + print(f" RFID: {user['rfiduid']}") + print(f" Geburtsdatum: {user['birthdate']}") + print(f" Leaderboard: {user['show_in_leaderboard']}") + print("-" * 60) + else: + if i % 100 == 0: # Fortschritt alle 100 Requests + print(f"⏳ [{i:4d}] Kein User gefunden (Fortschritt: {i}/{len(test_uuids)})") + else: + if i % 100 == 0: + print(f"❌ [{i:4d}] HTTP {response.status_code} (Fortschritt: {i}/{len(test_uuids)})") + + except requests.exceptions.RequestException as e: + print(f"🔥 [{i:4d}] Fehler bei UUID {uuid_str}: {e}") + continue + + print("\n" + "=" * 60) + print("📈 ENUMERATION ABGESCHLOSSEN") + print("=" * 60) + print(f"Total Requests: {total_requests}") + print(f"Gefundene Users: {len(found_users)}") + print(f"Erfolgsrate: {(len(found_users)/total_requests*100):.2f}%" if total_requests > 0 else "0%") + + if found_users: + print("\n🎯 GEFUNDENE USERS:") + print("-" * 60) + for i, user in enumerate(found_users, 1): + print(f"{i}. {user['firstname']} {user['lastname']}") + print(f" ID: {user['id']} | RFID: {user['rfiduid']} | Geburtstag: {user['birthdate']}") + print("-" * 60) + + # Speichere Ergebnisse in Datei + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"enumerated_users_{timestamp}.json" + with open(filename, 'w', encoding='utf-8') as f: + json.dump(found_users, f, indent=2, ensure_ascii=False) + print(f"💾 Ergebnisse gespeichert in: {filename}") + else: + print("\n❌ Keine Users gefunden") + + print(f"\n⏰ Abgeschlossen um: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + return found_users + +def enumerate_rfid_uids(api_key, max_attempts=100): + """RFID UID Enumeration (benötigt gültigen API-Key)""" + base_url = "http://localhost:3000/api/v1/private/users/find" + found_rfids = [] + + print("\n🔍 STARTE RFID UID ENUMERATION") + print("=" * 60) + print(f"Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Target: {base_url}") + print(f"API-Key: {api_key[:10]}...") + print("=" * 60) + + # Generiere RFID UIDs zum Testen + for i in range(1, max_attempts + 1): + # Generiere RFID im Format AA:BB:CC:XX + rfid_uid = f"AA:BB:CC:{i:02X}" + + try: + response = requests.post( + base_url, + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + }, + json={"uid": rfid_uid}, + timeout=5 + ) + + if response.status_code == 200: + data = response.json() + if data.get("success") and data.get("data", {}).get("exists"): + found_rfids.append(data["data"]) + user = data["data"] + print(f"✅ [{i:3d}] RFID GEFUNDEN!") + print(f" RFID: {rfid_uid}") + print(f" Name: {user['firstname']} {user['lastname']}") + print(f" Alter: {user['alter']}") + print("-" * 60) + else: + if i % 20 == 0: # Fortschritt alle 20 Requests + print(f"⏳ [{i:3d}] Kein User für RFID {rfid_uid}") + else: + print(f"❌ [{i:3d}] HTTP {response.status_code} für RFID {rfid_uid}") + + except requests.exceptions.RequestException as e: + print(f"🔥 [{i:3d}] Fehler bei RFID {rfid_uid}: {e}") + continue + + print("\n📈 RFID ENUMERATION ABGESCHLOSSEN") + print(f"Gefundene RFIDs: {len(found_rfids)}") + + return found_rfids + +def test_admin_login(): + """Teste Admin Login Enumeration""" + base_url = "http://localhost:3000/api/v1/public/login" + + # Häufige Admin-Usernamen + admin_usernames = [ + "admin", "administrator", "root", "user", "test", "demo", + "admin1", "admin2", "superuser", "manager", "operator" + ] + + print("\n🔍 TESTE ADMIN LOGIN ENUMERATION") + print("=" * 60) + + for username in admin_usernames: + try: + start_time = time.time() + response = requests.post( + base_url, + json={"username": username, "password": "wrongpassword"}, + timeout=5 + ) + end_time = time.time() + response_time = (end_time - start_time) * 1000 # in ms + + print(f"👤 {username:12} | Status: {response.status_code:3d} | Zeit: {response_time:6.1f}ms") + + if response.status_code == 200: + print(f" ⚠️ MÖGLICHERWEISE GÜLTIGER USERNAME!") + + except Exception as e: + print(f"🔥 Fehler bei {username}: {e}") + +# Führe Enumeration aus +if __name__ == "__main__": + print("🚨 NINJA SERVER SECURITY AUDIT - USER ENUMERATION") + print("⚠️ WARNUNG: Nur für autorisierte Sicherheitstests!") + print() + + # 1. Supabase User ID Enumeration + found_users = enumerate_supabase_users() + + # 2. Admin Login Test + test_admin_login() + + # 3. RFID Enumeration (nur mit gültigem API-Key) + api_key = input("\n🔑 API-Key für RFID Enumeration eingeben (oder Enter zum Überspringen): ").strip() + if api_key: + enumerate_rfid_uids(api_key, 50) # Teste nur 50 RFIDs + else: + print("⏭️ RFID Enumeration übersprungen") + + print("\n🏁 AUDIT ABGESCHLOSSEN") \ No newline at end of file diff --git a/pentest/realistic_enumeration.py b/pentest/realistic_enumeration.py new file mode 100644 index 0000000..f1c2f31 --- /dev/null +++ b/pentest/realistic_enumeration.py @@ -0,0 +1,312 @@ +import requests +import time +import json +from datetime import datetime +import statistics + +def test_admin_login_timing(): + """Detaillierte Timing-Analyse für Admin Login""" + base_url = "http://localhost:3000/api/v1/public/login" + + # Erweiterte Liste von Admin-Usernamen + admin_usernames = [ + "admin", "administrator", "root", "user", "test", "demo", + "admin1", "admin2", "superuser", "manager", "operator", + "ninja", "parkour", "system", "api", "service", + "backup", "support", "helpdesk", "it", "tech" + ] + + print("🔍 DETAILLIERTE ADMIN LOGIN TIMING-ANALYSE") + print("=" * 70) + print(f"Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Target: {base_url}") + print("=" * 70) + + results = [] + + # Teste jeden Username mehrfach für bessere Statistik + for username in admin_usernames: + times = [] + + print(f"\n👤 Testing: {username}") + print("-" * 50) + + for attempt in range(5): # 5 Versuche pro Username + try: + start_time = time.time() + response = requests.post( + base_url, + json={"username": username, "password": "wrongpassword123"}, + timeout=10 + ) + end_time = time.time() + response_time = (end_time - start_time) * 1000 # in ms + times.append(response_time) + + print(f" Attempt {attempt+1}: {response_time:6.1f}ms | Status: {response.status_code}") + + # Kleine Pause zwischen Requests + time.sleep(0.1) + + except Exception as e: + print(f" Attempt {attempt+1}: ERROR - {e}") + continue + + if times: + avg_time = statistics.mean(times) + std_dev = statistics.stdev(times) if len(times) > 1 else 0 + min_time = min(times) + max_time = max(times) + + results.append({ + 'username': username, + 'avg_time': avg_time, + 'std_dev': std_dev, + 'min_time': min_time, + 'max_time': max_time, + 'times': times, + 'suspicious': avg_time > 50 # Verdächtig wenn > 50ms + }) + + print(f" 📊 Stats: Avg={avg_time:.1f}ms, Std={std_dev:.1f}ms, Range={min_time:.1f}-{max_time:.1f}ms") + + if avg_time > 50: + print(f" ⚠️ SUSPEKT: Deutlich längere Response-Zeit!") + + # Sortiere nach durchschnittlicher Response-Zeit + results.sort(key=lambda x: x['avg_time'], reverse=True) + + print("\n" + "=" * 70) + print("📈 TIMING-ANALYSE ERGEBNISSE") + print("=" * 70) + + print(f"{'Username':<15} {'Avg(ms)':<8} {'Std(ms)':<8} {'Min(ms)':<8} {'Max(ms)':<8} {'Status'}") + print("-" * 70) + + for result in results: + status = "⚠️ SUSPEKT" if result['suspicious'] else "✅ Normal" + print(f"{result['username']:<15} {result['avg_time']:<8.1f} {result['std_dev']:<8.1f} " + f"{result['min_time']:<8.1f} {result['max_time']:<8.1f} {status}") + + # Speichere detaillierte Ergebnisse + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"timing_analysis_{timestamp}.json" + with open(filename, 'w', encoding='utf-8') as f: + json.dump(results, f, indent=2, ensure_ascii=False) + print(f"\n💾 Detaillierte Ergebnisse gespeichert in: {filename}") + + return results + +def test_rfid_creation_enumeration(): + """Teste RFID Enumeration über Spieler-Erstellung""" + base_url = "http://localhost:3000/api/v1/public/players/create-with-rfid" + + print("\n🔍 RFID ENUMERATION ÜBER SPIELER-ERSTELLUNG") + print("=" * 70) + print(f"Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Target: {base_url}") + print("=" * 70) + + found_rfids = [] + + # Teste verschiedene RFID-Patterns + test_patterns = [ + "AA:BB:CC:DD", "AA:BB:CC:DE", "AA:BB:CC:DF", + "11:22:33:44", "11:22:33:45", "11:22:33:46", + "FF:FF:FF:FF", "00:00:00:00", "12:34:56:78", + "AB:CD:EF:12", "DE:AD:BE:EF", "CA:FE:BA:BE" + ] + + for i, rfid in enumerate(test_patterns, 1): + try: + payload = { + "rfiduid": rfid, + "firstname": "Test", + "lastname": "User", + "birthdate": "1990-01-01" + } + + response = requests.post(base_url, json=payload, timeout=5) + + print(f"[{i:2d}] RFID: {rfid:<12} | Status: {response.status_code:3d}", end="") + + if response.status_code == 400: + try: + data = response.json() + if "existiert bereits" in data.get("message", ""): + print(" | ✅ EXISTIERT!") + if "existingPlayer" in data.get("details", {}): + existing = data["details"]["existingPlayer"] + found_rfids.append({ + "rfid": rfid, + "existing_player": existing + }) + print(f" → Name: {existing.get('firstname')} {existing.get('lastname')}") + else: + print(" | ❌ Anderer Fehler") + except: + print(" | ❌ JSON Parse Error") + else: + print(" | ⚠️ Unexpected Status") + + except Exception as e: + print(f"[{i:2d}] RFID: {rfid:<12} | ERROR: {e}") + + print(f"\n📊 RFID Enumeration abgeschlossen: {len(found_rfids)} gefunden") + return found_rfids + +def test_leaderboard_data_leak(): + """Teste Leaderboard auf sensible Daten""" + base_url = "http://localhost:3000/api/v1/public/times-with-details" + + print("\n🔍 LEADERBOARD DATENLEAK-TEST") + print("=" * 70) + print(f"Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Target: {base_url}") + print("=" * 70) + + try: + response = requests.get(base_url, timeout=10) + + print(f"Status: {response.status_code}") + + if response.status_code == 200: + data = response.json() + + if isinstance(data, list) and len(data) > 0: + print(f"✅ Leaderboard-Daten gefunden: {len(data)} Einträge") + + # Analysiere erste paar Einträge + for i, entry in enumerate(data[:3]): + print(f"\n📋 Eintrag {i+1}:") + if 'player' in entry: + player = entry['player'] + print(f" Name: {player.get('firstname')} {player.get('lastname')}") + print(f" RFID: {player.get('rfiduid')}") + print(f" ID: {player.get('id')}") + + if 'location' in entry: + location = entry['location'] + print(f" Location: {location.get('name')}") + print(f" Koordinaten: {location.get('latitude')}, {location.get('longitude')}") + + if 'recorded_time_seconds' in entry: + print(f" Zeit: {entry['recorded_time_seconds']} Sekunden") + + # Speichere alle Daten + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"leaderboard_data_{timestamp}.json" + with open(filename, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + print(f"\n💾 Leaderboard-Daten gespeichert in: {filename}") + + return data + else: + print("❌ Keine Leaderboard-Daten gefunden") + else: + print(f"❌ Fehler: HTTP {response.status_code}") + + except Exception as e: + print(f"🔥 Fehler beim Abrufen der Leaderboard-Daten: {e}") + + return None + +def test_error_message_analysis(): + """Analysiere Error Messages auf Information Leakage""" + base_url = "http://localhost:3000/api/v1/public/user-player" + + print("\n🔍 ERROR MESSAGE ANALYSE") + print("=" * 70) + + test_uuids = [ + "00000000-0000-0000-0000-000000000000", # Null UUID + "invalid-uuid-format", # Ungültiges Format + "12345678-1234-1234-1234-123456789012", # Gültiges Format, aber wahrscheinlich nicht existent + "../../../etc/passwd", # Path Traversal + "", # XSS Test + "'; DROP TABLE players; --" # SQL Injection Test + ] + + error_responses = {} + + for i, test_input in enumerate(test_uuids, 1): + try: + response = requests.get(f"{base_url}/{test_input}", timeout=5) + + status_code = response.status_code + + print(f"[{i}] Input: {test_input:<30} | Status: {status_code}") + + if status_code not in error_responses: + error_responses[status_code] = [] + + try: + json_data = response.json() + error_responses[status_code].append({ + 'input': test_input, + 'response': json_data + }) + except: + error_responses[status_code].append({ + 'input': test_input, + 'response': response.text[:200] # Erste 200 Zeichen + }) + + except Exception as e: + print(f"[{i}] Input: {test_input:<30} | ERROR: {e}") + + # Analysiere verschiedene Error-Messages + print(f"\n📊 Error-Message Analyse:") + print("-" * 50) + + for status_code, responses in error_responses.items(): + print(f"Status {status_code}: {len(responses)} Responses") + + # Prüfe auf unterschiedliche Error-Messages + unique_messages = set() + for resp in responses: + if isinstance(resp['response'], dict): + message = resp['response'].get('message', 'No message') + else: + message = str(resp['response'])[:100] + unique_messages.add(message) + + print(f" Unique messages: {len(unique_messages)}") + for msg in list(unique_messages)[:3]: # Zeige erste 3 + print(f" - {msg}") + + return error_responses + +if __name__ == "__main__": + print("🚨 NINJA SERVER - REALISTISCHE SICHERHEITSTESTS") + print("⚠️ WARNUNG: Nur für autorisierte Sicherheitstests!") + print() + + # 1. Admin Login Timing Analysis + timing_results = test_admin_login_timing() + + # 2. RFID Enumeration über Spieler-Erstellung + rfid_results = test_rfid_creation_enumeration() + + # 3. Leaderboard Datenleak-Test + leaderboard_data = test_leaderboard_data_leak() + + # 4. Error Message Analysis + error_analysis = test_error_message_analysis() + + print("\n" + "=" * 70) + print("🏁 REALISTISCHE SICHERHEITSTESTS ABGESCHLOSSEN") + print("=" * 70) + + # Zusammenfassung + suspicious_users = [r for r in timing_results if r['suspicious']] + print(f"🔍 Timing-Suspicious Users: {len(suspicious_users)}") + print(f"🔍 Gefundene RFIDs: {len(rfid_results)}") + print(f"🔍 Leaderboard-Einträge: {len(leaderboard_data) if leaderboard_data else 0}") + + if suspicious_users: + print(f"\n⚠️ SUSPEKTE USERNAMES (Timing):") + for user in suspicious_users: + print(f" - {user['username']}: {user['avg_time']:.1f}ms") + + print(f"\n⏰ Abgeschlossen um: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") diff --git a/public/agb.html b/public/agb.html new file mode 100644 index 0000000..8be8b76 --- /dev/null +++ b/public/agb.html @@ -0,0 +1,239 @@ + + +
+ + +NinjaCross Parkour System
+Diese Allgemeinen Geschäftsbedingungen (AGB) gelten für die Nutzung des NinjaCross Parkour Systems + im Schwimmbad. Mit der Registrierung und Nutzung des Systems erkennen Sie diese AGB als verbindlich an.
+Wir erheben folgende personenbezogene Daten:
+Ihre Daten werden für folgende Zwecke verwendet:
+Das NinjaCross System verfügt über ein öffentlich zugängliches Leaderboard, das folgende Informationen anzeigt:
+Durch die Nutzung des Systems erklären Sie sich damit einverstanden, dass diese Daten öffentlich angezeigt werden.
+Sie haben das Recht, Auskunft über die zu Ihrer Person gespeicherten Daten zu verlangen.
+ +Sie können jederzeit die Löschung Ihrer Daten und Ihres Profils beantragen.
+ +Sie können der Verarbeitung Ihrer Daten für das Leaderboard widersprechen. + In diesem Fall werden Ihre Daten aus dem öffentlichen Leaderboard entfernt, + aber weiterhin für die Systemfunktionalität verwendet.
+Die Teilnahme am NinjaCross System erfolgt auf eigene Gefahr. Wir haften nicht für:
+Bei der Nutzung des Systems sind folgende Regeln zu beachten:
+Wir behalten uns vor, diese AGB zu ändern. Wesentliche Änderungen werden Ihnen + mitgeteilt und erfordern Ihre erneute Zustimmung.
+Bei Fragen zu diesen AGB oder zum Datenschutz wenden Sie sich an:
+
+ NinjaCross Team
+ Schwimmbad Ulm
+ E-Mail: info@ninjacross.de
+ Telefon: 0731-123456
+
Stand: September 2024
+Diese AGB sind Teil der Registrierung und gelten ab dem Zeitpunkt der Zustimmung.
+