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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Datenpreisgabe Unautorisierter Zugang zu sensiblen Daten einschließlich personenbezogener Informationen, Finanzdaten und Geschäftsgeheimnissen. |
| Integrität | Bereich: Datenänderung Unautorisierte Möglichkeit, Daten und Systemkonfigurationen zu ändern, zu löschen oder zu beschädigen. |
| Verfügbarkeit | Bereich: 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
-
CVE-2021-22893 -- Pulse Secure Zugriffskontrollumgehung.
-
CVE-2020-3452 -- Cisco ASA Path Traversal.
-
CVE-2019-19781 -- Citrix ADC Zugriffskontrollumgehung.
Referenzen
-
MITRE. "CWE-284: Improper Access Control." https://cwe.mitre.org/data/definitions/284.html
-
OWASP. "A01:2021 -- Broken Access Control." https://owasp.org/Top10/A01_2021-Broken_Access_Control/