Covert Timing Channel
Description
Covert Timing Channel is a vulnerability that occurs when sensitive information can be inferred by observing the timing of system operations. These channels convey information by modulating some aspect of system behavior over time, so that an attacker monitoring the timing can deduce protected information. Common examples include cryptographic operations that take different amounts of time based on the input or key material, password validation that returns early on mismatch, and database queries whose execution time varies based on the data accessed.
Risk
Timing channels can leak cryptographic keys, passwords, and other sensitive data. Attackers can determine valid usernames by comparing response times for valid versus invalid accounts. Cryptographic implementations vulnerable to timing attacks may have their keys extracted through statistical analysis of operation timing. Database queries can reveal information about data existence and content through query execution time analysis. These attacks are particularly dangerous because they leave no trace in application logs and can be conducted remotely. Modern CPU features like branch prediction and caching make timing attacks more practical.
Solution
Design implementations that eliminate time variances in security-sensitive operations. Use constant-time comparison functions for passwords, MACs, and other secrets. Add artificial or random delays so that the amount of CPU time consumed is independent of the action being taken. Use cryptographic libraries that implement constant-time operations. Avoid early-return patterns in authentication code. Consider adding random jitter to mask timing information. For particularly sensitive operations, ensure the execution time is uniform regardless of the path taken through the code.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Information exposure through the ability to read application data by analyzing operation timing. |
Example Code
Vulnerable Code
// Vulnerable: Early return on password mismatch
int vulnerable_check_password(const char *provided, const char *stored) {
size_t provided_len = strlen(provided);
size_t stored_len = strlen(stored);
// Vulnerable: Length comparison reveals password length
if (provided_len != stored_len) {
return 0; // Early return
}
// Vulnerable: Character-by-character comparison
for (size_t i = 0; i < stored_len; i++) {
if (provided[i] != stored[i]) {
return 0; // Early return reveals position of mismatch
}
}
return 1;
}
# Vulnerable: Timing-based username enumeration
def vulnerable_login(username, password):
user = database.find_user(username)
if user is None:
# Vulnerable: Fast return for invalid username
return False
# Slow hash computation only for valid usernames
hashed = hash_password(password, user.salt)
if hashed == user.password_hash:
return True
return False
# Vulnerable: String comparison
def vulnerable_verify_token(provided, expected):
# Vulnerable: Early termination reveals match position
return provided == expected
// Vulnerable: MAC verification with timing leak
public class VulnerableMacVerifier {
public boolean verifyMac(byte[] message, byte[] providedMac, SecretKey key) {
byte[] computedMac = computeMac(message, key);
// Vulnerable: Arrays.equals returns early on mismatch
return Arrays.equals(computedMac, providedMac);
}
public boolean verifySignature(byte[] data, byte[] signature) {
byte[] expected = computeSignature(data);
// Vulnerable: Byte-by-byte comparison leaks info
if (signature.length != expected.length) {
return false; // Length leak
}
for (int i = 0; i < expected.length; i++) {
if (signature[i] != expected[i]) {
return false; // Position leak
}
}
return true;
}
}
Fixed Code
// Fixed: Constant-time password comparison
#include <stdint.h>
#include <string.h>
int secure_check_password(const char *provided, const char *stored,
size_t max_len) {
volatile uint8_t result = 0;
size_t i;
// Fixed: Always process max_len bytes
for (i = 0; i < max_len; i++) {
// XOR accumulates differences without early exit
result |= provided[i] ^ stored[i];
}
// Fixed: Additional check for null terminators
// This is constant-time because both strings are processed
return result == 0;
}
// Fixed: Using OpenSSL's constant-time comparison
#include <openssl/crypto.h>
int secure_compare(const void *a, const void *b, size_t len) {
// Fixed: CRYPTO_memcmp is constant-time
return CRYPTO_memcmp(a, b, len) == 0;
}
# Fixed: Constant-time comparison using hmac.compare_digest
import hmac
import secrets
import time
def secure_login(username, password):
user = database.find_user(username)
# Fixed: Always compute hash, even for invalid username
if user is None:
# Use dummy values for timing consistency
dummy_salt = secrets.token_bytes(16)
dummy_hash = hash_password(password, dummy_salt)
# Compare against random value to maintain timing
hmac.compare_digest(dummy_hash, secrets.token_bytes(len(dummy_hash)))
return False
hashed = hash_password(password, user.salt)
# Fixed: Constant-time comparison
if hmac.compare_digest(hashed, user.password_hash):
return True
return False
# Fixed: Constant-time token verification
def secure_verify_token(provided, expected):
# Fixed: hmac.compare_digest is constant-time
if len(provided) != len(expected):
# Fixed: Still do comparison to avoid timing leak
# Compare against itself to burn same time
hmac.compare_digest(provided, provided)
return False
return hmac.compare_digest(provided.encode(), expected.encode())
# Fixed: Add artificial delay for additional protection
def secure_login_with_delay(username, password):
start_time = time.monotonic()
result = secure_login(username, password)
# Fixed: Ensure minimum operation time
elapsed = time.monotonic() - start_time
min_time = 0.1 # 100ms minimum
if elapsed < min_time:
time.sleep(min_time - elapsed)
return result
// Fixed: Constant-time MAC verification
import java.security.MessageDigest;
public class SecureMacVerifier {
public boolean verifyMac(byte[] message, byte[] providedMac, SecretKey key) {
byte[] computedMac = computeMac(message, key);
// Fixed: MessageDigest.isEqual is constant-time
return MessageDigest.isEqual(computedMac, providedMac);
}
// Fixed: Manual constant-time comparison
public static boolean constantTimeEquals(byte[] a, byte[] b) {
if (a == null || b == null) {
return a == b;
}
if (a.length != b.length) {
// Fixed: Still compare to avoid length timing leak
// Compare a against itself
int dummy = 0;
for (int i = 0; i < a.length; i++) {
dummy |= a[i] ^ a[i];
}
return false;
}
// Fixed: Accumulate all differences
int result = 0;
for (int i = 0; i < a.length; i++) {
result |= a[i] ^ b[i];
}
return result == 0;
}
// Fixed: Constant-time string comparison
public static boolean constantTimeStringEquals(String a, String b) {
if (a == null || b == null) {
return a == b;
}
byte[] aBytes = a.getBytes(StandardCharsets.UTF_8);
byte[] bBytes = b.getBytes(StandardCharsets.UTF_8);
return MessageDigest.isEqual(aBytes, bBytes);
}
}
CVE Examples
No specific CVEs are listed for this CWE. The vulnerability pattern appears in:
- Cryptographic implementations (timing attacks on AES, RSA)
- Password verification systems
- HMAC and digital signature verification
References
- MITRE Corporation. "CWE-385: Covert Timing Channel." https://cwe.mitre.org/data/definitions/385.html
- Kocher, Paul. "Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems."