Files
Ninjaserver/wiki/Sicherheit.md
2025-09-23 14:13:24 +02:00

15 KiB

🔒 Sicherheit

Sicherheitsrichtlinien und Best Practices für das Ninja Cross Parkour System.

📋 Inhaltsverzeichnis

🛡️ Sicherheitsübersicht

Sicherheitsprinzipien

  • Defense in Depth - Mehrschichtige Sicherheit
  • Least Privilege - Minimale Berechtigungen
  • Zero Trust - Kein Vertrauen ohne Verifikation
  • Security by Design - Sicherheit von Anfang an

Bedrohungsmodell

  • Externe Angriffe - Unbefugter Zugriff von außen
  • Interne Bedrohungen - Missbrauch durch autorisierte Benutzer
  • Datenlecks - Unbefugte Offenlegung von Daten
  • Service-Ausfälle - Verfügbarkeitsprobleme

🔐 Authentifizierung

Passwort-Sicherheit

// Passwort-Hashing mit bcrypt
const bcrypt = require('bcrypt');

// Passwort hashen
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);

// Passwort verifizieren
const isValid = await bcrypt.compare(password, hashedPassword);

Passwort-Richtlinien

  • Mindestlänge: 12 Zeichen
  • Komplexität: Groß-/Kleinbuchstaben, Zahlen, Sonderzeichen
  • Keine Wörterbuchwörter
  • Regelmäßige Rotation (alle 90 Tage)

Multi-Faktor-Authentifizierung (MFA)

// TOTP-Implementierung
const speakeasy = require('speakeasy');

// Secret generieren
const secret = speakeasy.generateSecret({
  name: 'Ninja Parkour',
  account: 'admin@example.com'
});

// Token verifizieren
const verified = speakeasy.totp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: userToken,
  window: 2
});

Session-Management

// Sichere Session-Konfiguration
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000, // 24 Stunden
    sameSite: 'strict'
  }
}));

🔑 Autorisierung

Rollenbasierte Zugriffskontrolle (RBAC)

// Rollen definieren
const ROLES = {
  ADMIN: 'admin',
  MODERATOR: 'moderator',
  USER: 'user',
  GUEST: 'guest'
};

// Berechtigungen definieren
const PERMISSIONS = {
  READ_PLAYERS: 'read:players',
  WRITE_PLAYERS: 'write:players',
  DELETE_PLAYERS: 'delete:players',
  READ_TIMES: 'read:times',
  WRITE_TIMES: 'write:times',
  ADMIN_ACCESS: 'admin:access'
};

// Rollen-Berechtigungen
const rolePermissions = {
  [ROLES.ADMIN]: Object.values(PERMISSIONS),
  [ROLES.MODERATOR]: [
    PERMISSIONS.READ_PLAYERS,
    PERMISSIONS.WRITE_PLAYERS,
    PERMISSIONS.READ_TIMES,
    PERMISSIONS.WRITE_TIMES
  ],
  [ROLES.USER]: [
    PERMISSIONS.READ_PLAYERS,
    PERMISSIONS.READ_TIMES
  ]
};

API-Key-Management

// API-Key generieren
const generateAPIKey = () => {
  return crypto.randomBytes(32).toString('hex');
};

// API-Key validieren
const validateAPIKey = async (apiKey) => {
  const token = await db.query(
    'SELECT * FROM api_tokens WHERE token = $1 AND is_active = true',
    [apiKey]
  );
  return token.rows.length > 0;
};

// Berechtigungen prüfen
const checkPermission = (userRole, requiredPermission) => {
  const userPermissions = rolePermissions[userRole] || [];
  return userPermissions.includes(requiredPermission);
};

Middleware für Autorisierung

// Authentifizierung prüfen
const requireAuth = (req, res, next) => {
  if (!req.session || !req.session.userId) {
    return res.status(401).json({ error: 'Nicht authentifiziert' });
  }
  next();
};

// Rolle prüfen
const requireRole = (role) => {
  return (req, res, next) => {
    if (req.session.role !== role) {
      return res.status(403).json({ error: 'Keine Berechtigung' });
    }
    next();
  };
};

// Berechtigung prüfen
const requirePermission = (permission) => {
  return (req, res, next) => {
    const userRole = req.session.role;
    if (!checkPermission(userRole, permission)) {
      return res.status(403).json({ error: 'Keine Berechtigung' });
    }
    next();
  };
};

🛡️ Datenverschlüsselung

Verschlüsselung im Ruhezustand

// Datenbank-Verschlüsselung
const crypto = require('crypto');

