Broken Access Control in Spring Boot with Cockroachdb
Broken Access Control in Spring Boot with Cockroachdb — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when an application fails to enforce proper authorization checks, allowing one user to access or modify data and functionality intended for another. In a Spring Boot application backed by Cockroachdb, this risk is shaped by how the framework handles authentication, how queries are constructed, and how database permissions are configured.
Spring Security provides method-level and request-level authorization constructs, but if these are inconsistently applied or bypassed, attackers can manipulate identifiers (IDs, tokens) to access other users’ resources. Cockroachdb, as a distributed SQL database, does not inherently enforce row-level permissions; it relies on the application to supply correct filters (e.g., tenant_id = ?). If those filters are missing or incorrectly composed, a user may issue a request such as /api/users/123 and, by altering the ID, access another user’s data stored in Cockroachdb without triggering authorization logic.
Common patterns that expose the vulnerability include:
- Using path variables or request parameters directly in queries without validating ownership (e.g., SELECT * FROM accounts WHERE id = ? without checking tenant or user context).
- Overly broad @PreAuthorize expressions or missing role checks on service methods that interact with Cockroachdb repositories.
- Insecure direct object references (IDOR) in REST endpoints where numeric or UUID identifiers are enumerable and not bound to the requesting user’s permissions.
- Misconfigured database users in Spring’s datasource configuration that grant broader read/write scopes than necessary, enabling privilege escalation across logical boundaries.
Because Cockroachdb supports PostgreSQL wire protocol and standard JDBC/ORM interactions, typical Spring Data JPA or MyBatis patterns are used. If these patterns omit tenant context or rely on client-supplied identifiers without server-side validation, the API surface becomes susceptible to IDOR and BOLA (Broken Object Level Authorization). Runtime findings from an unauthenticated scan may show endpoints that accept tampered IDs and return data without confirming identity or scope, even when Spring Security annotations are present but not uniformly applied.
Additionally, improper handling of ownership in composite keys (e.g., (user_id, resource_id)) can lead to scenarios where an attacker iterates over plausible resource IDs and reads records they should not see. The distributed nature of Cockroachdb does not mitigate this; if authorization checks are absent at the application layer, the database will faithfully return whatever rows match the query conditions, including cross-user data.
Cockroachdb-Specific Remediation in Spring Boot — concrete code fixes
To secure a Spring Boot application using Cockroachdb, implement strict row-level checks, parameterized queries, and least-privilege database roles. Below are concrete, realistic examples that align with the platform’s SQL semantics.
1. Enforce tenant and user ownership in queries
Always include tenant or user context in WHERE clauses. Do not rely on client-supplied IDs alone.
@Repository
public interface AccountRepository extends JpaRepository {
@Query("SELECT a FROM Account a WHERE a.id = :id AND a.tenantId = :tenantId")
Optional findByIdAndTenantId(@Param("id") Long id, @Param("tenantId") UUID tenantId);
}
In your service, resolve the current tenant from the authentication context and pass it to the repository. This ensures that even if an attacker enumerates IDs, rows from other tenants are not returned by Cockroachdb.
2. Use role-based method security consistently
Apply method-level security and validate permissions before invoking database operations.
@Service
public class AccountService {
private final AccountRepository accountRepository;
private final SecurityService securityService;
public AccountService(AccountRepository accountRepository, SecurityService securityService) {
this.accountRepository = accountRepository;
this.securityService = securityService;
}
public Account getAccount(Long id) {
String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
UUID tenantId = securityService.resolveTenant(currentUser);
return accountRepository.findByIdAndTenantId(id, tenantId)
.orElseThrow(() -> new AccessDeniedException("Account not found or access denied"));
}
}
3. Apply least-privilege database roles in Cockroachdb
Create dedicated database users for your application and restrict their permissions to the minimum required set. For example, avoid granting blanket SELECT on all tables; scope access to specific schemas and rows when possible.
-- Example Cockroachdb role setup (run via migration or admin client)
CREATE ROLE app_reader;
GRANT SELECT ON TABLE accounts TO app_reader;
GRANT SELECT ON TABLE tenant_config TO app_reader;
CREATE ROLE app_writer;
GRANT SELECT, INSERT, UPDATE ON TABLE accounts TO app_writer;
GRANT SELECT, INSERT ON TABLE audit_log TO app_writer;
CREATE USER spring_app WITH PASSWORD 'strong_password';
GRANT app_reader, app_writer TO spring_app;
Ensure your Spring Boot datasource configuration uses these roles appropriately, and rotate credentials regularly. This reduces the impact of a compromised credential and aligns with principle of least privilege.
4. Validate and encode all inputs
Treat all incoming identifiers as untrusted. Use validation annotations and enforce type constraints before constructing queries.
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
private final AccountService accountService;
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
@GetMapping("/{id}")
public ResponseEntity getAccount(@PathVariable Long id, @RequestHeader("X-Tenant-ID") UUID tenantId) {
if (id == null || id <= 0) {
return ResponseEntity.badRequest().build();
}
return accountService.getAccount(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
Combine these measures with regular scans using tools that understand your stack. middleBrick, for instance, can be integrated into your workflow via the CLI (middlebrick scan