# HMAC Forgery via Raw SHA-256 as a MAC

Language: Python
Severity: Critical
CWE: CWE-327

## Source
15

## Flow
14-15-16

## Sink
16

## Vulnerable Code
```python
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)
IOT_DEVICE_SECRET = b'factory_default_key_2024'

@app.route('/iot/telemetry', methods=['POST'])
def process_device_telemetry():
    payload = request.json
    device_id = payload.get('device_id')
    sensor_data = payload.get('data')
    received_mac = payload.get('signature')
    message = f"{device_id}:{json.dumps(sensor_data)}".encode()
    computed_mac = hashlib.sha256(IOT_DEVICE_SECRET + message).hexdigest()
    if received_mac == computed_mac:
        return jsonify({"status": "accepted", "device": device_id})
    return jsonify({"status": "rejected"}), 403
```

## Explanation

The code uses SHA-256 in a length-extension vulnerable construction (secret prefix pattern: hash(secret + message)) instead of HMAC. An attacker can forge valid signatures for modified messages without knowing the secret by exploiting SHA-256's Merkle-Damgård construction to append data and compute valid MACs. The vulnerable line constructs the MAC using hashlib.sha256(IOT_DEVICE_SECRET + message) and compares it with a plain equality operator, both of which are cryptographically unsafe practices.

## Remediation

The fix replaces the vulnerable SHA-256 secret-prefix construction (hash(secret + message)) with Python's standard HMAC module (hmac.new), which is not susceptible to length-extension attacks. Additionally, the string comparison is changed from '==' to hmac.compare_digest() to prevent timing side-channel attacks, and JSON serialization uses consistent separators and sorted keys for deterministic output.

## Secure Code
```python
import hmac
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)
IOT_DEVICE_SECRET = b'factory_default_key_2024'

@app.route('/iot/telemetry', methods=['POST'])
def process_device_telemetry():
    payload = request.json
    device_id = payload.get('device_id')
    sensor_data = payload.get('data')
    received_mac = payload.get('signature')
    message = f"{device_id}:{json.dumps(sensor_data, separators=(',', ':'), sort_keys=True)}".encode()
    computed_mac = hmac.new(IOT_DEVICE_SECRET, message, hashlib.sha256).hexdigest()
    if hmac.compare_digest(received_mac, computed_mac):
        return jsonify({"status": "accepted", "device": device_id})
    return jsonify({"status": "rejected"}), 403
```
