Verwendung desselben aufrufbaren Kontrollelements in mehreren Architekturschichten
Beschreibung
Verwendung desselben aufrufbaren Kontrollelements in mehreren Architekturschichten tritt auf, wenn ein Produkt identische Kontrollelemente (wie Funktionen, Methoden oder Module) über mehrere Architekturschichten implementiert, anstatt ordnungsgemäße Trennung der Zuständigkeiten beizubehalten. Dies verletzt das Prinzip der geschichteten Architektur, bei dem jede Schicht unterschiedliche Verantwortlichkeiten haben sollte. Wenn dieselbe Funktionalität über Schichten dupliziert wird (z.B. Präsentation, Geschäftslogik, Datenzugriff), entstehen Wartungsherausforderungen und potenzielle Inkonsistenzen.
Risiko
Das Duplizieren von Kontrollelementen über Architekturschichten hat indirekte Sicherheitsimplikationen. Inkonsistente Implementierungen über Schichten schaffen Sicherheitslücken, wenn eine Kopie gepatcht wird, aber andere nicht. Sicherheitsfixes müssen mehrfach angewendet werden, was die Chance erhöht, einen zu verpassen. Code-Review wird schwieriger, da Reviewer mehrere Stellen prüfen müssen. Änderungen an Sicherheitslogik müssen über alle Schichten synchronisiert werden. Die Komplexität erschwert die konsistente Anwendung von Sicherheitskontrollen. Divergierende Implementierungen über die Zeit können ausnutzbare Inkonsistenzen schaffen.
Lösung
Befolgen Sie ordnungsgemäße Prinzipien geschichteter Architektur mit klarer Trennung der Zuständigkeiten. Implementieren Sie gemeinsam genutzte Funktionalität in einer gemeinsamen Service-Schicht, die allen Architekturschichten zugänglich ist. Verwenden Sie Dependency Injection, um gemeinsame Dienste verschiedenen Schichten bereitzustellen. Definieren Sie klare Schnittstellen zwischen Schichten und setzen Sie diese durch. Wenden Sie das DRY-Prinzip (Don't Repeat Yourself) konsequent an. Verwenden Sie Architekturanalyse-Tools, um Schichtverletzungen zu erkennen. Führen Sie Architektur-Reviews durch, um ordnungsgemäße Trennung sicherzustellen. Dokumentieren Sie Schichtverantwortlichkeiten und -grenzen klar.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Wartbarkeit - Duplizierter Code über Schichten ist schwerer konsistent zu warten. |
| Andere | Bereich: Ändere Erhöhte analytische Komplexität - Sicherheitsanalyse muss mehrere Implementierungen derselben Logik abdecken. |
| Integrität | Bereich: Integrität Inkonsistenter Zustand - Verschiedene Implementierungen können sich unterschiedlich verhalten und Inkonsistenzen verursachen. |
Beispielcode
Anfälliger Code
// Anfällig: Dieselbe Validierungslogik über Schichten dupliziert
// Präsentationsschicht
@Controller
public class VulnerableUserController {
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
// Anfällig: Validierungslogik in Präsentationsschicht
if (user.getUsername() == null || user.getUsername().length() < 3) {
return ResponseEntity.badRequest().body("Ungültiger Benutzername");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
return ResponseEntity.badRequest().body("Ungültige E-Mail");
}
if (user.getPassword() == null || user.getPassword().length() < 8) {
return ResponseEntity.badRequest().body("Ungültiges Passwort");
}
return userService.createUser(user);
}
}
// Geschäftslogikschicht
@Service
public class VulnerableUserService {
public User createUser(UserDTO userDTO) {
// Anfällig: DIESELBE Validierungslogik hier dupliziert!
if (userDTO.getUsername() == null || userDTO.getUsername().length() < 3) {
throw new ValidationException("Ungültiger Benutzername");
}
if (userDTO.getEmail() == null || !userDTO.getEmail().contains("@")) {
throw new ValidationException("Ungültige E-Mail");
}
if (userDTO.getPassword() == null || userDTO.getPassword().length() < 8) {
throw new ValidationException("Ungültiges Passwort");
}
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPasswordHash(hashPassword(userDTO.getPassword()));
return userRepository.save(user);
}
}
// Datenzugriffsschicht
@Repository
public class VulnerableUserRepository {
public User save(User user) {
// Anfällig: DIESELBE Validierungslogik NOCHMALS dupliziert!
if (user.getUsername() == null || user.getUsername().length() < 3) {
throw new DataIntegrityException("Ungültiger Benutzername");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
throw new DataIntegrityException("Ungültige E-Mail");
}
// In Datenbank speichern
return jdbcTemplate.insert(user);
}
}
// Problem: Wenn sich Validierungsregeln ändern (z.B. minimale Passwortlänge),
// müssen DREI Stellen aktualisiert werden. Leicht eine zu übersehen, was Inkonsistenz erzeugt.
# Anfällig: Duplizierte Preisberechnung über Schichten
# API-Schicht
class VulnerablePriceController:
def calculate_price(self, request):
items = request.get('items', [])
# Anfällig: Preisberechnung in API-Schicht
total = 0
for item in items:
price = item['price'] * item['quantity']
if item.get('discount'):
price *= (1 - item['discount'] / 100)
total += price
# Steuer anwenden
total *= 1.19 # 19% Steuer
return {'total': total}
# Service-Schicht
class VulnerablePriceService:
def calculate_order_total(self, order):
# Anfällig: DIESELBE Berechnung dupliziert!
total = 0
for item in order.items:
price = item.price * item.quantity
if item.discount:
price *= (1 - item.discount / 100)
total += price
# Steuer anwenden - aber Moment, diese verwendet 20%!
total *= 1.20 # BUG: Anderer Steuersatz!
return total
# Hintergrundjob-Schicht
class VulnerableInvoiceGenerator:
def generate_invoice(self, order):
# Anfällig: DIESELBE Berechnung NOCHMALS dupliziert!
total = 0
for item in order.items:
price = item.price * item.quantity
# Hoppla, Rabattlogik hier vergessen!
total += price
# Steuerberechnung fehlt komplett!
return self.create_invoice_pdf(order, total)
# Problem: Drei verschiedene Implementierungen mit Inkonsistenzen:
# - API: 19% Steuer, hat Rabatt
# - Service: 20% Steuer (falsch!), hat Rabatt
# - Rechnung: keine Steuer, kein Rabatt (definitiv falsch!)
// Anfällig: Duplizierte Autorisierungslogik über Schichten
// Web-API-Schicht
public class VulnerableDocumentController : ApiController
{
[HttpGet]
public IHttpActionResult GetDocument(int documentId, int userId)
{
// Anfällig: Autorisierung im Controller
var document = _documentRepo.GetById(documentId);
if (document.OwnerId != userId &&
!IsAdmin(userId) &&
!document.SharedWith.Contains(userId))
{
return Unauthorized();
}
return Ok(document);
}
[HttpDelete]
public IHttpActionResult DeleteDocument(int documentId, int userId)
{
// Anfällig: DIESELBE Autorisierung dupliziert
var document = _documentRepo.GetById(documentId);
if (document.OwnerId != userId &&
!IsAdmin(userId) &&
!document.SharedWith.Contains(userId)) // BUG: Geteilte Benutzer sollten nicht löschen dürfen!
{
return Unauthorized();
}
_documentService.Delete(documentId);
return Ok();
}
}
// Service-Schicht
public class VulnerableDocumentService
{
public Document GetDocument(int documentId, int userId)
{
var document = _repository.GetById(documentId);
// Anfällig: DIESELBE Prüfung dupliziert, aber unterschiedlich!
if (document.OwnerId != userId && !IsAdmin(userId))
// BUG: Vergessen SharedWith zu prüfen!
{
throw new UnauthorizedAccessException();
}
return document;
}
public void Delete(int documentId, int userId)
{
// Anfällig: Gar keine Autorisierungsprüfung in dieser Methode!
// Nimmt an, Controller hat bereits geprüft...
_repository.Delete(documentId);
}
}
// Sicherheitslücken durch inkonsistente Implementierungen:
// 1. Geteilte Benutzer können Dokumente löschen (Controller-Bug)
// 2. Service-Schicht prüft SharedWith nicht (Service-Bug)
// 3. Delete im Service hat keine Autorisierung (fehlende Prüfung)
Korrigierter Code
// Korrigiert: Zentralisierte Validierung in dedizierter Komponente
// Validierungsschicht - Einzige Wahrheitsquelle
@Component
public class UserValidator {
public ValidationResult validate(UserDTO user) {
List<String> errors = new ArrayList<>();
if (user.getUsername() == null || user.getUsername().length() < 3) {
errors.add("Benutzername muss mindestens 3 Zeichen haben");
}
if (user.getEmail() == null || !isValidEmail(user.getEmail())) {
errors.add("Gültige E-Mail-Adresse erforderlich");
}
if (user.getPassword() == null || !isStrongPassword(user.getPassword())) {
errors.add("Passwort muss mindestens 8 Zeichen mit Groß-/Kleinschreibung haben");
}
return new ValidationResult(errors);
}
private boolean isValidEmail(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
private boolean isStrongPassword(String password) {
return password != null &&
password.length() >= 8 &&
password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*");
}
}
// Präsentationsschicht - Verwendet Validator
@Controller
public class FixedUserController {
private final UserValidator validator;
private final UserService userService;
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
// Korrigiert: An Validator delegieren
ValidationResult result = validator.validate(user);
if (!result.isValid()) {
return ResponseEntity.badRequest().body(result.getErrors());
}
return ResponseEntity.ok(userService.createUser(user));
}
}
// Geschäftslogikschicht - Verwendet denselben Validator
@Service
public class FixedUserService {
private final UserValidator validator;
private final UserRepository userRepository;
@Transactional
public User createUser(UserDTO userDTO) {
// Korrigiert: Derselbe Validator konsistent verwendet
ValidationResult result = validator.validate(userDTO);
if (!result.isValid()) {
throw new ValidationException(result.getErrors());
}
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPasswordHash(hashPassword(userDTO.getPassword()));
return userRepository.save(user);
}
}
// Datenzugriffsschicht - KEINE Validierung (nicht ihre Verantwortung)
@Repository
public class FixedUserRepository {
public User save(User user) {
// Korrigiert: Repository behandelt nur Persistenz
// Validierung erfolgt in höheren Schichten
return jdbcTemplate.insert(user);
}
}
# Korrigiert: Zentralisierter Preisservice
from dataclasses import dataclass
from decimal import Decimal
from typing import List
@dataclass
class PriceCalculation:
subtotal: Decimal
discount_amount: Decimal
tax_amount: Decimal
total: Decimal
class PricingService:
"""Einzige Wahrheitsquelle für alle Preisberechnungen."""
TAX_RATE = Decimal('0.19') # 19% MwSt
def calculate_item_price(self, item) -> Decimal:
"""Preis für einzelnen Artikel berechnen."""
base_price = Decimal(str(item.price)) * item.quantity
if item.discount:
discount_multiplier = 1 - (Decimal(str(item.discount)) / 100)
base_price *= discount_multiplier
return base_price
def calculate_total(self, items: List) -> PriceCalculation:
"""Vollständige Bestellsumme berechnen."""
subtotal = sum(
self.calculate_item_price(item) for item in items
)
discount_amount = Decimal('0') # Könnte Bestellebenen-Rabatte hinzufügen
tax_amount = subtotal * self.TAX_RATE
total = subtotal + tax_amount
return PriceCalculation(
subtotal=subtotal,
discount_amount=discount_amount,
tax_amount=tax_amount,
total=total
)
# API-Schicht - Verwendet Preisservice
class FixedPriceController:
def __init__(self, pricing_service: PricingService):
self._pricing = pricing_service
def calculate_price(self, request):
items = self._parse_items(request)
result = self._pricing.calculate_total(items)
return {
'subtotal': str(result.subtotal),
'tax': str(result.tax_amount),
'total': str(result.total)
}
# Service-Schicht - Verwendet denselben Preisservice
class FixedOrderService:
def __init__(self, pricing_service: PricingService):
self._pricing = pricing_service
def calculate_order_total(self, order) -> Decimal:
result = self._pricing.calculate_total(order.items)
return result.total
# Rechnungsgenerator - Verwendet denselben Preisservice
class FixedInvoiceGenerator:
def __init__(self, pricing_service: PricingService):
self._pricing = pricing_service
def generate_invoice(self, order):
result = self._pricing.calculate_total(order.items)
return self.create_invoice_pdf(
order,
subtotal=result.subtotal,
tax=result.tax_amount,
total=result.total
)
# Alle Schichten verwenden jetzt dieselbe Preislogik - überall konsistent!
// Korrigiert: Zentralisierter Autorisierungsservice
// Autorisierungsservice - Einzelner Kontrollpunkt
public interface IAuthorizationService
{
bool CanRead(int userId, Document document);
bool CanDelete(int userId, Document document);
void EnsureCanRead(int userId, Document document);
void EnsureCanDelete(int userId, Document document);
}
public class DocumentAuthorizationService : IAuthorizationService
{
private readonly IUserService _userService;
public bool CanRead(int userId, Document document)
{
return document.OwnerId == userId ||
_userService.IsAdmin(userId) ||
document.SharedWith.Contains(userId);
}
public bool CanDelete(int userId, Document document)
{
// Nur Eigentümer oder Admin können löschen - NICHT geteilte Benutzer
return document.OwnerId == userId ||
_userService.IsAdmin(userId);
}
public void EnsureCanRead(int userId, Document document)
{
if (!CanRead(userId, document))
{
throw new UnauthorizedAccessException(
$"Benutzer {userId} kann Dokument {document.Id} nicht lesen");
}
}
public void EnsureCanDelete(int userId, Document document)
{
if (!CanDelete(userId, document))
{
throw new UnauthorizedAccessException(
$"Benutzer {userId} kann Dokument {document.Id} nicht löschen");
}
}
}
// Controller - Delegiert an Autorisierungsservice
public class FixedDocumentController : ApiController
{
private readonly IDocumentService _documentService;
private readonly IAuthorizationService _auth;
[HttpGet]
public IHttpActionResult GetDocument(int documentId)
{
var userId = GetCurrentUserId();
var document = _documentService.GetDocument(documentId, userId);
return Ok(document);
}
[HttpDelete]
public IHttpActionResult DeleteDocument(int documentId)
{
var userId = GetCurrentUserId();
_documentService.Delete(documentId, userId);
return Ok();
}
}
// Service - Verwendet denselben Autorisierungsservice
public class FixedDocumentService : IDocumentService
{
private readonly IDocumentRepository _repository;
private readonly IAuthorizationService _auth;
public Document GetDocument(int documentId, int userId)
{
var document = _repository.GetById(documentId);
_auth.EnsureCanRead(userId, document);
return document;
}
public void Delete(int documentId, int userId)
{
var document = _repository.GetById(documentId);
_auth.EnsureCanDelete(userId, document);
_repository.Delete(documentId);
}
}
// Autorisierung ist jetzt:
// 1. Einmal im AuthorizationService definiert
// 2. Konsistent über alle Schichten
// 3. Einfach zu auditieren und zu ändern
// 4. Trennt ordnungsgemäß Lese- vs. Löschberechtigungen
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein Codequalitäts-/Wartbarkeitsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-710: Improper Adherence to Coding Standards (Eltern)
- CWE-1006: Bad Coding Practices (Kategoriemitglied)
- CWE-1061: Insufficient Encapsulation (verwandt)
Referenzen
- MITRE Corporation. "CWE-1092: Use of Same Invokable Control Element in Multiple Architectural Layers." https://cwe.mitre.org/data/definitions/1092.html
- Martin, Robert C. "Clean Architecture."
- CISQ Quality Measures - Maintainability.