{"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_sensor_telemetry(device_id, metric_type):\n    conn = sqlite3.connect('/var/iot/telemetry.db')\n    cur = conn.cursor()\n    usr_filter = request.args.get('threshold', '0')\n    time_range = request.args.get('range', '24h')\n    qry = f\"SELECT ts, val FROM sensor_data WHERE device_id='{device_id}' AND metric='{metric_type}' AND val > {usr_filter} AND recorded_within='{time_range}'\"\n    cur.execute(qry)\n    rows = cur.fetchall()\n    conn.close()\n    return rows","explanation":"Lines 7-8 retrieve user-controlled inputs 'threshold' and 'range' directly from HTTP query parameters without any sanitization or parameterization. These values are interpolated into a raw SQL f-string on line 9, and the constructed query is executed on line 10 via cur.execute(qry), allowing an attacker to inject arbitrary SQL into any of the four interpolated fields (device_id, metric_type, usr_filter, time_range).","remediation":"The fix replaces the dangerous f-string SQL construction with a parameterized query using placeholder markers (`?`). All four user-controlled values—`device_id`, `metric_type`, `usr_filter`, and `time_range`—are now passed as bound parameters to `cur.execute()`, which ensures the database driver properly escapes them and prevents any injected SQL from being interpreted as part of the query structure.","secure_code":"from flask import Flask, request\nimport sqlite3\napp = Flask(__name__)\ndef fetch_sensor_telemetry(device_id, metric_type):\n    conn = sqlite3.connect('/var/iot/telemetry.db')\n    cur = conn.cursor()\n    usr_filter = request.args.get('threshold', '0')\n    time_range = request.args.get('range', '24h')\n    qry = \"SELECT ts, val FROM sensor_data WHERE device_id=? AND metric=? AND val > ? AND recorded_within=?\"\n    cur.execute(qry, (device_id, metric_type, usr_filter, time_range))\n    rows = cur.fetchall()\n    conn.close()\n    return rows"}