{"title":"Code Injection via eval() on User-Supplied Expression","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[4],"flow_lines":[4,6,4,7],"sink_lines":[6,7],"vulnerable_code":"@app.route('/iot/device/threshold', methods=['POST'])\ndef update_sensor_threshold():\n    device_id = request.json.get('device_id')\n    threshold_expr = request.json.get('threshold_formula')\n    sensor_data = {'temp': 25.3, 'humidity': 60, 'pressure': 1013}\n    try:\n        computed_threshold = eval(threshold_expr, {'__builtins__': {}}, sensor_data)\n        db.execute(f\"UPDATE iot_devices SET alert_threshold={computed_threshold} WHERE id={device_id}\")\n        return jsonify({'status': 'success', 'new_threshold': computed_threshold})\n    except Exception as e:\n        return jsonify({'error': str(e)}), 400","explanation":"Line 4 accepts unsanitized user input 'threshold_formula' which flows to line 6 where eval() executes arbitrary Python code despite namespace restrictions. Additionally, line 7 uses string formatting to insert user-controlled 'device_id' and computed values directly into SQL, enabling SQL injection attacks.","remediation":"The fix replaces eval() with a safe AST-based expression parser that only allows arithmetic operations (+, -, *, /, **) on numeric constants and whitelisted sensor variable names, preventing arbitrary code execution. The SQL injection vulnerability is fixed by using parameterized queries with placeholders instead of string formatting, and device_id is validated to be an integer.","secure_code":"import ast\nimport operator\n\nALLOWED_OPERATORS = {\n    ast.Add: operator.add,\n    ast.Sub: operator.sub,\n    ast.Mult: operator.mul,\n    ast.Div: operator.truediv,\n    ast.Pow: operator.pow,\n    ast.USub: operator.neg,\n    ast.UAdd: operator.pos,\n}\n\nALLOWED_VARIABLES = {'temp', 'humidity', 'pressure'}\n\ndef safe_eval_expr(expr, variables):\n    \"\"\"Safely evaluate a mathematical expression with allowed variables.\"\"\"\n    tree = ast.parse(expr, mode='eval')\n    return _eval_node(tree.body, variables)\n\ndef _eval_node(node, variables):\n    if isinstance(node, ast.Expression):\n        return _eval_node(node.body, variables)\n    elif isinstance(node, ast.Constant):\n        if isinstance(node.value, (int, float)):\n            return node.value\n        raise ValueError(f\"Unsupported constant type: {type(node.value)}\")\n    elif isinstance(node, ast.Name):\n        if node.id in ALLOWED_VARIABLES and node.id in variables:\n            return variables[node.id]\n        raise ValueError(f\"Unsupported variable: {node.id}\")\n    elif isinstance(node, ast.BinOp):\n        op_type = type(node.op)\n        if op_type not in ALLOWED_OPERATORS:\n            raise ValueError(f\"Unsupported operator: {op_type.__name__}\")\n        left = _eval_node(node.left, variables)\n        right = _eval_node(node.right, variables)\n        return ALLOWED_OPERATORS[op_type](left, right)\n    elif isinstance(node, ast.UnaryOp):\n        op_type = type(node.op)\n        if op_type not in ALLOWED_OPERATORS:\n            raise ValueError(f\"Unsupported unary operator: {op_type.__name__}\")\n        operand = _eval_node(node.operand, variables)\n        return ALLOWED_OPERATORS[op_type](operand)\n    else:\n        raise ValueError(f\"Unsupported expression type: {type(node).__name__}\")\n\n@app.route('/iot/device/threshold', methods=['POST'])\ndef update_sensor_threshold():\n    device_id = request.json.get('device_id')\n    threshold_expr = request.json.get('threshold_formula')\n    sensor_data = {'temp': 25.3, 'humidity': 60, 'pressure': 1013}\n    try:\n        if not isinstance(threshold_expr, str) or len(threshold_expr) > 200:\n            return jsonify({'error': 'Invalid threshold formula'}), 400\n        if not isinstance(device_id, int):\n            return jsonify({'error': 'Invalid device_id, must be an integer'}), 400\n        computed_threshold = safe_eval_expr(threshold_expr, sensor_data)\n        if not isinstance(computed_threshold, (int, float)):\n            return jsonify({'error': 'Formula must evaluate to a number'}), 400\n        db.execute(\n            \"UPDATE iot_devices SET alert_threshold = ? WHERE id = ?\",\n            (float(computed_threshold), int(device_id))\n        )\n        return jsonify({'status': 'success', 'new_threshold': computed_threshold})\n    except Exception as e:\n        return jsonify({'error': str(e)}), 400"}