Violation of Secure Design Principles

Description

Violation of Secure Design Principles occurs when software architecture and design don't follow established security principles. This includes violating least privilege, defense in depth, fail-safe defaults, economy of mechanism, complete mediation, open design, separation of privilege, least common mechanism, and psychological acceptability. Poor design leads to systemic vulnerabilities that are difficult to fix later.

Risk

Architectural vulnerabilities pervade entire systems. Single points of failure compromise all security. Excessive privileges enable larger breaches. Missing defense layers allow easy exploitation. Complex designs hide security flaws. Poor compartmentalization allows lateral movement. Design flaws require costly rewrites to fix.

Solution

Apply security principles from design phase. Implement defense in depth. Follow principle of least privilege. Use fail-safe defaults. Keep security mechanisms simple. Verify authorization on every access. Separate critical functions. Minimize shared resources. Design for usability and security together.

Common Consequences

ImpactDetails
SecurityScope: Systemic Vulnerabilities

Design flaws affect entire application.
MaintenanceScope: Technical Debt

Insecure design requires extensive refactoring.
ComplianceScope: Audit Failures

Violations of security standards and regulations.

Example Code + Solution Code

Vulnerable Code

// VIOLATION: Least Privilege - Everything runs as admin
public class VulnerableService {

    // VIOLATION: Service runs with admin credentials for everything
    private DataSource adminConnection = createAdminConnection();

    public List<Product> getPublicProducts() {
        // Uses admin connection for read-only public query
        return adminConnection.query("SELECT * FROM products WHERE public = true");
    }

    public void deleteUser(String userId) {
        // Same admin connection
        adminConnection.execute("DELETE FROM users WHERE id = ?", userId);
    }
}

// VIOLATION: Defense in Depth - Single security layer
public class VulnerableSingleLayerSecurity {

    public void accessResource(String userId, String resourceId) {
        // VIOLATION: Only one security check
        if (isAuthenticated(userId)) {
            // No authorization check
            // No input validation
            // No audit logging
            return getResource(resourceId);
        }
    }
}

// VIOLATION: Fail-Safe Defaults - Default to allow
public class VulnerableDefaultAllow {

    public boolean checkPermission(String userId, String action) {
        try {
            Permission perm = permissionService.get(userId, action);
            return perm.isAllowed();
        } catch (Exception e) {
            // VIOLATION: Defaults to allow on error
            return true;
        }
    }
}

// VIOLATION: Economy of Mechanism - Overcomplicated security
public class VulnerableComplexSecurity {

    public boolean authenticate(Request req) {
        // VIOLATION: Complex, hard to audit
        return checkHeader(req) &&
               checkCookie(req) &&
               checkSession(req) &&
               checkToken(req) &&
               checkCertificate(req) &&
               checkIPRange(req) &&
               checkTimeOfDay(req) &&
               checkMoonPhase(req) && // Joke, but illustrates complexity
               validateAllTheThings(req);
    }
}
# VIOLATION: Complete Mediation - Not checking every access
class VulnerableIncompleteMediation:
    def __init__(self):
        self.verified_users = set()

    def access_resource(self, user_id, resource_id):
        # VIOLATION: Only checks first time
        if user_id not in self.verified_users:
            if self.check_authorization(user_id, resource_id):
                self.verified_users.add(user_id)  # Cached forever
            else:
                raise PermissionError()

        # Subsequent accesses skip authorization
        return self.get_resource(resource_id)

# VIOLATION: Separation of Privilege - Single key for everything
class VulnerableSinglePrivilege:
    def __init__(self, master_key):
        # VIOLATION: One key controls everything
        self.master_key = master_key

    def authenticate(self, key):
        # Master key grants all access
        return key == self.master_key

    def delete_data(self, key):
        if self.authenticate(key):
            self.db.delete_all()

    def read_secrets(self, key):
        if self.authenticate(key):
            return self.get_all_secrets()

# VIOLATION: Least Common Mechanism - Shared resources
class VulnerableSharedResources:
    # VIOLATION: All tenants share same connection pool
    shared_pool = ConnectionPool()

    # VIOLATION: All tenants share same cache
    shared_cache = Cache()

    # VIOLATION: All tenants share same file storage
    shared_storage = FileStorage('/data')

    def get_tenant_data(self, tenant_id, query):
        # Relies on query to isolate data - not enforced at resource level
        return self.shared_pool.execute(
            f"SELECT * FROM data WHERE tenant_id = '{tenant_id}'"
        )
// VIOLATION: Multiple principles in web application
class VulnerableWebApp {
    constructor() {
        // VIOLATION: Least Privilege - Global admin context
        this.db = new DatabaseConnection({ user: 'admin' });
    }

