Unsachgemäße Autorisierung

Beschreibung

Unsachgemäße Autorisierung tritt auf, wenn Software keine Autorisierungsprüfungen durchführt oder diese falsch durchführt, wodurch Benutzer auf Ressourcen zugreifen oder Aktionen über ihre vorgesehenen Privilegien hinaus ausführen können. Dies unterscheidet sich von Authentifizierung (Identitätsverifikation), da Autorisierung bestimmt, was ein authentifizierter Benutzer tun darf. Häufige Probleme umfassen fehlende Autorisierungsprüfungen, fehlerhafte Rollenverifikation, Verlass auf clientseitige Autorisierung und Privilegieneskalation durch Parametermanipulation.

Risiko

Unsachgemäße Autorisierung gehört konsistent zu den kritischsten Webanwendungsschwachstellen. Sie kann zu unbefugtem Datenzugriff, Datenmodifikation, administrativen Aktionen durch reguläre Benutzer oder Zugriff auf Konten anderer Benutzer führen. IDOR (Insecure Direct Object Reference)-Angriffe nutzen fehlende Autorisierung aus. Horizontale Privilegieneskalation ermöglicht den Zugriff auf Daten anderer Benutzer. Vertikale Privilegieneskalation ermöglicht die Durchführung administrativer Aktionen. Diese Schwachstellen haben oft kritische Auswirkungen, da sie direkt Geschäftslogikfehler offenlegen.

Lösung

Implementieren Sie zentralisierte Autorisierungsprüfungen, die nicht umgangen werden können. Verwenden Sie rollenbasierte Zugriffskontrolle (RBAC) oder attributbasierte Zugriffskontrolle (ABAC). Verifizieren Sie die Autorisierung bei jeder Anfrage, nicht nur bei der Navigation. Verlassen Sie sich nie auf clientseitige Autorisierung. Validieren Sie, dass der authentifizierte Benutzer die Berechtigung hat, auf die spezifisch angeforderte Ressource zuzugreifen. Implementieren Sie geeignete Zugriffssteuerungsmatrizen. Protokollieren Sie Autorisierungsfehler für Sicherheitsüberwachung. Verwenden Sie vom Framework bereitgestellte Autorisierungsmechanismen.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Privilegieneskalation

Benutzer können Zugang zu Ressourcen und Aktionen über ihr Autorisierungsniveau hinaus erlangen.
VertraulichkeitBereich: Datenoffenlegung

Unbefugter Zugang zu sensiblen Daten anderer Benutzer oder des Systems.
IntegritätBereich: Unbefugte Modifikation

Fähigkeit, Daten oder Konfigurationen ohne entsprechende Befugnis zu modifizieren.

Beispielcode + Korrigierter Code

Anfälliger Code

# ANFÄLLIG: Keine Autorisierungsprüfung
@app.route('/api/users/<user_id>/profile')
@login_required
def get_user_profile(user_id):
    # Prüft nur Authentifizierung, nicht Autorisierung
    # Jeder eingeloggte Benutzer kann auf jedes Profil zugreifen!
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

# ANFÄLLIG: Clientseitige Autorisierung
@app.route('/admin/delete-user/<user_id>', methods=['DELETE'])
@login_required
def delete_user(user_id):
    # Autorisierung nur im Frontend erzwungen!
    # Backend verifiziert nicht, ob Benutzer Admin ist
    User.query.filter_by(id=user_id).delete()
    return jsonify({'status': 'deleted'})

# ANFÄLLIG: IDOR - Direkte Objektreferenz
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
    # Jeder Benutzer kann auf jedes Dokument durch Erraten von IDs zugreifen
    document = Document.query.get(doc_id)
    return send_file(document.path)
// ANFÄLLIG: Fehlende Autorisierungsprüfung
@RestController
public class OrderController {

    @GetMapping("/orders/{orderId}")
    public Order getOrder(@PathVariable Long orderId, Principal principal) {
        // Holt jede Bestellung unabhängig davon, wem sie gehört
        return orderRepository.findById(orderId).orElseThrow();
    }

    @PostMapping("/admin/settings")
    public void updateSettings(@RequestBody Settings settings) {
        // Keine Prüfung, ob Benutzer tatsächlich Admin ist!
        settingsService.update(settings);
    }
}

// ANFÄLLIG: Rollenprüfung am falschen Ort
@Controller
public class AdminController {

