{"title":"Code Injection via pandas.eval()","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[9],"flow_lines":[9,10],"sink_lines":[10],"vulnerable_code":"import pandas as pd\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/iot/sensor-analytics', methods=['POST'])\ndef compute_sensor_metrics():\n    device_data = pd.DataFrame(request.json.get('sensor_readings'))\n    metric_formula = request.json.get('calculation_rule')\n    aggregated_result = device_data.eval(metric_formula)\n    return jsonify({'computed_metrics': aggregated_result.tolist(), 'formula_applied': metric_formula})","explanation":"The application accepts a user-controlled 'calculation_rule' formula from the JSON request and directly passes it to pandas DataFrame.eval() without any validation or sanitization. The eval() method executes arbitrary Python expressions, allowing attackers to inject malicious code that will be executed on the server.","remediation":"The fix adds a comprehensive formula validation function that whitelists only safe arithmetic operations, known DataFrame column names, numeric literals, and a set of allowed mathematical functions. It blocks dangerous patterns like '__import__', 'os', 'exec', and other code injection vectors, and limits formula length to prevent abuse. Input validation is also added for the request payload structure.","secure_code":"import pandas as pd\nimport re\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n# Whitelist of allowed operations and patterns for sensor metric calculations\nALLOWED_COLUMN_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')\nALLOWED_OPERATORS = {'+', '-', '*', '/', '(', ')', '.', ' '}\nALLOWED_FUNCTIONS = {'abs', 'sqrt', 'log', 'exp', 'mean', 'std', 'min', 'max', 'sum'}\n\ndef validate_formula(formula, valid_columns):\n    \"\"\"Validate that the formula only contains safe operations on known columns.\"\"\"\n    if not isinstance(formula, str) or len(formula) > 200:\n        return False\n    \n    # Block dangerous patterns\n    dangerous_patterns = [\n        '__', 'import', 'eval', 'exec', 'compile', 'globals', 'locals',\n        'getattr', 'setattr', 'delattr', 'open', 'file', 'input',\n        'breakpoint', 'system', 'popen', 'subprocess', 'os.', 'sys.',\n        'builtins', 'class', 'mro', 'subclasses'\n    ]\n    formula_lower = formula.lower()\n    for pattern in dangerous_patterns:\n        if pattern in formula_lower:\n            return False\n    \n    # Tokenize and validate each token\n    tokens = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*|[^a-zA-Z0-9_\\s]+|\\d+\\.?\\d*', formula)\n    for token in tokens:\n        if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', token):\n            # Token is an identifier - must be a known column or allowed function\n            if token not in valid_columns and token not in ALLOWED_FUNCTIONS:\n                return False\n        elif re.match(r'^\\d+\\.?\\d*$', token):\n            # Token is a number - allowed\n            continue\n        else:\n            # Token is an operator - check each character\n            for char in token:\n                if char not in ALLOWED_OPERATORS:\n                    return False\n    \n    return True\n\n@app.route('/api/iot/sensor-analytics', methods=['POST'])\ndef compute_sensor_metrics():\n    sensor_readings = request.json.get('sensor_readings')\n    if not sensor_readings or not isinstance(sensor_readings, list):\n        return jsonify({'error': 'Invalid sensor readings provided'}), 400\n    \n    device_data = pd.DataFrame(sensor_readings)\n    metric_formula = request.json.get('calculation_rule')\n    \n    if not metric_formula or not isinstance(metric_formula, str):\n        return jsonify({'error': 'Invalid calculation rule'}), 400\n    \n    valid_columns = set(device_data.columns.tolist())\n    \n    if not validate_formula(metric_formula, valid_columns):\n        return jsonify({'error': 'Invalid or disallowed formula. Only arithmetic operations on known sensor columns are permitted.'}), 400\n    \n    try:\n        aggregated_result = device_data.eval(metric_formula)\n        return jsonify({'computed_metrics': aggregated_result.tolist(), 'formula_applied': metric_formula})\n    except Exception as e:\n        return jsonify({'error': f'Formula evaluation failed: {str(e)}'}), 400"}