    // VIOLATION: Defense in Depth - Single auth check
    async handleRequest(req) {
        if (req.cookies.session) {
            // No token validation
            // No CSRF protection
            // No rate limiting
            return this.processRequest(req);
        }
        return { error: 'Unauthorized' };
    }

    // VIOLATION: Fail-Safe Defaults
    async checkAccess(userId, resource) {
        try {
            const allowed = await this.permissionService.check(userId, resource);
            return allowed;
        } catch (error) {
            // VIOLATION: Error = allow
            console.error(error);
            return true;
        }
    }

    // VIOLATION: Complete Mediation - Caching auth decisions
    async getResource(userId, resourceId) {
        const cacheKey = `auth:${userId}`;

        // VIOLATION: Cached auth decision used without refresh
        if (this.cache.has(cacheKey)) {
            return this.loadResource(resourceId);
        }

        if (await this.checkAccess(userId, resourceId)) {
            // Cache forever - permissions changes not reflected
            this.cache.set(cacheKey, true);
            return this.loadResource(resourceId);
        }

        throw new Error('Forbidden');
    }
}

// VIOLATION: Open Design - Security depends on secrecy
const SECRET_ADMIN_PATH = '/xK9mN2pL/admin';  // "Hidden" admin

app.get(SECRET_ADMIN_PATH, (req, res) => {
    // No authentication - URL secrecy is the "security"
    res.json(getAllAdminData());
});

Fixed Code

// CORRECT: Following secure design principles
public class SecureService {

    // PRINCIPLE: Least Privilege - Separate connections per role
    private DataSource readOnlyConnection;
    private DataSource writeConnection;
    private DataSource adminConnection;

    // PRINCIPLE: Economy of Mechanism - Simple, auditable
    public List<Product> getPublicProducts() {
        // Uses minimal privilege for the operation
        return readOnlyConnection.query(
            "SELECT * FROM products WHERE public = true"
        );
    }

    @RequiresRole("ADMIN")
    @AuditLogged
    public void deleteUser(String adminUserId, String targetUserId) {
        // Uses admin connection only for admin operations
        adminConnection.execute("DELETE FROM users WHERE id = ?", targetUserId);
    }
}

// CORRECT: Defense in Depth - Multiple security layers
public class SecureMultiLayerSecurity {

    public Object accessResource(Request request, String resourceId) {
        // Layer 1: Input validation
        if (!validator.isValidResourceId(resourceId)) {
            throw new InvalidInputException();
        }

        // Layer 2: Authentication
        User user = authService.authenticate(request);
        if (user == null) {
            throw new AuthenticationException();
        }

        // Layer 3: Authorization
        if (!authzService.canAccess(user, resourceId)) {
            throw new AuthorizationException();
        }

        // Layer 4: Rate limiting
        if (!rateLimiter.allowRequest(user.getId())) {
            throw new RateLimitException();
        }

        // Layer 5: Get resource
        Object resource = resourceService.get(resourceId);

        // Layer 6: Audit logging
        auditLog.logAccess(user, resourceId);

        return resource;
    }
}

// CORRECT: Fail-Safe Defaults - Default to deny
public class SecureDefaultDeny {

    public boolean checkPermission(String userId, String action) {
        // Default is deny
        boolean allowed = false;

        try {
            Permission perm = permissionService.get(userId, action);
            if (perm != null) {
                allowed = perm.isAllowed();
            }
        } catch (Exception e) {
            // PRINCIPLE: Fail-safe - deny on error
            log.error("Permission check failed, denying access", e);
            allowed = false;
        }

        return allowed;
    }
}

// CORRECT: Separation of Privilege - Multiple factors required
public class SecureSeparatedPrivilege {

    public boolean authorizeHighRiskAction(
            String userId,
            String password,
            String totpCode,
            String approverUserId
    ) {
        // PRINCIPLE: Multiple independent factors
        // Factor 1: User authenticated
        if (!authService.verifyPassword(userId, password)) {
            return false;
        }

        // Factor 2: MFA verified
        if (!totpService.verify(userId, totpCode)) {
            return false;
        }

        // Factor 3: Second person approval (separation of duties)
        if (!approvalService.isApproved(approverUserId, userId)) {
            return false;
        }

        return true;
    }
}
# CORRECT: Complete Mediation - Check every access
class SecureCompleteMediation:
    def access_resource(self, user_id, resource_id):
        # PRINCIPLE: Always verify on every request
        # Never rely on cached authorization decisions

        # Verify authentication
        user = self.auth_service.verify_session(user_id)
        if not user:
            raise AuthenticationError()

        # Verify authorization (fresh check, not cached)
        if not self.authz_service.can_access(user, resource_id):
            raise AuthorizationError()

        # Get resource
        resource = self.resource_service.get(resource_id)

        # Audit
        self.audit_log.log_access(user, resource_id)

        return resource