    @GetMapping("/admin/users")
    public String listUsers(Model model) {
        // Versteckt nur das Menü in der UI - Endpunkt ist trotzdem erreichbar!
        // isAdmin ist nur clientseitig
        return "admin/users";
    }
}
// ANFÄLLIG: Autorisierungsumgehung durch Parametermanipulation
app.get('/api/account', (req, res) => {
    // Benutzer-ID aus Request-Parameter, nicht aus Session
    const userId = req.query.userId;  // Angreifer ändert dies!
    const account = await Account.findById(userId);
    res.json(account);
});

// ANFÄLLIG: Rolle vom Client
app.post('/api/admin/create-user', (req, res) => {
    // Rolle vom Client übergeben - Angreifer setzt role: 'admin'
    const { username, password, role } = req.body;
    await User.create({ username, password, role });
    res.json({ success: true });
});

// ANFÄLLIG: Fehlende Eigentümerschaftsprüfung
app.put('/api/posts/:postId', async (req, res) => {
    // Jeder authentifizierte Benutzer kann jeden Beitrag bearbeiten
    await Post.findByIdAndUpdate(req.params.postId, req.body);
    res.json({ success: true });
});

Korrigierter Code

# SICHER: Ordnungsgemäße Autorisierungsprüfung
from functools import wraps

def authorize_resource(resource_type):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            resource_id = kwargs.get(f'{resource_type}_id')
            if not current_user.can_access(resource_type, resource_id):
                abort(403, 'Zugriff verweigert')
            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/api/users/<user_id>/profile')
@login_required
def get_user_profile(user_id):
    # Prüfen, ob aktueller Benutzer auf dieses Profil zugreifen kann
    if str(current_user.id) != user_id and not current_user.is_admin:
        abort(403, 'Zugriff verweigert')

    user = User.query.get_or_404(user_id)
    return jsonify(user.to_dict())

# SICHER: Serverseitige Rollenverifikation
@app.route('/admin/delete-user/<user_id>', methods=['DELETE'])
@login_required
def delete_user(user_id):
    # Admin-Rolle auf Server verifizieren
    if not current_user.has_role('admin'):
        abort(403, 'Admin-Zugang erforderlich')

    # Zusätzliche Prüfung: kann sich selbst oder andere Admins nicht löschen
    target_user = User.query.get_or_404(user_id)
    if target_user.id == current_user.id:
        abort(400, 'Kann sich selbst nicht löschen')
    if target_user.has_role('admin'):
        abort(403, 'Kann Admin-Benutzer nicht löschen')

    db.session.delete(target_user)
    db.session.commit()
    return jsonify({'status': 'deleted'})

# SICHER: IDOR-Prävention mit Eigentümerschaftsprüfung
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
    document = Document.query.get_or_404(doc_id)

    # Eigentümerschaft oder Berechtigung verifizieren
    if not document.is_accessible_by(current_user):
        abort(403, 'Zugriff verweigert')

    return send_file(document.path)

# Verwendung von Decorators für saubereren Code
from flask_principal import Permission, RoleNeed

admin_permission = Permission(RoleNeed('admin'))

@app.route('/admin/settings', methods=['POST'])
@login_required
@admin_permission.require(http_exception=403)
def admin_settings():
    # Nur von Admins erreichbar
    pass
// SICHER: Ordnungsgemäße Autorisierung mit Eigentümerschaftsprüfung
@RestController
public class SecureOrderController {

    @GetMapping("/orders/{orderId}")
    @PreAuthorize("isAuthenticated()")
    public Order getOrder(@PathVariable Long orderId, Principal principal) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new NotFoundException("Bestellung nicht gefunden"));

        // Eigentümerschaft verifizieren
        if (!order.getUserId().equals(principal.getName()) &&
            !hasRole(principal, "ADMIN")) {
            throw new AccessDeniedException("Nicht berechtigt, diese Bestellung einzusehen");
        }

        return order;
    }

    @PostMapping("/admin/settings")
    @PreAuthorize("hasRole('ADMIN')")  // Spring Security Annotation
    public ResponseEntity<?> updateSettings(@RequestBody Settings settings) {
        settingsService.update(settings);
        return ResponseEntity.ok().build();
    }
}

// SICHER: Service-Layer-Autorisierung
@Service
public class DocumentService {

    @PreAuthorize("hasPermission(#docId, 'Document', 'read')")
    public Document getDocument(Long docId) {
        return documentRepository.findById(docId).orElseThrow();
    }

