{"title":"Arbitrary Code Execution via logging.config.listen()","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[8],"flow_lines":[8,9],"sink_lines":[9],"vulnerable_code":"import logging.config\nimport threading\n\ndef bootstrap_cloud_telemetry(port=9030, verify_cert=False):\n    telemetry_host = '0.0.0.0'\n    print(f'[CloudMonitor] Initializing telemetry receiver on {telemetry_host}:{port}')\n    listener = threading.Thread(\n        target=logging.config.listen,\n        args=(port,),\n        daemon=True\n    )\n    listener.start()\n    print('[CloudMonitor] Telemetry service active - accepting remote log configurations')\n    return listener","explanation":"The logging.config.listen() function accepts pickled configuration objects from any network client without authentication or validation. An attacker can send a malicious serialized pickle payload that executes arbitrary code when deserialized by the logging configuration listener.","remediation":"The fix replaces the dangerous logging.config.listen() call (which deserializes arbitrary pickle data) with a custom secure listener that only accepts JSON-formatted logging configurations, validates them against a whitelist of allowed keys and handler classes, and applies them safely via logging.config.dictConfig(). Additionally, the listener binds to localhost (127.0.0.1) instead of all interfaces (0.0.0.0) to limit network exposure.","secure_code":"import logging.config\nimport json\nimport threading\nimport socket\nimport struct\n\ndef _verify_config(config_bytes):\n    \"\"\"Verify and validate logging configuration before applying.\n    Only accepts JSON-formatted logging configurations with allowed handlers.\"\"\"\n    try:\n        config = json.loads(config_bytes)\n    except (json.JSONDecodeError, UnicodeDecodeError):\n        print('[CloudMonitor] Rejected invalid configuration: not valid JSON')\n        return None\n\n    allowed_top_keys = {'version', 'disable_existing_loggers', 'formatters', 'filters', 'handlers', 'loggers', 'root', 'incremental'}\n    if not set(config.keys()).issubset(allowed_top_keys):\n        print('[CloudMonitor] Rejected configuration: contains disallowed keys')\n        return None\n\n    allowed_handler_classes = {\n        'logging.StreamHandler',\n        'logging.FileHandler',\n        'logging.handlers.RotatingFileHandler',\n        'logging.handlers.TimedRotatingFileHandler',\n        'logging.handlers.SysLogHandler',\n        'logging.NullHandler',\n    }\n\n    handlers = config.get('handlers', {})\n    for handler_name, handler_config in handlers.items():\n        handler_class = handler_config.get('class', '')\n        if handler_class not in allowed_handler_classes:\n            print(f'[CloudMonitor] Rejected configuration: disallowed handler class \"{handler_class}\"')\n            return None\n\n    return config\n\n\ndef _secure_config_listener(port, bind_address='127.0.0.1'):\n    \"\"\"A secure logging configuration listener that only accepts JSON configs\n    and validates them before applying.\"\"\"\n    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    server.bind((bind_address, port))\n    server.listen(1)\n    print(f'[CloudMonitor] Secure config listener bound to {bind_address}:{port}')\n\n    while True:\n        try:\n            conn, addr = server.accept()\n            print(f'[CloudMonitor] Connection from {addr}')\n\n            length_bytes = conn.recv(4)\n            if len(length_bytes) < 4:\n                conn.close()\n                continue\n\n            length = struct.unpack('>I', length_bytes)[0]\n\n            if length > 65536:\n                print('[CloudMonitor] Rejected configuration: exceeds maximum size')\n                conn.close()\n                continue\n\n            config_bytes = b''\n            while len(config_bytes) < length:\n                chunk = conn.recv(min(4096, length - len(config_bytes)))\n                if not chunk:\n                    break\n                config_bytes += chunk\n\n            conn.close()\n\n            if len(config_bytes) != length:\n                print('[CloudMonitor] Rejected configuration: incomplete data')\n                continue\n\n            config = _verify_config(config_bytes)\n            if config is not None:\n                logging.config.dictConfig(config)\n                print('[CloudMonitor] Applied validated logging configuration')\n        except Exception as e:\n            print(f'[CloudMonitor] Error processing configuration: {e}')\n\n\ndef bootstrap_cloud_telemetry(port=9030, bind_address='127.0.0.1'):\n    print(f'[CloudMonitor] Initializing telemetry receiver on {bind_address}:{port}')\n    listener = threading.Thread(\n        target=_secure_config_listener,\n        args=(port, bind_address),\n        daemon=True\n    )\n    listener.start()\n    print('[CloudMonitor] Telemetry service active - accepting validated JSON log configurations only')\n    return listener"}