Unsachgemäße Zugriffskontrolle

Beschreibung

Unsachgemäße Zugriffskontrolle tritt auf, wenn Software den Zugriff auf eine Ressource durch einen unautorisierten Akteur nicht ordnungsgemäß einschränkt. Dies ist eine breite Kategorie, die viele spezifische Zugriffskontrollfehler umfasst, darunter Authentifizierungsumgehung, Autorisierungsumgehung und unsachgemäße Durchsetzung von Zugriffsrichtlinien. Zugriffskontrolle umfasst die Bestimmung, wer auf welche Ressourcen zugreifen kann und welche Aktionen ausgeführt werden dürfen. Fehler können bei der Authentifizierung (wer Sie sind), der Autorisierung (was Sie tun dürfen) oder der Rechenschaftspflicht (Nachverfolgung, was getan wurde) auftreten.

Risiko

Zugriffskontrollfehler gehören konstant zu den kritischsten Sicherheitsschwachstellen. Sie führen direkt zu unautorisiertem Zugriff auf sensible Daten, unautorisierter Änderung von Systemen und potenzieller vollständiger Systemkompromittierung. OWASP stuft Broken Access Control als die Nr. 1 Schwachstellenkategorie ein. Reale Auswirkungen umfassen Datenverstöße, die Millionen von Datensätzen offenlegen, Finanzbetrug und Systemübernahmen. Zugriffskontrollfehler sind besonders gefährlich, da sie oft keine Ausnutzungsfähigkeiten erfordern -- nur Kenntnis des Fehlers.

Lösung

Implementieren Sie das Prinzip der minimalen Rechtevergabe. Zentralisieren Sie die Zugriffskontrolllogik und vermeiden Sie duplizierte Autorisierungsprüfungen. Verweigern Sie standardmäßig -- verlangen Sie explizite Gewährungen. Validieren Sie die Autorisierung bei jeder Anfrage serverseitig. Verwenden Sie etablierte Zugriffskontroll-Frameworks. Implementieren Sie rollenbasierte Zugriffskontrolle (RBAC) oder attributbasierte Zugriffskontrolle (ABAC). Protokollieren Sie Zugriffskontrollfehler zur Überwachung. Führen Sie regelmäßige Zugriffskontrollprüfungen und Tests durch.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Datenpreisgabe

Unautorisierter Zugang zu sensiblen Daten einschließlich personenbezogener Informationen, Finanzdaten und Geschäftsgeheimnissen.
IntegritätBereich: Datenänderung

Unautorisierte Möglichkeit, Daten und Systemkonfigurationen zu ändern, zu löschen oder zu beschädigen.
VerfügbarkeitBereich: Denial of Service

Unautorisierter Zugang kann genutzt werden, um Dienste zu storen oder Daten zu zerstoren.

Beispielcode und Lösung

Verwundbarer Code

# VERWUNDBAR: Fehlende Zugriffskontrolle
@app.route('/admin/users')
def list_all_users():
    # Keine Authentifizierungs- oder Autorisierungsprüfung!
    return jsonify(User.query.all())

# VERWUNDBAR: Insecure Direct Object Reference (IDOR)
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
    # Jeder angemeldete Benutzer kann auf jede Rechnung zugreifen
    invoice = Invoice.query.get(invoice_id)
    return jsonify(invoice.to_dict())

# VERWUNDBAR: Horizontale Rechteeskalation
@app.route('/api/user/<user_id>/settings', methods=['PUT'])
@login_required
def update_settings(user_id):
    # Benutzer kann die Einstellungen jedes anderen ändern, indem er user_id ändert
    user = User.query.get(user_id)
    user.settings = request.json
    db.session.commit()
    return 'Updated'

# VERWUNDBAR: Vertikale Rechteeskalation
@app.route('/api/user/<user_id>/promote', methods=['POST'])
@login_required
def promote_user(user_id):
    # Keine Admin-Prüfung - jeder Benutzer kann jeden befördern!
    user = User.query.get(user_id)
    user.role = 'admin'
    db.session.commit()
    return 'Promoted'