    public boolean canAccess(User user, Document document) {
        // Eigentümerschaft prüfen
        if (document.getOwnerId().equals(user.getId())) {
            return true;
        }

        // Geteilte Berechtigungen prüfen
        return documentPermissionRepository
            .existsByDocumentIdAndUserId(document.getId(), user.getId());
    }
}

// SICHER: Benutzerdefinierter Berechtigungsauswerter
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication auth, Object targetId,
                                 Object targetType, Object permission) {
        User user = (User) auth.getPrincipal();

        if ("Document".equals(targetType)) {
            Document doc = documentRepository.findById((Long) targetId).orElse(null);
            if (doc == null) return false;

            switch (permission.toString()) {
                case "read":
                    return doc.getOwnerId().equals(user.getId()) ||
                           doc.isPublic() ||
                           hasSharedAccess(user, doc);
                case "write":
                    return doc.getOwnerId().equals(user.getId());
                case "delete":
                    return doc.getOwnerId().equals(user.getId()) ||
                           user.hasRole("ADMIN");
            }
        }
        return false;
    }
}
// SICHER: Ordnungsgemäße Autorisierung
const authorize = (requiredRole) => {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({ error: 'Authentifizierung erforderlich' });
        }
        if (requiredRole && !req.user.roles.includes(requiredRole)) {
            return res.status(403).json({ error: 'Unzureichende Berechtigungen' });
        }
        next();
    };
};

const authorizeResource = async (req, res, next) => {
    const resource = await Resource.findById(req.params.resourceId);
    if (!resource) {
        return res.status(404).json({ error: 'Nicht gefunden' });
    }

    // Eigentümerschaft oder Admin prüfen
    if (resource.userId.toString() !== req.user.id &&
        !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Zugriff verweigert' });
    }

    req.resource = resource;
    next();
};

// SICHER: Benutzer-ID aus Session, nicht aus Request
app.get('/api/account', authenticate, (req, res) => {
    // Benutzer-ID aus authentifizierter Session
    const userId = req.user.id;  // Von Authentifizierungs-Middleware
    const account = await Account.findById(userId);
    res.json(account);
});

// SICHER: Serverbestimmte Rolle
app.post('/api/admin/create-user', authenticate, authorize('admin'), (req, res) => {
    const { username, password } = req.body;
    // Rolle wird vom Server bestimmt, nicht vom Client
    const role = 'user';  // Standardrolle
    await User.create({ username, password, role });
    res.json({ success: true });
});

// SICHER: Eigentümerschaftsverifikation
app.put('/api/posts/:postId', authenticate, async (req, res) => {
    const post = await Post.findById(req.params.postId);

    if (!post) {
        return res.status(404).json({ error: 'Beitrag nicht gefunden' });
    }

    // Nur Eigentümer oder Admin kann bearbeiten
    if (post.authorId.toString() !== req.user.id &&
        !req.user.roles.includes('admin')) {
        return res.status(403).json({ error: 'Nicht autorisiert' });
    }

    await Post.findByIdAndUpdate(req.params.postId, req.body);
    res.json({ success: true });
});

Ausgenutzt in der Praxis

Parler-Datenleck (2021)

Nachdem Parlers AWS-Hosting beendet wurde, entdeckten Benutzer, dass öffentliche APIs keine Autorisierungsprüfungen hatten, was die Enumeration und den Download von Millionen von Beiträgen ermöglichte, einschließlich gelöschter Inhalte und Benutzerdaten.

Facebook Graph API-Schwachstelle (2018)

Ein Bug in Facebooks "Ansehen als"-Funktion kombiniert mit Autorisierungsfehlern ermöglichte es Angreifern, Zugriffstoken von 50 Millionen Benutzern zu stehlen.

Über API-Autorisierungsumgehung (2016)

Eine Autorisierungsumgehung in Ubers API ermöglichte das Einsehen persönlicher Informationen jedes Fahrgastes durch Manipulation von Benutzer-IDs.


Tools zum Testen/Ausnutzen

  • Burp Suite — Abfangen und Modifizieren von Autorisierungsparametern.

  • OWASP ZAP — Automatisiertes Autorisierungstesten.

  • Autorize — Burp-Erweiterung für Autorisierungstests.

  • AuthMatrix — Autorisierungstest-Matrix.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-285: Improper Authorization." https://cwe.mitre.org/data/definitions/285.html

  2. OWASP. "Authorization Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html