Missing Check for Certificate Revocation after Initial Check
Description
Missing Check for Certificate Revocation after Initial Check is a vulnerability that occurs when a product fails to verify certificate revocation status before each privileged action, only checking at initial connection or authentication. A certificate may be revoked between the initial check and subsequent operations due to key compromise, policy changes, or employee termination. This creates a window where revoked credentials retain their privileges, allowing continued access that should have been terminated. The vulnerability is essentially a TOCTOU (time-of-check time-of-use) race condition applied to certificate validation.
Risk
This vulnerability allows attackers with revoked certificates to continue accessing protected resources until the session ends or a periodic recheck occurs. If a private key is compromised and the certificate revoked, attackers can still use the compromised key for the remainder of any active sessions. In long-lived connections or sessions with infrequent reauthentication, the window of exposure can be significant. Terminated employees whose certificates are revoked may retain access. The risk is particularly severe for high-security applications where immediate revocation is critical, such as financial systems, healthcare applications, or access control systems.
Solution
Ensure certificates are checked for revoked status before each use of a protected resource, not just at initial authentication. Implement short session timeouts that require reauthentication. Use OCSP stapling to efficiently check revocation status. Cache revocation status for short periods (minutes, not hours) to balance performance and security. Consider using Certificate Transparency logs for additional verification. Implement real-time revocation checking for high-security operations. Design systems to minimize the time between revocation and enforcement.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Trust may be assigned to an entity who is not who it claims to be, as revoked credentials continue to function. |
| Integrity | Scope: Integrity Data from an untrusted source may be integrated when revoked certificates are still accepted. |
| Confidentiality | Scope: Confidentiality Data may be disclosed to an entity impersonating a trusted entity using revoked credentials. |
Example Code
Vulnerable Code
// Vulnerable: Only checks certificate at connection time
public class VulnerableSSLServer {
public void handleConnection(SSLSocket socket) throws Exception {
// Certificate checked only at handshake
SSLSession session = socket.getSession();
X509Certificate[] certs = (X509Certificate[])
session.getPeerCertificates();
// Initial verification
verifyCertificate(certs[0]);
// Vulnerable: No revocation check for subsequent operations
while (true) {
String command = readCommand(socket);
// Certificate may have been revoked since connection
executePrivilegedCommand(command); // Dangerous!
}
}
private void verifyCertificate(X509Certificate cert) {
// Only checks validity, not revocation for each operation
}
}
# Vulnerable: Session reuses initial cert validation
class VulnerableAuthenticator:
def __init__(self):
self.authenticated_sessions = {}
def authenticate(self, cert, session_id):
# Vulnerable: Only checks revocation at login
if self.verify_certificate(cert) and not self.is_revoked(cert):
self.authenticated_sessions[session_id] = {
'cert': cert,
'authenticated_at': time.time()
}
return True
return False
def perform_action(self, session_id, action):
# Vulnerable: No revocation recheck
if session_id in self.authenticated_sessions:
# Certificate may now be revoked!
execute_action(action)
Fixed Code
// Fixed: Check revocation before each privileged operation
public class SecureSSLServer {
private OCSPChecker ocspChecker;
private int maxOperationsBeforeRecheck = 10;
private int operationCount = 0;
public void handleConnection(SSLSocket socket) throws Exception {
SSLSession session = socket.getSession();
X509Certificate[] certs = (X509Certificate[])
session.getPeerCertificates();
X509Certificate clientCert = certs[0];
// Initial verification
verifyAndCheckRevocation(clientCert);
while (true) {
String command = readCommand(socket);
// Fixed: Recheck revocation periodically
operationCount++;
if (operationCount >= maxOperationsBeforeRecheck) {
verifyAndCheckRevocation(clientCert);
operationCount = 0;
}
executePrivilegedCommand(command);
}
}
private void verifyAndCheckRevocation(X509Certificate cert) throws Exception {
// Verify certificate chain
verifyCertificateChain(cert);
// Fixed: Check OCSP/CRL for revocation
if (ocspChecker.isRevoked(cert)) {
throw new CertificateRevokedException("Certificate has been revoked");
}
}
}
// Fixed: Check revocation before each sensitive operation
public class SecureOperationHandler {
public void performSensitiveOperation(X509Certificate cert, Operation op)
throws Exception {
// Fixed: Check revocation before EVERY sensitive operation
checkRevocationStatus(cert);
// Only proceed if certificate is still valid
executeOperation(op);
}
private void checkRevocationStatus(X509Certificate cert) throws Exception {
// Use OCSP for real-time revocation checking
OCSPResponse response = queryOCSP(cert);
if (response.getStatus() == OCSPResponseStatus.REVOKED) {
throw new CertificateRevokedException("Certificate revoked");
}
}
}
# Fixed: Recheck certificate status regularly
import time
from functools import wraps
class SecureAuthenticator:
RECHECK_INTERVAL = 300 # 5 minutes
def __init__(self):
self.authenticated_sessions = {}
def authenticate(self, cert, session_id):
if self.verify_certificate(cert) and not self.is_revoked(cert):
self.authenticated_sessions[session_id] = {
'cert': cert,
'authenticated_at': time.time(),
'last_revocation_check': time.time()
}
return True
return False
def _check_session_validity(self, session_id):
"""Fixed: Recheck revocation status periodically."""
session = self.authenticated_sessions.get(session_id)
if not session:
return False
cert = session['cert']
last_check = session['last_revocation_check']
# Fixed: Recheck if interval has passed
if time.time() - last_check > self.RECHECK_INTERVAL:
if self.is_revoked(cert):
del self.authenticated_sessions[session_id]
return False
session['last_revocation_check'] = time.time()
return True
def perform_action(self, session_id, action):
# Fixed: Validate session before each action
if not self._check_session_validity(session_id):
raise PermissionError("Session invalid or certificate revoked")
execute_action(action)
# Fixed: Decorator for sensitive operations
def require_valid_cert(self, func):
@wraps(func)
def wrapper(session_id, *args, **kwargs):
session = self.authenticated_sessions.get(session_id)
if not session:
raise PermissionError("Not authenticated")
# Fixed: Always check revocation for sensitive ops
if self.is_revoked(session['cert']):
del self.authenticated_sessions[session_id]
raise PermissionError("Certificate revoked")
return func(session_id, *args, **kwargs)
return wrapper
CVE Examples
No specific CVEs are listed for this CWE, but the vulnerability pattern appears in:
- Long-lived TLS sessions without revocation recheck
- Web applications using client certificates
- VPN systems with certificate authentication
References
- MITRE Corporation. "CWE-370: Missing Check for Certificate Revocation after Initial Check." https://cwe.mitre.org/data/definitions/370.html
- RFC 6960. "Online Certificate Status Protocol - OCSP." https://tools.ietf.org/html/rfc6960