// VERWUNDBAR: Path-Traversal-Umgehung
@GetMapping("/files/{filename}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
    // Keine Validierung - Angreifer nutzt ../../../etc/passwd
    Path filePath = Paths.get("/uploads/" + filename);
    Resource resource = new FileSystemResource(filePath);
    return ResponseEntity.ok(resource);
}

// VERWUNDBAR: Parametermanipulation
@PostMapping("/transfer")
public ResponseEntity<?> transfer(@RequestBody TransferRequest request) {
    // Vertraut 'fromAccount' aus der Anfrage - sollte das Konto des authentifizierten Benutzers verwenden
    accountService.transfer(
        request.getFromAccount(),  // Angreifer gibt das Konto des Opfers an!
        request.getToAccount(),
        request.getAmount()
    );
    return ResponseEntity.ok().build();
}

// VERWUNDBAR: Fehlende Zugriffskontrolle auf Funktionsebene
@DeleteMapping("/users/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
    // Keine Prüfung, ob der Benutzer Löschberechtigung hat
    userRepository.deleteById(id);
    return ResponseEntity.ok().build();
}
// VERWUNDBAR: Nur clientseitige Zugriffskontrolle
// Frontend versteckt Admin-Menü, aber Backend erzwingt nichts
app.get('/api/admin/config', (req, res) => {
    // Keine serverseitige Prüfung - jeder kann zugreifen!
    res.json(systemConfig);
});

// VERWUNDBAR: Vorhersagbare Ressourcen-IDs
app.get('/api/documents/:id', async (req, res) => {
    // Sequenzielle IDs machen die Aufzählung einfach
    // Keine Eigentümerprüfung
    const doc = await Document.findById(req.params.id);
    res.json(doc);
});

// VERWUNDBAR: Mass Assignment
app.put('/api/profile', async (req, res) => {
    // Benutzer kann beliebige Felder setzen, einschließlich 'role' oder 'isAdmin'
    await User.findByIdAndUpdate(req.userId, req.body);
    res.json({ success: true });
});

Sichere Lösung

# SICHER: Ordnungsgemäße Zugriffskontrolle mit Authentifizierung und Autorisierung
from functools import wraps

def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated:
            abort(401)
        if not current_user.has_role('admin'):
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@app.route('/admin/users')
@admin_required
def list_all_users():
    return jsonify([u.to_dict() for u in User.query.all()])

# SICHER: Ressourcen-Eigentümerprüfung
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
    invoice = Invoice.query.get_or_404(invoice_id)

    # Eigentümer oder Admin-Zugang prüfen
    if invoice.user_id != current_user.id and not current_user.is_admin:
        abort(403, 'Access denied')

    return jsonify(invoice.to_dict())

# SICHER: Horizontale Rechteeskalation verhindern
@app.route('/api/user/settings', methods=['PUT'])
@login_required
def update_settings():
    # Immer die ID des authentifizierten Benutzers verwenden, nicht aus der Anfrage
    current_user.settings = request.json
    db.session.commit()
    return jsonify({'status': 'updated'})

# SICHER: Ordnungsgemäße Autorisierung für Rechteoperationen
@app.route('/api/user/<user_id>/promote', methods=['POST'])
@login_required
@admin_required
def promote_user(user_id):
    # Nur Admins können dies erreichen (Decorator)
    user = User.query.get_or_404(user_id)

    # Zusätzliche Geschäftslogikprüfungen
    if user.role == 'admin':
        abort(400, 'User is already an admin')

    user.role = 'admin'
    db.session.commit()
    audit_log.info(f'User {user_id} promoted by {current_user.id}')
    return jsonify({'status': 'promoted'})
// SICHER: Pfadvalidierung zur Verhinderung von Traversal
@GetMapping("/files/{filename}")
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
    // Dateinamen validieren
    if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
        return ResponseEntity.badRequest().build();
    }

    Path basePath = Paths.get("/uploads").toAbsolutePath().normalize();
    Path filePath = basePath.resolve(filename).normalize();

    // Sicherstellen, dass der aufgelöste Pfad innerhalb des Basisverzeichnisses liegt
    if (!filePath.startsWith(basePath)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }

    Resource resource = new FileSystemResource(filePath);
    if (!resource.exists()) {
        return ResponseEntity.notFound().build();
    }

    return ResponseEntity.ok(resource);
}

