{"title":"Code Injection via eval() on Untrusted Expression","language":"Python","severity":"Critical","cwe":"CWE-95","source_lines":[3],"flow_lines":[3,9],"sink_lines":[9],"vulnerable_code":"@app.route('/api/iot/telemetry/filter', methods=['POST'])\ndef filter_sensor_data():\n    device_id = request.json.get('device_id')\n    filter_expr = request.json.get('filter_expression')\n    telemetry_data = fetch_device_telemetry(device_id)\n    filtered_results = []\n    for reading in telemetry_data:\n        temp = reading['temperature']\n        humidity = reading['humidity']\n        pressure = reading['pressure']\n        if eval(filter_expr):\n            filtered_results.append(reading)\n    return jsonify({'device': device_id, 'filtered_data': filtered_results, 'count': len(filtered_results)})","explanation":"The application accepts an unsanitized filter expression from user input (filter_expression) and directly passes it to the eval() function. This allows attackers to execute arbitrary Python code on the server by injecting malicious expressions that access system functions, modules, or data beyond the intended sensor filtering logic.","remediation":"The fix implements a two-layer defense: first, it validates the filter expression using AST parsing to ensure it only contains allowed variable names (temp, humidity, pressure) and disallows function calls, attribute access, and imports. Second, it executes eval() with a restricted namespace that removes all builtins and only exposes the three sensor variables, preventing access to dangerous functions even if validation is bypassed.","secure_code":"@app.route('/api/iot/telemetry/filter', methods=['POST'])\ndef filter_sensor_data():\n    device_id = request.json.get('device_id')\n    filter_expr = request.json.get('filter_expression')\n    telemetry_data = fetch_device_telemetry(device_id)\n    filtered_results = []\n\n    # Validate the filter expression using AST parsing\n    import ast\n\n    ALLOWED_NAMES = {'temp', 'humidity', 'pressure', 'True', 'False', 'None'}\n    ALLOWED_COMPARISONS = (ast.Compare, ast.BoolOp, ast.UnaryOp, ast.BinOp, ast.Num, ast.Name, ast.Constant)\n\n    def validate_expression(expr_str):\n        \"\"\"Validate that the expression only contains safe comparison operations.\"\"\"\n        try:\n            tree = ast.parse(expr_str, mode='eval')\n        except SyntaxError:\n            return False\n        for node in ast.walk(tree):\n            if isinstance(node, ast.Name):\n                if node.id not in ALLOWED_NAMES:\n                    return False\n            elif isinstance(node, ast.Call):\n                return False\n            elif isinstance(node, ast.Attribute):\n                return False\n            elif isinstance(node, (ast.Import, ast.ImportFrom)):\n                return False\n        return True\n\n    if not filter_expr or not isinstance(filter_expr, str):\n        return jsonify({'error': 'Invalid filter expression'}), 400\n\n    if not validate_expression(filter_expr):\n        return jsonify({'error': 'Filter expression contains disallowed operations. Only comparisons using temp, humidity, and pressure are permitted.'}), 400\n\n    for reading in telemetry_data:\n        temp = reading['temperature']\n        humidity = reading['humidity']\n        pressure = reading['pressure']\n        # Use a restricted namespace with no builtins to prevent access to dangerous functions\n        safe_globals = {\"__builtins__\": {}}\n        safe_locals = {\"temp\": temp, \"humidity\": humidity, \"pressure\": pressure}\n        try:\n            result = eval(filter_expr, safe_globals, safe_locals)\n            if result:\n                filtered_results.append(reading)\n        except Exception:\n            return jsonify({'error': 'Error evaluating filter expression'}), 400\n\n    return jsonify({'device': device_id, 'filtered_data': filtered_results, 'count': len(filtered_results)})"}