313 lines
12 KiB
Python
313 lines
12 KiB
Python
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
|
|
"<script>alert('xss')</script>", # 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')}")
|