{"title":"HMAC Forgery via Raw SHA-256 as a MAC","language":"Python","severity":"Critical","cwe":"CWE-327","source_lines":[15],"flow_lines":[14,15,16],"sink_lines":[16],"vulnerable_code":"import hashlib\nimport json\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\nIOT_DEVICE_SECRET = b'factory_default_key_2024'\n\n@app.route('/iot/telemetry', methods=['POST'])\ndef process_device_telemetry():\n    payload = request.json\n    device_id = payload.get('device_id')\n    sensor_data = payload.get('data')\n    received_mac = payload.get('signature')\n    message = f\"{device_id}:{json.dumps(sensor_data)}\".encode()\n    computed_mac = hashlib.sha256(IOT_DEVICE_SECRET + message).hexdigest()\n    if received_mac == computed_mac:\n        return jsonify({\"status\": \"accepted\", \"device\": device_id})\n    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":"import hmac\nimport hashlib\nimport json\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\nIOT_DEVICE_SECRET = b'factory_default_key_2024'\n\n@app.route('/iot/telemetry', methods=['POST'])\ndef process_device_telemetry():\n    payload = request.json\n    device_id = payload.get('device_id')\n    sensor_data = payload.get('data')\n    received_mac = payload.get('signature')\n    message = f\"{device_id}:{json.dumps(sensor_data, separators=(',', ':'), sort_keys=True)}\".encode()\n    computed_mac = hmac.new(IOT_DEVICE_SECRET, message, hashlib.sha256).hexdigest()\n    if hmac.compare_digest(received_mac, computed_mac):\n        return jsonify({\"status\": \"accepted\", \"device\": device_id})\n    return jsonify({\"status\": \"rejected\"}), 403"}