# Timing Attack via Non-Constant-Time String Comparison in API Key Authentication

Language: Python
Severity: High
CWE: CWE-208

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import boto3
from flask import request, jsonify
def validate_iot_device_token(req_ctx):
    dev_token = req_ctx.headers.get('X-Device-Token', '')
    svc = boto3.client('secretsmanager', region_name='us-east-1')
    secret_blob = svc.get_secret_value(SecretId='iot/device-master-token')
    master_tok = secret_blob['SecretString']
    if dev_token == master_tok:
        return jsonify({'status': 'authorized', 'scope': 'full'}), 200
    return jsonify({'status': 'forbidden'}), 403
```

## Explanation

The attacker-controlled token is read from the HTTP header at line 4 (dev_token) and compared against the master secret at line 8 using Python's built-in == operator. The == operator short-circuits on the first mismatched byte, meaning comparison time varies depending on how many leading characters match, allowing a timing oracle that lets an attacker brute-force the secret one character at a time by measuring response latency.

## Remediation

Replaced the `==` operator on line 8 with `hmac.compare_digest()`, which performs a constant-time comparison of the two strings. This prevents timing side-channel attacks because the function always compares all bytes regardless of where a mismatch occurs, eliminating the information leakage that allowed character-by-character brute-forcing. Both values are explicitly encoded to bytes via UTF-8 to satisfy `compare_digest`'s preference for bytes-like objects.

## Secure Code
```python
import boto3
import hmac
from flask import request, jsonify
def validate_iot_device_token(req_ctx):
    dev_token = req_ctx.headers.get('X-Device-Token', '')
    svc = boto3.client('secretsmanager', region_name='us-east-1')
    secret_blob = svc.get_secret_value(SecretId='iot/device-master-token')
    master_tok = secret_blob['SecretString']
    if hmac.compare_digest(dev_token.encode('utf-8'), master_tok.encode('utf-8')):
        return jsonify({'status': 'authorized', 'scope': 'full'}), 200
    return jsonify({'status': 'forbidden'}), 403
```
