{"title":"SQL Injection","language":"Python","severity":"Critical","cwe":"CWE-89","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request\nimport sqlite3\napp = Flask(__name__)\ndef fetch_device_telemetry(device_id, metric_type):\n    conn = sqlite3.connect('iot_telemetry.db')\n    cur = conn.cursor()\n    qry = \"SELECT ts, val FROM sensor_readings WHERE dev_id='\" + device_id + \"' AND metric='\" + metric_type + \"' ORDER BY ts DESC LIMIT 50\"\n    cur.execute(qry)\n    rows = cur.fetchall()\n    conn.close()\n    return rows\n@app.route('/api/v2/telemetry')\ndef telemetry_endpoint():\n    dev = request.args.get('device_id', '')\n    mtype = request.args.get('metric', 'temperature')\n    return str(fetch_device_telemetry(dev, mtype))","explanation":"User-supplied HTTP query parameters 'device_id' and 'metric' (lines 13-14) are retrieved without sanitization and passed directly into fetch_device_telemetry(), where they are concatenated into a raw SQL string (line 7) and executed via cur.execute() (line 8). This classic string concatenation pattern allows an attacker to break out of the intended SQL context and inject arbitrary SQL commands.","remediation":"The fix replaces the dangerous string concatenation in the SQL query with parameterized placeholders (`?`) and passes the user-supplied `device_id` and `metric_type` values as a tuple to `cur.execute()`. This ensures the database driver properly escapes and binds the parameters, completely preventing SQL injection regardless of what input the attacker supplies. An additional basic input validation check was added for the required `device_id` parameter.","secure_code":"from flask import Flask, request, jsonify\nimport sqlite3\n\napp = Flask(__name__)\n\ndef fetch_device_telemetry(device_id, metric_type):\n    conn = sqlite3.connect('iot_telemetry.db')\n    cur = conn.cursor()\n    qry = \"SELECT ts, val FROM sensor_readings WHERE dev_id=? AND metric=? ORDER BY ts DESC LIMIT 50\"\n    cur.execute(qry, (device_id, metric_type))\n    rows = cur.fetchall()\n    conn.close()\n    return rows\n\n@app.route('/api/v2/telemetry')\ndef telemetry_endpoint():\n    dev = request.args.get('device_id', '')\n    mtype = request.args.get('metric', 'temperature')\n    if not dev:\n        return jsonify({\"error\": \"device_id parameter is required\"}), 400\n    return str(fetch_device_telemetry(dev, mtype))"}