// Verschlüsselung
const encrypt = (text, key) => {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipher('aes-256-cbc', key);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return iv.toString('hex') + ':' + encrypted;
};

// Entschlüsselung
const decrypt = (text, key) => {
  const textParts = text.split(':');
  const iv = Buffer.from(textParts.shift(), 'hex');
  const encryptedText = textParts.join(':');
  const decipher = crypto.createDecipher('aes-256-cbc', key);
  let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
};

Verschlüsselung in Bewegung

// HTTPS-Konfiguration
const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
  ciphers: [
    'ECDHE-RSA-AES256-GCM-SHA384',
    'ECDHE-RSA-AES128-GCM-SHA256',
    'ECDHE-RSA-AES256-SHA384',
    'ECDHE-RSA-AES128-SHA256'
  ].join(':'),
  honorCipherOrder: true
};

https.createServer(options, app).listen(443);

Passwort-Verschlüsselung

// Starke Passwort-Generierung
const generatePassword = (length = 16) => {
  const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
  let password = '';
  for (let i = 0; i < length; i++) {
    password += charset.charAt(Math.floor(Math.random() * charset.length));
  }
  return password;
};

// Passwort-Stärke prüfen
const validatePassword = (password) => {
  const minLength = 12;
  const hasUpperCase = /[A-Z]/.test(password);
  const hasLowerCase = /[a-z]/.test(password);
  const hasNumbers = /\d/.test(password);
  const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
  
  return password.length >= minLength && 
         hasUpperCase && 
         hasLowerCase && 
         hasNumbers && 
         hasSpecialChar;
};

🌐 Netzwerksicherheit

Firewall-Konfiguration

# UFW-Firewall (Ubuntu)
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Erlaubte Ports
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw allow 3000/tcp  # App (nur intern)

# Rate Limiting
sudo ufw limit ssh

DDoS-Schutz

// Rate Limiting
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 Minuten
  max: 100, // Max 100 Requests pro IP
  message: 'Zu viele Anfragen von dieser IP',
  standardHeaders: true,
  legacyHeaders: false
});

app.use('/api/', limiter);

// Strikte Rate Limits für sensible Endpoints
const strictLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Zu viele Login-Versuche'
});

app.use('/api/login', strictLimiter);

CORS-Konfiguration

// Sichere CORS-Einstellungen
const cors = require('cors');

const corsOptions = {
  origin: (origin, callback) => {
    const allowedOrigins = [
      'https://ninja.reptilfpv.de',
      'https://www.ninja.reptilfpv.de'
    ];
    
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Nicht erlaubt durch CORS'));
    }
  },
  credentials: true,
  optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

SSL/TLS-Konfiguration

// SSL-Redirect
app.use((req, res, next) => {
  if (req.header('x-forwarded-proto') !== 'https') {
    res.redirect(`https://${req.header('host')}${req.url}`);
  } else {
    next();
  }
});

// Security Headers
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

🗄️ Datenbanksicherheit

Verbindungssicherheit

// Sichere Datenbankverbindung
const dbConfig = {
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  ssl: {
    rejectUnauthorized: true,
    ca: fs.readFileSync('ca-certificate.pem')
  },
  connectionTimeoutMillis: 5000,
  idleTimeoutMillis: 30000,
  max: 20
};

SQL-Injection-Schutz

// Parametrisierte Queries
const getUser = async (userId) => {
  const query = 'SELECT * FROM players WHERE id = $1';
  const values = [userId];
  const result = await db.query(query, values);
  return result.rows[0];
};

// Input-Validierung
const validateInput = (input) => {
  if (typeof input !== 'string') {
    throw new Error('Invalid input type');
  }
  
  // SQL-Injection-Patterns erkennen
  const sqlPatterns = [
    /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi,
    /(\b(OR|AND)\s+\d+\s*=\s*\d+)/gi,
    /(\b(OR|AND)\s+['"]\s*=\s*['"])/gi
  ];
  
  for (const pattern of sqlPatterns) {
    if (pattern.test(input)) {
      throw new Error('Potential SQL injection detected');
    }
  }
  
  return input;
};

Datenbank-Berechtigungen

-- Benutzer mit minimalen Rechten erstellen
CREATE USER ninja_app WITH PASSWORD 'secure_password';

-- Nur notwendige Berechtigungen gewähren
GRANT SELECT, INSERT, UPDATE, DELETE ON players TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON times TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON locations TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON achievements TO ninja_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON player_achievements TO ninja_app;

-- Keine Admin-Rechte
REVOKE ALL ON SCHEMA public FROM ninja_app;

📱 API-Sicherheit

Input-Validierung

// Joi-Schema für Validierung
const Joi = require('joi');

const playerSchema = Joi.object({
  firstname: Joi.string().min(2).max(50).required(),
  lastname: Joi.string().min(2).max(50).required(),
  birthdate: Joi.date().max('now').required(),
  rfiduid: Joi.string().pattern(/^[A-F0-9:]{11}$/).optional()
});

const validatePlayer = (req, res, next) => {
  const { error } = playerSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ error: error.details[0].message });
  }
  next();
};

