Fehlende vollständige Mediation

Beschreibung

Fehlende vollständige Mediation tritt auf, wenn ein System nicht bei jedem Zugriff auf eine geschützte Ressource die Autorisierung prüft. Anstatt Berechtigungen bei jeder Anfrage zu verifizieren, prüft das System möglicherweise nur einmal (beim Login, ersten Zugriff oder aus gecachten Ergebnissen) und nimmt an, dass nachfolgende Zugriffe autorisiert sind. Dies ermöglicht Angreifern, Zugriffskontrollen nach der initialen Verifizierung oder durch direkte Objektreferenzen zu umgehen.

Risiko

Benutzer können auf Ressourcen zugreifen, nachdem ihre Berechtigungen widerrufen wurden. Session-Hijacking gewährt vollen Zugriff ohne erneute Verifizierung. Direkter URL-Zugriff umgeht navigationsbasierte Sicherheit. Gecachte Autorisierungsentscheidungen werden veraltet. Race-Conditions zwischen Berechtigungsänderungen und Zugriff. Horizontale und vertikale Privilegieneskalation durch direkte Referenzen.

Lösung

Verifizieren Sie Autorisierung bei jeder Zugriffsanfrage. Cachen Sie Autorisierungsentscheidungen niemals permanent. Implementieren Sie serverseitige Autorisierungsprüfungen unabhängig von Client-Navigation. Verwenden Sie einen zentralisierten Autorisierungsdienst. Revalidieren Sie Berechtigungen für sensible Operationen. Protokollieren Sie alle Zugriffe auf geschützte Ressourcen.

Häufige Auswirkungen

AuswirkungDetails
AutorisierungBereich: Bypass

Zugriffskontrollen umgangen nach initialer Prüfung.
VertraulichkeitBereich: Datenexposition

Unbefugter Zugriff auf geschützte Daten.
IntegritätBereich: Unbefugte Änderung

Änderungen ohne ordnungsgemäße Autorisierung vorgenommen.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Einmalige Autorisierungsprüfung
public class VulnerableDocumentService {

    private Map<String, Boolean> authorizedUsers = new HashMap<>();

    public void login(String userId) {
        // Autorisierung nur beim Login prüfen
        boolean hasAccess = authService.checkDocumentAccess(userId);
        authorizedUsers.put(userId, hasAccess);  // Für immer gecacht!
    }

    public Document getDocument(String userId, String docId) {
        // Verwendet gecachte Autorisierung - prüft nie erneut!
        if (authorizedUsers.getOrDefault(userId, false)) {
            return documentRepository.findById(docId);
        }
        throw new UnauthorizedException();
    }

    // Berechtigungsänderungen werden erst nach Re-Login reflektiert!
}

// VERWUNDBAR: Keine Autorisierungsprüfung bei direktem Zugriff
@RestController
public class VulnerableFileController {

    @GetMapping("/files/{fileId}")
    public byte[] getFile(@PathVariable String fileId) {
        // Keine Autorisierungsprüfung!
        // Jeder mit der URL kann auf jede Datei zugreifen
        return fileService.getFileContent(fileId);
    }

    @GetMapping("/admin/users")
    public List<User> listUsers() {
        // Nimmt an, nur Admins kennen diese URL
        // Keine tatsächliche Admin-Verifizierung!
        return userService.findAll();
    }
}

// VERWUNDBAR: Client-seitige Navigation als einziger Schutz
public class VulnerableAdminPanel {

    public void showAdminMenu(User user) {
        if (user.isAdmin()) {
            // Admin-Menüpunkte anzeigen
        }
        // Tatsächliche Endpunkte haben keine Prüfungen!
    }

    // Admin-Funktionen für jeden zugänglich, der die URL kennt
    public void deleteUser(String userId) {
        userRepository.delete(userId);  // Keine Auth-Prüfung!
    }
}
# VERWUNDBAR: Session-basiertes Caching ohne Revalidierung
class VulnerableAccessControl:
    def __init__(self):
        self.permission_cache = {}

    def login(self, user_id):
        # Berechtigungen beim Login cachen
        permissions = self.db.get_permissions(user_id)
        self.permission_cache[user_id] = permissions
        # Wird nie aktualisiert!

    def check_access(self, user_id, resource):
        # Verwendet veraltete gecachte Berechtigungen
        permissions = self.permission_cache.get(user_id, [])
        return resource in permissions

