Use of Same Invokable Control Element in Multiple Architectural Layers
Description
Use of Same Invokable Control Element in Multiple Architectural Layers occurs when a product implements identical control elements (such as functions, methods, or modules) across multiple architectural layers rather than maintaining proper separation of concerns. This violates the principle of layered architecture where each layer should have distinct responsibilities. When the same functionality is duplicated across layers (e.g., presentation, business logic, data access), it creates maintenance challenges and potential inconsistencies.
Risk
Duplicating control elements across architectural layers has indirect security implications. Inconsistent implementations across layers create security gaps when one copy is patched but others are not. Security fixes must be applied multiple times, increasing the chance of missing one. Code review becomes more difficult as reviewers must check multiple locations. Changes to security logic must be synchronized across all layers. The complexity makes it harder to ensure security controls are applied consistently. Divergent implementations over time can create exploitable inconsistencies.
Solution
Follow proper layered architecture principles with clear separation of concerns. Implement shared functionality in a common service layer accessible to all architectural layers. Use dependency injection to provide shared services to different layers. Define clear interfaces between layers and enforce them. Apply the DRY (Don't Repeat Yourself) principle consistently. Use architectural analysis tools to detect layer violations. Conduct architecture reviews to ensure proper separation. Document layer responsibilities and boundaries clearly.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Duplicate code across layers is harder to maintain consistently. |
| Other | Scope: Other Increase Analytical Complexity - Security analysis must cover multiple implementations of the same logic. |
| Integrity | Scope: Integrity Inconsistent State - Different implementations may behave differently, causing inconsistencies. |
Example Code
Vulnerable Code
// Vulnerable: Same validation logic duplicated across layers
// Presentation Layer
@Controller
public class VulnerableUserController {
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
// Vulnerable: Validation logic in presentation layer
if (user.getUsername() == null || user.getUsername().length() < 3) {
return ResponseEntity.badRequest().body("Invalid username");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
return ResponseEntity.badRequest().body("Invalid email");
}
if (user.getPassword() == null || user.getPassword().length() < 8) {
return ResponseEntity.badRequest().body("Invalid password");
}
return userService.createUser(user);
}
}
// Business Logic Layer
@Service
public class VulnerableUserService {
public User createUser(UserDTO userDTO) {
// Vulnerable: SAME validation logic duplicated here!
if (userDTO.getUsername() == null || userDTO.getUsername().length() < 3) {
throw new ValidationException("Invalid username");
}
if (userDTO.getEmail() == null || !userDTO.getEmail().contains("@")) {
throw new ValidationException("Invalid email");
}
if (userDTO.getPassword() == null || userDTO.getPassword().length() < 8) {
throw new ValidationException("Invalid password");
}
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPasswordHash(hashPassword(userDTO.getPassword()));
return userRepository.save(user);
}
}
// Data Access Layer
@Repository
public class VulnerableUserRepository {
public User save(User user) {
// Vulnerable: SAME validation logic duplicated AGAIN!
if (user.getUsername() == null || user.getUsername().length() < 3) {
throw new DataIntegrityException("Invalid username");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
throw new DataIntegrityException("Invalid email");
}
// Save to database
return jdbcTemplate.insert(user);
}
}
// Problem: If validation rules change (e.g., minimum password length),
// must update THREE places. Easy to miss one, creating inconsistency.
# Vulnerable: Duplicate price calculation across layers
# API Layer
class VulnerablePriceController:
def calculate_price(self, request):
items = request.get('items', [])
# Vulnerable: Price calculation in API layer
total = 0
for item in items:
price = item['price'] * item['quantity']
if item.get('discount'):
price *= (1 - item['discount'] / 100)
total += price
# Apply tax
total *= 1.19 # 19% tax
return {'total': total}
# Service Layer
class VulnerablePriceService:
def calculate_order_total(self, order):
# Vulnerable: SAME calculation duplicated!
total = 0
for item in order.items:
price = item.price * item.quantity
if item.discount:
price *= (1 - item.discount / 100)
total += price
# Apply tax - but wait, this uses 20%!
total *= 1.20 # BUG: Different tax rate!
return total
# Background Job Layer
class VulnerableInvoiceGenerator:
def generate_invoice(self, order):
# Vulnerable: SAME calculation duplicated AGAIN!
total = 0
for item in order.items:
price = item.price * item.quantity
# Oops, forgot discount logic here!
total += price
# Tax calculation missing entirely!
return self.create_invoice_pdf(order, total)
# Problem: Three different implementations with inconsistencies:
# - API: 19% tax, has discount
# - Service: 20% tax (wrong!), has discount
# - Invoice: no tax, no discount (definitely wrong!)
// Vulnerable: Duplicate authorization logic across layers
// Web API Layer
public class VulnerableDocumentController : ApiController
{
[HttpGet]
public IHttpActionResult GetDocument(int documentId, int userId)
{
// Vulnerable: Authorization in 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)
{
// Vulnerable: SAME authorization duplicated
var document = _documentRepo.GetById(documentId);
if (document.OwnerId != userId &&
!IsAdmin(userId) &&
!document.SharedWith.Contains(userId)) // BUG: Shared users shouldn't delete!
{
return Unauthorized();
}
_documentService.Delete(documentId);
return Ok();
}
}
// Service Layer
public class VulnerableDocumentService
{
public Document GetDocument(int documentId, int userId)
{
var document = _repository.GetById(documentId);
// Vulnerable: SAME check duplicated, but different!
if (document.OwnerId != userId && !IsAdmin(userId))
// BUG: Forgot to check SharedWith!
{
throw new UnauthorizedAccessException();
}
return document;
}
public void Delete(int documentId, int userId)
{
// Vulnerable: No authorization check at all in this method!
// Assumes controller already checked...
_repository.Delete(documentId);
}
}
// Security holes created by inconsistent implementations:
// 1. Shared users can delete documents (controller bug)
// 2. Service layer doesn't check SharedWith (service bug)
// 3. Delete in service has no authorization (missing check)
Fixed Code
// Fixed: Centralized validation in dedicated component
// Validation Layer - Single source of truth
@Component
public class UserValidator {
public ValidationResult validate(UserDTO user) {
List<String> errors = new ArrayList<>();
if (user.getUsername() == null || user.getUsername().length() < 3) {
errors.add("Username must be at least 3 characters");
}
if (user.getEmail() == null || !isValidEmail(user.getEmail())) {
errors.add("Valid email address required");
}
if (user.getPassword() == null || !isStrongPassword(user.getPassword())) {
errors.add("Password must be at least 8 characters with mixed case");
}
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].*");
}
}
// Presentation Layer - Uses validator
@Controller
public class FixedUserController {
private final UserValidator validator;
private final UserService userService;
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody UserDTO user) {
// Fixed: Delegate to validator
ValidationResult result = validator.validate(user);
if (!result.isValid()) {
return ResponseEntity.badRequest().body(result.getErrors());
}
return ResponseEntity.ok(userService.createUser(user));
}
}
// Business Logic Layer - Uses same validator
@Service
public class FixedUserService {
private final UserValidator validator;
private final UserRepository userRepository;
@Transactional
public User createUser(UserDTO userDTO) {
// Fixed: Same validator used consistently
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);
}
}
// Data Access Layer - NO validation (not its responsibility)
@Repository
public class FixedUserRepository {
public User save(User user) {
// Fixed: Repository only handles persistence
// Validation is done at higher layers
return jdbcTemplate.insert(user);
}
}
# Fixed: Centralized pricing service
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:
"""Single source of truth for all pricing calculations."""
TAX_RATE = Decimal('0.19') # 19% VAT
def calculate_item_price(self, item) -> Decimal:
"""Calculate price for a single item."""
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:
"""Calculate complete order total."""
subtotal = sum(
self.calculate_item_price(item) for item in items
)
discount_amount = Decimal('0') # Could add order-level discounts
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 Layer - Uses pricing service
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 Layer - Uses same pricing service
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
# Invoice Generator - Uses same pricing service
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
)
# All layers now use the same pricing logic - consistent everywhere!
// Fixed: Centralized authorization service
// Authorization Service - Single point of control
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)
{
// Only owner or admin can delete - NOT shared users
return document.OwnerId == userId ||
_userService.IsAdmin(userId);
}
public void EnsureCanRead(int userId, Document document)
{
if (!CanRead(userId, document))
{
throw new UnauthorizedAccessException(
$"User {userId} cannot read document {document.Id}");
}
}
public void EnsureCanDelete(int userId, Document document)
{
if (!CanDelete(userId, document))
{
throw new UnauthorizedAccessException(
$"User {userId} cannot delete document {document.Id}");
}
}
}
// Controller - Delegates to authorization service
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 - Uses same authorization service
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);
}
}
// Authorization is now:
// 1. Defined once in AuthorizationService
// 2. Consistent across all layers
// 3. Easy to audit and modify
// 4. Properly separates read vs delete permissions
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality/maintainability concern rather than a direct security vulnerability.
Related CWEs
- CWE-710: Improper Adherence to Coding Standards (parent)
- CWE-1006: Bad Coding Practices (category member)
- CWE-1061: Insufficient Encapsulation (related)
References
- 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.