# CORRECT: Least Common Mechanism - Isolated resources per tenant
class SecureIsolatedResources:
    def __init__(self):
        self.tenant_pools = {}
        self.tenant_caches = {}
        self.tenant_storage = {}

    def get_tenant_pool(self, tenant_id):
        # PRINCIPLE: Each tenant gets isolated resources
        if tenant_id not in self.tenant_pools:
            self.tenant_pools[tenant_id] = ConnectionPool(
                database=f'tenant_{tenant_id}_db',
                user=f'tenant_{tenant_id}_user'
            )
        return self.tenant_pools[tenant_id]

    def get_tenant_data(self, tenant_id, query):
        # Uses tenant-specific connection
        # Database-level isolation, not just query filtering
        pool = self.get_tenant_pool(tenant_id)
        return pool.execute(query)

# CORRECT: Defense in Depth implementation
class SecureDefenseInDepth:
    def process_request(self, request):
        # Layer 1: TLS/Transport security (handled at infrastructure)

        # Layer 2: Input validation
        validated_input = self.validate_input(request)

        # Layer 3: Authentication
        user = self.authenticate(request)

        # Layer 4: Authorization
        self.authorize(user, validated_input.action)

        # Layer 5: Business logic with validation
        result = self.execute_action(user, validated_input)

        # Layer 6: Output encoding
        safe_output = self.encode_output(result)

        # Layer 7: Audit logging
        self.audit_log(user, validated_input.action, result.success)

        return safe_output
// CORRECT: Following all secure design principles
class SecureWebApp {
    constructor() {
        // PRINCIPLE: Least Privilege - Different connections for different needs
        this.readOnlyDb = new DatabaseConnection({ user: 'reader', readOnly: true });
        this.writeDb = new DatabaseConnection({ user: 'writer' });
        this.adminDb = new DatabaseConnection({ user: 'admin' });
    }

    // PRINCIPLE: Defense in Depth - Multiple security layers
    async handleRequest(req) {
        // Layer 1: Input validation
        const validatedInput = this.validateInput(req.body);

        // Layer 2: Rate limiting
        if (!await this.rateLimiter.check(req.ip)) {
            throw new RateLimitError();
        }

        // Layer 3: Authentication
        const user = await this.authenticate(req);

        // Layer 4: CSRF validation
        if (!this.validateCsrfToken(req, user)) {
            throw new CsrfError();
        }

        // Layer 5: Authorization
        if (!await this.authorize(user, validatedInput.action)) {
            throw new AuthorizationError();
        }

        // Layer 6: Process request
        const result = await this.processRequest(user, validatedInput);

        // Layer 7: Audit logging
        await this.auditLog(user, validatedInput.action, result);

        return result;
    }

    // PRINCIPLE: Fail-Safe Defaults - Deny on error
    async checkAccess(userId, resource) {
        try {
            const allowed = await this.permissionService.check(userId, resource);
            return allowed === true;  // Explicit true check
        } catch (error) {
            // Log and deny
            this.logger.error('Access check failed', { userId, resource, error });
            return false;  // Fail-safe: deny
        }
    }

    // PRINCIPLE: Complete Mediation - Fresh authorization check every time
    async getResource(userId, resourceId) {
        // Always verify authorization (no caching of auth decisions)
        if (!await this.authorize(userId, resourceId)) {
            throw new ForbiddenError();
        }

        const resource = await this.loadResource(resourceId);

        // Log access
        await this.auditLog({ userId, action: 'read', resourceId });

        return resource;
    }

    // PRINCIPLE: Separation of Privilege for high-risk operations
    async deleteAllUserData(userId, password, totpCode, adminApproval) {
        // Multiple factors required
        const passwordValid = await this.verifyPassword(userId, password);
        const totpValid = await this.verifyTotp(userId, totpCode);
        const approved = await this.verifyAdminApproval(adminApproval);

        if (!passwordValid || !totpValid || !approved) {
            throw new InsufficientPrivilegeError();
        }

        return this.performDeletion(userId);
    }
}

// PRINCIPLE: Open Design - Security doesn't depend on secrecy
// Admin routes are documented but require authentication
app.get('/api/admin/users',
    authenticate,
    requireRole('admin'),
    auditLog('admin_users_list'),
    adminController.listUsers
);

Exploited in the Wild

Privilege Escalation

Lack of least privilege led to massive breaches.

Single Point Failures

Missing defense in depth allowed complete compromise.

Cached Auth Bypass

Authorization caching enabled access after revocation.


Tools to test/exploit

  • Architecture review tools.

  • Threat modeling frameworks.

  • Security design checklists.


CVE Examples

  • Architectural flaws in major applications.

  • Design-level vulnerabilities.


References

  1. MITRE. "CWE-657: Violation of Secure Design Principles." https://cwe.mitre.org/data/definitions/657.html

  2. Saltzer and Schroeder. "The Protection of Information in Computer Systems."