// SICHER: Konto des authentifizierten Benutzers verwenden
@PostMapping("/transfer")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> transfer(
        @RequestBody TransferRequest request,
        @AuthenticationPrincipal UserDetails user) {

    // Konto vom authentifizierten Benutzer holen, nicht aus der Anfrage
    Account fromAccount = accountRepository.findByUserId(user.getId());

    // Prüfen, ob der Benutzer das Quellkonto besitzt
    if (fromAccount == null) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }

    accountService.transfer(
        fromAccount.getId(),
        request.getToAccount(),
        request.getAmount()
    );

    return ResponseEntity.ok().build();
}

// SICHER: Zugriffskontrolle auf Funktionsebene
@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteUser(@PathVariable Long id, Principal principal) {
    // Prüfen, ob nicht das eigene Konto gelöscht wird
    if (id.equals(getUserId(principal))) {
        return ResponseEntity.badRequest()
            .body("Cannot delete your own account");
    }

    userRepository.deleteById(id);
    auditService.log("USER_DELETED", id, principal.getName());

    return ResponseEntity.ok().build();
}
// SICHER: Serverseitige Zugriffskontrolle
const isAdmin = (req, res, next) => {
    if (!req.user || !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Admin access required' });
    }
    next();
};

app.get('/api/admin/config', authenticate, isAdmin, (req, res) => {
    res.json(systemConfig);
});

// SICHER: Eigentümerprüfung mit UUIDs
app.get('/api/documents/:id', authenticate, async (req, res) => {
    const doc = await Document.findById(req.params.id);

    if (!doc) {
        return res.status(404).json({ error: 'Not found' });
    }

    // Eigentümer oder Freigabe prüfen
    if (doc.ownerId !== req.user.id &&
        !doc.sharedWith.includes(req.user.id) &&
        !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Access denied' });
    }

    res.json(doc);
});

// SICHER: Erlaubte Felder per Allowlist
app.put('/api/profile', authenticate, async (req, res) => {
    // Nur bestimmte Felder zur Aktualisierung erlauben
    const allowedFields = ['name', 'email', 'preferences'];
    const updates = {};

    for (const field of allowedFields) {
        if (req.body[field] !== undefined) {
            updates[field] = req.body[field];
        }
    }

    await User.findByIdAndUpdate(req.user.id, updates);
    res.json({ success: true });
});

Ausgenutzt in der Praxis

Facebook Datenschutzfehler (2018)

Ein Datenschutzfehler ermöglichte es Apps, auf Fotos zuzugreifen, die Benutzer hochgeladen, aber nie geteilt hatten, was 6,8 Millionen Benutzer betraf. Der Zugriffskontrollfehler legte private Fotos für Drittanbieter-Anwendungen offen.

Über "God View" (2014)

Uber-Mitarbeiter hatten Zugang zu einem "God View"-Tool, das Kundenstandorte in Echtzeit verfolgte, mit unzureichenden Zugriffskontrollen, was zu Datenschutzverletzungen und regulatorischen Maßnahmen führte.

First American Financial (2019)

885 Millionen Kundendatensätze wurden aufgrund einer IDOR-Schwachstelle offengelegt, bei der Dokumente durch Inkrementieren der Dokument-IDs in URLs abgerufen werden könnten.


Tools zum Testen und Ausnutzen

  • Burp Suite -- umfassendes Testen der Zugriffskontrolle.

  • OWASP ZAP -- automatisierte Sicherheitstests.

  • Autorize -- Burp-Erweiterung für Zugriffskontrolltests.

  • Postman -- API-Tests zur Verifizierung der Zugriffskontrolle.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-284: Improper Access Control." https://cwe.mitre.org/data/definitions/284.html

  2. OWASP. "A01:2021 -- Broken Access Control." https://owasp.org/Top10/A01_2021-Broken_Access_Control/