API-Versionierung

// API-Versionierung
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

// Deprecation-Warnungen
app.use('/api/v1', (req, res, next) => {
  res.set('X-API-Version', '1.0.0');
  res.set('X-API-Deprecated', 'false');
  next();
});

Request-Logging

// Sicherheitsrelevante Logs
const securityLogger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/security.log' })
  ]
});

// Login-Versuche loggen
app.post('/api/login', (req, res) => {
  const { username, ip } = req.body;
  securityLogger.info('Login attempt', { username, ip, timestamp: new Date() });
  // ... Login-Logik
});

🔍 Monitoring

Sicherheits-Monitoring

// Anomalie-Erkennung
const detectAnomalies = (req, res, next) => {
  const ip = req.ip;
  const userAgent = req.get('User-Agent');
  
  // Verdächtige Patterns erkennen
  const suspiciousPatterns = [
    /sqlmap/i,
    /nikto/i,
    /nmap/i,
    /masscan/i
  ];
  
  for (const pattern of suspiciousPatterns) {
    if (pattern.test(userAgent)) {
      securityLogger.warn('Suspicious user agent detected', { ip, userAgent });
      return res.status(403).json({ error: 'Request blocked' });
    }
  }
  
  next();
};

Log-Analyse

# Sicherheitsrelevante Logs analysieren
grep "ERROR\|WARN\|SECURITY" logs/server.log | tail -100

# Failed Login-Versuche
grep "Login attempt" logs/security.log | grep "failed"

# Verdächtige Aktivitäten
grep "suspicious\|anomaly\|attack" logs/security.log

Alerting

// E-Mail-Benachrichtigungen
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransporter({
  host: 'smtp.gmail.com',
  port: 587,
  secure: false,
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS
  }
});

const sendSecurityAlert = async (message) => {
  await transporter.sendMail({
    from: process.env.EMAIL_USER,
    to: process.env.ADMIN_EMAIL,
    subject: 'Security Alert - Ninja Parkour',
    text: message
  });
};

🚨 Incident Response

Incident-Response-Plan

  1. Erkennung - Monitoring und Alerts
  2. Bewertung - Schweregrad bestimmen
  3. Eindämmung - Schaden begrenzen
  4. Eliminierung - Bedrohung entfernen
  5. Wiederherstellung - System reparieren
  6. Lektionen - Aus Fehlern lernen

Notfall-Kontakte

Backup-Strategie

# Tägliche Backups
pg_dump -h localhost -U username -d ninjaserver | gzip > backup_$(date +%Y%m%d).sql.gz

# Wöchentliche Vollständige Backups
tar -czf full_backup_$(date +%Y%m%d).tar.gz /var/lib/postgresql/data/

# Backup-Verifizierung
psql -d ninjaserver < backup_$(date +%Y%m%d).sql

Recovery-Prozeduren

# System wiederherstellen
sudo systemctl stop ninjaserver
psql -d ninjaserver < latest_backup.sql
sudo systemctl start ninjaserver

# Datenbank reparieren
psql -d ninjaserver -c "VACUUM FULL;"
psql -d ninjaserver -c "REINDEX DATABASE ninjaserver;"

🔧 Sicherheits-Checkliste

Regelmäßige Überprüfungen

  • Passwörter geändert (alle 90 Tage)
  • API-Keys rotiert (alle 180 Tage)
  • SSL-Zertifikate gültig
  • Sicherheits-Updates installiert
  • Logs überprüft
  • Backups getestet
  • Penetrationstests durchgeführt

Wöchentliche Aufgaben

  • Sicherheits-Logs analysiert
  • System-Updates geprüft
  • Backup-Status überprüft
  • Performance-Metriken analysiert

Monatliche Aufgaben

  • Sicherheits-Audit durchgeführt
  • Berechtigungen überprüft
  • Incident-Response-Plan getestet
  • Sicherheitsschulungen durchgeführt

Wichtig: Diese Sicherheitsrichtlinien müssen regelmäßig überprüft und aktualisiert werden. Bei Sicherheitsvorfällen wenden Sie sich sofort an den Sicherheitsbeauftragten.