Broken Access Control with Mutual Tls
How Broken Access Control Manifests in Mutual Tls
Broken Access Control in Mutual TLS environments occurs when the mutual authentication process fails to properly validate the client's identity and authorization level before granting access to protected resources. Unlike basic TLS where only the server is authenticated, Mutual TLS requires both parties to present valid certificates, creating a false sense of security if access control checks are not properly implemented.
A common vulnerability arises when developers assume that a successful Mutual TLS handshake automatically grants appropriate permissions. This leads to scenarios where any client with a valid certificate can access resources they shouldn't have access to. For example, a financial API might authenticate a client certificate but fail to check whether that client is authorized to access sensitive account data.
Consider this vulnerable Mutual TLS endpoint:
app.get('/api/transactions', (req, res) => {
// Only checks if client cert exists, not WHO the client is
if (!req.client.authorized) {
return res.status(401).json({ error: 'Unauthorized' });
}
// No authorization check - any authenticated client can see all transactions
const transactions = db.getAllTransactions();
res.json(transactions);
});The vulnerability here is that the code only verifies the presence of a client certificate but never validates the client's identity against an authorization database or policy store. Any client with a valid certificate from the trusted CA can access all transaction data.
Another manifestation involves improper certificate validation logic. Developers might check certificate properties incorrectly, such as:
app.get('/api/sensitive-data', (req, res) => {
const cert = req.client.getPeerCertificate();
// Vulnerable: only checks CN, not the full certificate chain or revocation status
if (cert.subject.CN !== 'trusted-client') {
return res.status(403).json({ error: 'Access denied' });
}
// Even if CN matches, this doesn't verify the client's actual permissions
res.json(sensitiveData);
});This code is vulnerable because it relies on a single certificate property (Common Name) rather than implementing proper role-based access control. A compromised certificate with the same CN could gain unauthorized access.
Mutual Tls-Specific Detection
Detecting Broken Access Control in Mutual TLS requires both automated scanning and manual code review. The most effective approach combines runtime testing with static analysis of the authentication and authorization logic.
Automated detection tools like middleBrick can identify Mutual TLS-specific vulnerabilities by testing unauthenticated endpoints that should require client certificates. The scanner attempts to access protected resources without presenting a client certificate, then with different client certificates to identify authorization gaps. For Mutual TLS endpoints, middleBrick performs:
- Certificate validation bypass attempts
- Authorization bypass tests with valid but unauthorized certificates
- Property authorization checks for certificate-based access controls
- Input validation testing for certificate-related parameters
- SSRF testing to ensure certificate validation can't be bypassed through proxying
Here's how you can manually test for Mutual TLS Broken Access Control:
# Test without client certificate (should fail)
curl -k https://api.example.com/protected --cacert ca.crt
# Test with valid certificate but different identity
openssl req -x509 -newkey rsa:2048 -keyout client2.key -out client2.crt -days 365 -nodes
curl -k https://api.example.com/protected --key client2.key --cert client2.crt --cacert ca.crt
# Test with revoked certificate
curl -k https://api.example.com/protected --cert client-revoked.crt --key client-revoked.key --cacert ca.crtDuring runtime testing, monitor for these indicators of Broken Access Control:
| Indicator | What to Look For | Risk Level |
|---|---|---|
| Certificate-only authentication | Endpoints that only check certificate presence, not identity | High |
| Static CN validation | Code that only validates certificate Common Name | High |
| Missing revocation checks | No certificate revocation list (CRL) or OCSP verification | Medium |
| Role-based access missing | No mapping between certificate properties and user roles | Critical |
For comprehensive Mutual TLS security assessment, use middleBrick's CLI to scan your API endpoints:
npx middlebrick scan https://api.example.com --output json --verbose
# Or integrate into CI/CD
middlebrick scan https://staging-api.example.com --fail-below B
The scanner will identify if your Mutual TLS implementation has broken access control patterns and provide specific remediation guidance for each finding.
Mutual Tls-Specific Remediation
Fixing Broken Access Control in Mutual TLS requires implementing proper certificate validation and authorization checks. The solution involves three layers: certificate validation, identity mapping, and role-based access control.
First, implement comprehensive certificate validation:
const fs = require('fs');
const https = require('https');
const options = {
cert: fs.readFileSync('server.crt'),
key: fs.readFileSync('server.key'),
ca: [fs.readFileSync('ca.crt')],
requestCert: true,
rejectUnauthorized: true,
checkServerIdentity: (host, cert) => {
// Additional server identity validation
if (cert.subject.CN !== host) {
throw new Error('Certificate subject does not match host');
}
}
};
https.createServer(options, (req, res) => {
const cert = req.socket.getPeerCertificate();
// Verify certificate chain and revocation
if (!verifyCertificateChain(cert)) {
return res.writeHead(403).end('Invalid certificate chain');
}
// Check certificate revocation status
if (!checkCertificateRevocation(cert)) {
return res.writeHead(403).end('Certificate revoked');
}
// Map certificate to identity
const identity = mapCertificateToIdentity(cert);
if (!identity) {
return res.writeHead(403).end('Unknown client identity');
}
// Perform authorization check
if (!authorizeAccess(identity, req.url)) {
return res.writeHead(403).end('Insufficient permissions');
}
// Grant access
res.writeHead(200);
res.end('Authorized access granted');
}).listen(443);
function verifyCertificateChain(cert) {
// Implement chain verification logic
// Check if certificate is signed by trusted CA
// Verify intermediate certificates
return true;
}
function checkCertificateRevocation(cert) {
// Implement CRL or OCSP checking
// Return false if certificate is revoked
return true;
}
function mapCertificateToIdentity(cert) {
// Extract identity from certificate properties
// This could be from SAN, CN, or custom extensions
return {
id: cert.subject.CN,
roles: extractRolesFromCertificate(cert),
organization: cert.subject.O
};
}
function authorizeAccess(identity, resource) {
// Implement role-based access control
const permissions = {
'finance-admin': ['transactions', 'reports', 'users'],
'finance-viewer': ['transactions', 'reports'],
'external-partner': ['public-data']
};
const requiredPermission = resource.split('/')[2];
return identity.roles.some(role =>
permissions[role]?.includes(requiredPermission)
);
}
function extractRolesFromCertificate(cert) {
// Extract roles from certificate extensions or SAN
// This is application-specific
const roles = [];
// Check for custom extension containing roles
if (cert.extensions) {
cert.extensions.forEach(ext => {
if (ext.name === 'subjectAltName') {
// Parse SAN for role information
const sanRoles = parseRolesFromSAN(ext.value);
roles.push(...sanRoles);
}
});
}
return roles;
}
function parseRolesFromSAN(sanValue) {
// Parse roles from SAN field
// Format: DNS:role1.example.com,DNS:role2.example.com
const roles = [];
const parts = sanValue.split(',');
parts.forEach(part => {
if (part.startsWith('DNS:')) {
const domain = part.substring(4);
const roleMatch = domain.match(/role-([^.]+)//);
if (roleMatch) {
roles.push(roleMatch[1]);
}
}
});
return roles;
}For Java Spring Boot applications, implement Mutual TLS with Spring Security:
@Configuration
@EnableWebSecurity
public class MutualTlsSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.requiresChannel()
.anyRequest()
.requiresSecure()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)"))
.and()
.authorizeHttpRequests()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated();
return http.build();
}
@Bean
public X509AuthenticationProvider x509AuthenticationProvider() {
return new X509AuthenticationProvider();
}
@Bean
public AuthenticationManager authenticationManager(
X509AuthenticationProvider x509Provider) {
return new AuthenticationManager(x509Provider);
}
}
@Component
public class X509AuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
X509Certificate clientCert =
(X509Certificate) authentication.getCredentials();
// Verify certificate chain and revocation
if (!verifyCertificate(clientCert)) {
throw new BadCredentialsException("Invalid certificate");
}
// Extract identity and roles from certificate
String subject = clientCert.getSubjectX500Principal().getName();
String[] parts = subject.split(",");
Map attributes = new HashMap<>();
for (String part : parts) {
String[] kv = part.split("=");
if (kv.length == 2) {
attributes.put(kv[0].trim(), kv[1].trim());
}
}
String username = attributes.getOrDefault("CN", "unknown");
String role = determineRoleFromCertificate(clientCert);
// Create authentication token with roles
return new UsernamePasswordAuthenticationToken(
username, clientCert,
AuthorityUtils.createAuthorityList("ROLE_" + role)
);
}
private boolean verifyCertificate(X509Certificate cert) {
// Implement certificate verification
// Check CA signature, revocation, validity period
return true;
}
private String determineRoleFromCertificate(X509Certificate cert) {
// Application-specific logic to determine role
// Could be based on organization, custom extensions, etc.
return "USER";
}
} The key remediation principles are:
- Never rely solely on certificate presence for authorization
- Always verify certificate chain and revocation status
- Map certificates to identities using multiple properties, not just CN
- Implement proper role-based access control
- Log and monitor authentication and authorization decisions
Testing your remediation is critical. Use middleBrick's continuous monitoring to ensure your fixes are effective:
# Continuous monitoring configuration
{
"apis": [
{
"url": "https://api.example.com",
"scan_frequency": "daily",
"fail_threshold": "B",
"alert_channels": ["email", "slack"]
}
]
}