# VERWUNDBAR: Nur-Erstzugriff-Prüfung
class VulnerableResourceManager:
    def __init__(self):
        self.accessed_resources = set()

    def get_resource(self, user_id, resource_id):
        # Nur beim ersten Zugriff prüfen
        if resource_id not in self.accessed_resources:
            if not self.auth_service.check(user_id, resource_id):
                raise PermissionError()
            self.accessed_resources.add(resource_id)
            # Nachfolgende Zugriffe überspringen Prüfung!

        return self.load_resource(resource_id)

# VERWUNDBAR: Keine Mediation auf API-Endpunkten
from flask import Flask, request
app = Flask(__name__)

@app.route('/api/users/<user_id>/data')
def get_user_data(user_id):
    # Keine Prüfung ob Anfragender auf diese Benutzerdaten zugreifen darf!
    # Jeder kann auf jedes Benutzers Daten zugreifen
    return jsonify(db.get_user_data(user_id))

@app.route('/api/documents/<doc_id>')
def get_document(doc_id):
    # Direkte Objektreferenz ohne Autorisierung
    return send_file(f'documents/{doc_id}')
// VERWUNDBAR: Einmalige Rollenprüfung
class VulnerableAuthMiddleware {
    constructor() {
        this.authorizedSessions = new Map();
    }

    async onLogin(req, res, next) {
        const user = await this.authenticate(req.body);
        if (user) {
            // Autorisierungsentscheidung speichern
            this.authorizedSessions.set(req.sessionId, user.role);
        }
    }

    // Middleware verwendet gecachte Rolle
    checkRole(requiredRole) {
        return (req, res, next) => {
            const role = this.authorizedSessions.get(req.sessionId);
            // Verwendet gecachte Rolle - nicht aktuelle!
            if (role === requiredRole) {
                next();
            } else {
                res.status(403).send('Verboten');
            }
        };
    }
}

// VERWUNDBAR: Fehlende Autorisierung auf Routen
app.get('/users/:userId/profile', async (req, res) => {
    // Keine Prüfung ob aktueller Benutzer dieses Profil sehen darf!
    const profile = await db.getUserProfile(req.params.userId);
    res.json(profile);
});

app.delete('/posts/:postId', async (req, res) => {
    // Keine Prüfung ob aktueller Benutzer diesen Post besitzt!
    await db.deletePost(req.params.postId);
    res.json({ deleted: true });
});

Lösungscode

// SICHER: Vollständige Mediation bei jedem Zugriff
@Service
public class SafeDocumentService {

    private final AuthorizationService authService;
    private final DocumentRepository documentRepository;

    public Document getDocument(String userId, String docId) {
        // Immer Autorisierung verifizieren
        if (!authService.canAccessDocument(userId, docId)) {
            throw new UnauthorizedException("Zugriff auf Dokument verweigert");
        }

        return documentRepository.findById(docId);
    }

    public void updateDocument(String userId, String docId, String content) {
        // Auch für Updates erneut prüfen
        if (!authService.canModifyDocument(userId, docId)) {
            throw new UnauthorizedException("Dokument kann nicht geändert werden");
        }

        documentRepository.update(docId, content);
    }
}

// SICHER: Autorisierung auf jedem Endpunkt
@RestController
public class SafeFileController {

    @GetMapping("/files/{fileId}")
    public byte[] getFile(@AuthenticationPrincipal User user,
                          @PathVariable String fileId) {
        // Immer Zugriff verifizieren
        if (!fileService.canUserAccess(user.getId(), fileId)) {
            throw new AccessDeniedException("Dateizugriff nicht möglich");
        }

        return fileService.getFileContent(fileId);
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/admin/users")
    public List<User> listUsers() {
        // Spring Security verifiziert Rolle bei jeder Anfrage
        return userService.findAll();
    }
}

// SICHER: Zentralisierter Autorisierungsdienst
@Service
public class AuthorizationService {

