{"title":"Code Injection via eval() on User-Supplied Expressions","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[9],"flow_lines":[9,10],"sink_lines":[10],"vulnerable_code":"@app.route('/iot/device/telemetry', methods=['POST'])\ndef process_sensor_data():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('transform')\n    raw_temp = request.json.get('temperature', 0)\n    raw_humidity = request.json.get('humidity', 0)\n    raw_pressure = request.json.get('pressure', 0)\n    context = {'temp': raw_temp, 'hum': raw_humidity, 'press': raw_pressure}\n    if metric_formula:\n        computed_value = eval(metric_formula, {\"__builtins__\": {}}, context)\n        store_telemetry(device_id, computed_value)\n        return jsonify({'status': 'processed', 'result': computed_value})\n    return jsonify({'error': 'missing transform'}), 400","explanation":"The application accepts a user-controlled 'transform' parameter containing arbitrary Python expressions and directly passes it to eval() on line 10. Despite attempting to restrict builtins, attackers can still access dangerous functions through object introspection and execute arbitrary code on the server.","remediation":"The fix replaces the dangerous eval() call with a custom AST-based expression parser that only allows arithmetic operations (addition, subtraction, multiplication, division, modulo, power) on numeric constants and pre-defined sensor variables. This completely eliminates the code injection vector since no arbitrary Python code, function calls, attribute access, or imports can be executed through the safe evaluator.","secure_code":"import ast\nimport operator\n\n# Safe mathematical operations whitelist\nSAFE_OPERATORS = {\n    ast.Add: operator.add,\n    ast.Sub: operator.sub,\n    ast.Mult: operator.mul,\n    ast.Div: operator.truediv,\n    ast.FloorDiv: operator.floordiv,\n    ast.Mod: operator.mod,\n    ast.Pow: operator.pow,\n    ast.USub: operator.neg,\n    ast.UAdd: operator.pos,\n}\n\ndef safe_eval_expression(expr, variables):\n    \"\"\"Safely evaluate a mathematical expression with only arithmetic operations.\"\"\"\n    try:\n        tree = ast.parse(expr, mode='eval')\n    except SyntaxError:\n        raise ValueError(\"Invalid expression syntax\")\n\n    def _eval_node(node):\n        if isinstance(node, ast.Expression):\n            return _eval_node(node.body)\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).__name__}\")\n        elif isinstance(node, ast.Name):\n            if node.id in variables:\n                value = variables[node.id]\n                if isinstance(value, (int, float)):\n                    return value\n                raise ValueError(f\"Variable '{node.id}' must be numeric\")\n            raise ValueError(f\"Unknown variable: {node.id}\")\n        elif isinstance(node, ast.BinOp):\n            op_type = type(node.op)\n            if op_type not in SAFE_OPERATORS:\n                raise ValueError(f\"Unsupported operator: {op_type.__name__}\")\n            left = _eval_node(node.left)\n            right = _eval_node(node.right)\n            if op_type == ast.Pow and right > 100:\n                raise ValueError(\"Exponent too large\")\n            return SAFE_OPERATORS[op_type](left, right)\n        elif isinstance(node, ast.UnaryOp):\n            op_type = type(node.op)\n            if op_type not in SAFE_OPERATORS:\n                raise ValueError(f\"Unsupported unary operator: {op_type.__name__}\")\n            operand = _eval_node(node.operand)\n            return SAFE_OPERATORS[op_type](operand)\n        else:\n            raise ValueError(f\"Unsupported expression element: {type(node).__name__}\")\n\n    return _eval_node(tree)\n\n\n@app.route('/iot/device/telemetry', methods=['POST'])\ndef process_sensor_data():\n    device_id = request.json.get('device_id')\n    metric_formula = request.json.get('transform')\n    raw_temp = request.json.get('temperature', 0)\n    raw_humidity = request.json.get('humidity', 0)\n    raw_pressure = request.json.get('pressure', 0)\n\n    # Validate that sensor inputs are numeric\n    if not all(isinstance(v, (int, float)) for v in [raw_temp, raw_humidity, raw_pressure]):\n        return jsonify({'error': 'sensor values must be numeric'}), 400\n\n    context = {'temp': raw_temp, 'hum': raw_humidity, 'press': raw_pressure}\n\n    if metric_formula:\n        if not isinstance(metric_formula, str) or len(metric_formula) > 200:\n            return jsonify({'error': 'invalid transform formula'}), 400\n        try:\n            computed_value = safe_eval_expression(metric_formula, context)\n            store_telemetry(device_id, computed_value)\n            return jsonify({'status': 'processed', 'result': computed_value})\n        except (ValueError, ZeroDivisionError, OverflowError) as e:\n            return jsonify({'error': f'transform error: {str(e)}'}), 400\n    return jsonify({'error': 'missing transform'}), 400"}