    public boolean canAccessDocument(String userId, String docId) {
        // Immer aktuelle Berechtigungen aus Datenbank holen
        User user = userRepository.findById(userId);
        Document doc = documentRepository.findById(docId);

        // Eigentümerschaft prüfen
        if (doc.getOwnerId().equals(userId)) {
            return true;
        }

        // Freigegebenen Zugriff prüfen
        return shareRepository.hasAccess(userId, docId);
    }
}
# SICHER: Vollständige Mediation mit frischen Berechtigungsprüfungen
class SafeAccessControl:
    def __init__(self, db):
        self.db = db

    def check_access(self, user_id: str, resource_id: str, action: str) -> bool:
        """Immer aktuelle Berechtigungen holen - nie cachen."""
        # Aktuelle Benutzerberechtigungen aus Datenbank holen
        permissions = self.db.get_current_permissions(user_id)

        # Ressourcenspezifischen Zugriff prüfen
        return self._evaluate_permissions(permissions, resource_id, action)

# SICHER: Decorator für vollständige Mediation
from functools import wraps

def require_access(resource_type):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            user_id = get_current_user_id()
            resource_id = kwargs.get('resource_id') or args[0]

            # Immer bei jeder Anfrage prüfen
            if not auth_service.check_access(user_id, resource_type, resource_id):
                raise PermissionError(f"Zugriff auf {resource_type} verweigert")

            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/api/users/<user_id>/data')
@require_access('user_data')
def get_user_data(user_id):
    # Zugriff bereits durch Decorator verifiziert
    return jsonify(db.get_user_data(user_id))

@app.route('/api/documents/<doc_id>')
@require_access('document')
def get_document(doc_id):
    return send_file(f'documents/{doc_id}')

# SICHER: Objektebenen-Autorisierung
class SafeResourceManager:
    def get_resource(self, user_id: str, resource_id: str):
        # Eigentümerschaft/Zugriff jedes Mal prüfen
        resource = self.db.get_resource(resource_id)

        if resource.owner_id != user_id:
            if not self.db.has_shared_access(user_id, resource_id):
                raise PermissionError("Zugriff verweigert")

        return resource
// SICHER: Middleware mit vollständiger Mediation
class SafeAuthMiddleware {
    constructor(authService) {
        this.authService = authService;
    }

    // Bei jeder Anfrage prüfen
    authorize(requiredPermission) {
        return async (req, res, next) => {
            // Immer aktuelle Berechtigungen holen
            const hasPermission = await this.authService.checkPermission(
                req.user.id,
                requiredPermission,
                req.params
            );

            if (!hasPermission) {
                return res.status(403).json({ error: 'Zugriff verweigert' });
            }

            next();
        };
    }
}

// SICHER: Vollständige Mediation auf allen Routen
const auth = new SafeAuthMiddleware(authService);

app.get('/users/:userId/profile',
    auth.authorize('read:user_profile'),
    async (req, res) => {
        // Zusätzliche Eigentümerschaftsprüfung
        if (req.user.id !== req.params.userId &&
            !req.user.roles.includes('admin')) {
            return res.status(403).json({ error: 'Zugriff verweigert' });
        }

        const profile = await db.getUserProfile(req.params.userId);
        res.json(profile);
    }
);

app.delete('/posts/:postId',
    auth.authorize('delete:post'),
    async (req, res) => {
        const post = await db.getPost(req.params.postId);

        // Eigentümerschaft verifizieren
        if (post.authorId !== req.user.id) {
            return res.status(403).json({ error: 'Nicht Ihr Post' });
        }

        await db.deletePost(req.params.postId);
        res.json({ deleted: true });
    }
);

Ausgenutzt in der Praxis

IDOR-Schwachstellen

Direkte Objektreferenzen ohne Autorisierungsprüfungen.

Admin-Panel-Zugriff

URLs ohne ordnungsgemäße Zugriffskontrolle entdeckbar.

Session-Fixierung

Session bleibt autorisiert nach Berechtigungswiderruf.


Tools zum Testen/Ausnutzen

  • Burp Suite — Autorisierung auf allen Endpunkten testen.
  • OWASP ZAP automatisiertes Scanning.
  • Manuelles Testen direkter Objektreferenzen.

CVE-Beispiele

  • Zahlreiche IDOR-CVEs (Insecure Direct Object Reference).
  • Admin-Bypass durch URL-Raten.

Referenzen

  1. MITRE. "CWE-638: Not Using Complete Mediation." https://cwe.mitre.org/data/definitions/638.html
  2. OWASP. "Broken Access Control."