Improper Preservation of Permissions

Description

Improper Preservation of Permissions is a vulnerability that occurs when a product does not preserve permissions or incorrectly preserves permissions when copying, restoring, or sharing objects, which can cause them to have less restrictive permissions than intended. Unlike CWE-278 which focuses on inheriting insecure permissions from source objects, this weakness concerns the failure to maintain intended security settings during operations that should preserve them. This can occur during backup restoration, file cloning, data export/import, or sharing operations where the target objects end up with different (typically weaker) permissions than the source.

Risk

Improper permission preservation creates security vulnerabilities by silently degrading access controls during common operations. Backup restorations that don't preserve ACLs may expose previously protected data. File cloning operations that reset permissions to defaults can make sensitive files world-readable. Export operations that strip permission metadata leave exported data unprotected in destination systems. The risk is amplified because administrators and users typically expect these operations to maintain security settings, and may not verify permissions after completion. Symlink attacks during backup restoration can further corrupt permission assignments. Systems with complex ACL requirements are particularly vulnerable as simple permission copying may lose extended attributes.

Solution

Ensure all copy, backup, restore, and sharing operations preserve complete permission information including owner, group, standard permissions, ACLs, and extended attributes. Use backup and restoration tools that properly handle all permission metadata for the target platform. Verify permissions after restoration or copy operations, especially for security-critical files. Implement permission verification as part of backup restoration validation. When exporting data between systems with different permission models, document the expected permissions and provide mechanisms to apply them at the destination. For operations where permissions cannot be preserved, warn users and apply restrictive default permissions rather than permissive ones. Consider using immutable backups that cryptographically verify permission integrity.

Common Consequences

ImpactDetails
Confidentiality, IntegrityScope: Confidentiality, Integrity

Unauthorized reading of application data occurs when copied or restored files have more permissive read permissions than originals. Unauthorized modification occurs when write permissions are not properly restricted after operations that should preserve them.

Example Code

Vulnerable Code (Python)

The following examples demonstrate improper permission preservation:

# Vulnerable: Backup restoration doesn't preserve permissions
import os
import shutil
import tarfile

class VulnerableBackupRestore:

    def restore_from_tar(self, archive_path, dest_dir):
        # Vulnerable: extractall may not preserve all permissions
        with tarfile.open(archive_path, 'r:gz') as tar:
            tar.extractall(dest_dir)
        # Permissions may differ from original backup
        # ACLs and extended attributes may be lost

    def copy_file_simple(self, source, dest):
        # Vulnerable: shutil.copy doesn't preserve permissions
        shutil.copy(source, dest)
        # Destination file has different permissions than source

    def clone_directory(self, source_dir, dest_dir):
        # Vulnerable: copytree uses default permissions for new files
        shutil.copytree(source_dir, dest_dir)
        # If source had restrictive permissions, they may not be preserved

    def restore_with_symlink_vulnerability(self, backup_file, dest_path):
        # Vulnerable: Symlink can redirect restoration
        # Attacker creates symlink before restoration
        with open(dest_path, 'wb') as f:
            f.write(read_backup(backup_file))
        # If dest_path is symlink, writes to wrong location
        # Permissions applied to wrong file
// Vulnerable: C file copy without permission preservation
#include <stdio.h>
#include <sys/stat.h>

int vulnerable_copy_file(const char *source, const char *dest) {
    FILE *src = fopen(source, "rb");
    FILE *dst = fopen(dest, "wb");

    if (!src || !dst) return -1;

    char buffer[8192];
    size_t bytes;
    while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0) {
        fwrite(buffer, 1, bytes, dst);
    }

    fclose(src);
    fclose(dst);

    // Vulnerable: Destination file has default permissions (from umask)
    // Source file permissions not preserved
    return 0;
}

int vulnerable_restore_backup(const char *backup, const char *dest) {
    // Vulnerable: Doesn't preserve ACLs
    struct stat st;
    stat(backup, &st);

    // Only copies basic permissions, not ACLs or extended attributes
    copy_file_content(backup, dest);
    chmod(dest, st.st_mode);

    // Extended ACLs and capabilities are lost
    return 0;
}
# Vulnerable: Shell operations losing permissions
#!/bin/bash

# Vulnerable: cp without -p loses permissions
cp /secure/sensitive.conf /backup/sensitive.conf
# Backup file has default permissions

# Vulnerable: tar may not preserve extended attributes
tar -czf backup.tar.gz /secure/data/
# Restore won't have original ACLs

# Vulnerable: rsync without proper flags
rsync /source/data/ /dest/data/
# Permissions may not be preserved

Fixed Code (Python)

# Fixed: Proper permission preservation during operations
import os
import shutil
import tarfile
import stat

class SecureBackupRestore:

    def restore_from_tar(self, archive_path, dest_dir):
        # Verify destination doesn't contain symlinks that could redirect
        if os.path.exists(dest_dir):
            for root, dirs, files in os.walk(dest_dir):
                for name in dirs + files:
                    path = os.path.join(root, name)
                    if os.path.islink(path):
                        raise SecurityError(f"Symlink in restore path: {path}")

        with tarfile.open(archive_path, 'r:gz') as tar:
            # Check for path traversal and symlink attacks
            for member in tar.getmembers():
                if member.name.startswith('/') or '..' in member.name:
                    raise SecurityError(f"Unsafe path: {member.name}")
                if member.issym() or member.islnk():
                    # Handle symlinks carefully
                    link_target = os.path.normpath(
                        os.path.join(dest_dir, member.linkname)
                    )
                    if not link_target.startswith(dest_dir):
                        raise SecurityError(f"Symlink escape: {member.name}")

            # Extract with permission preservation
            tar.extractall(dest_dir, numeric_owner=True)

        # Verify permissions were preserved
        self.verify_permissions(archive_path, dest_dir)

    def copy_file_with_permissions(self, source, dest):
        # Use copy2 which preserves permissions and metadata
        shutil.copy2(source, dest)

        # Verify permissions match
        src_stat = os.stat(source)
        dst_stat = os.stat(dest)
        if src_stat.st_mode != dst_stat.st_mode:
            # Force permissions if copy2 failed
            os.chmod(dest, src_stat.st_mode)

        # Copy ACLs if available (Linux)
        try:
            import posix1e
            acl = posix1e.ACL(file=source)
            acl.applyto(dest)
        except (ImportError, OSError):
            pass  # ACL support not available

    def clone_directory_securely(self, source_dir, dest_dir):
        # Create destination with same permissions as source
        src_stat = os.stat(source_dir)

        def copy_function(src, dst):
            shutil.copy2(src, dst)
            # Also copy extended attributes
            try:
                import xattr
                for attr in xattr.listxattr(src):
                    xattr.setxattr(dst, attr, xattr.getxattr(src, attr))
            except (ImportError, OSError):
                pass

        shutil.copytree(
            source_dir, dest_dir,
            copy_function=copy_function,
            dirs_exist_ok=False
        )

        # Verify directory permissions
        os.chmod(dest_dir, src_stat.st_mode)

    def verify_permissions(self, archive_path, dest_dir):
        """Verify restored files have correct permissions"""
        with tarfile.open(archive_path, 'r:gz') as tar:
            for member in tar.getmembers():
                dest_path = os.path.join(dest_dir, member.name)
                if os.path.exists(dest_path) and not os.path.islink(dest_path):
                    actual_mode = os.stat(dest_path).st_mode & 0o7777
                    expected_mode = member.mode & 0o7777
                    if actual_mode != expected_mode:
                        raise PermissionVerificationError(
                            f"Permission mismatch for {member.name}: "
                            f"expected {oct(expected_mode)}, got {oct(actual_mode)}"
                        )
// Fixed: C file copy with permission preservation
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/xattr.h>
#include <sys/acl.h>

int secure_copy_file(const char *source, const char *dest) {
    struct stat st;
    if (stat(source, &st) != 0) {
        return -1;
    }

    // Open source for reading
    int src_fd = open(source, O_RDONLY);
    if (src_fd < 0) return -1;

    // Create destination with source permissions
    int dst_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode);
    if (dst_fd < 0) {
        close(src_fd);
        return -1;
    }

    // Copy content
    char buffer[8192];
    ssize_t bytes;
    while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) {
        if (write(dst_fd, buffer, bytes) != bytes) {
            close(src_fd);
            close(dst_fd);
            return -1;
        }
    }

    close(src_fd);
    close(dst_fd);

    // Explicitly set permissions (in case umask affected creation)
    chmod(dest, st.st_mode);

    // Preserve ownership (requires root)
    chown(dest, st.st_uid, st.st_gid);

    // Copy ACLs
    acl_t acl = acl_get_file(source, ACL_TYPE_ACCESS);
    if (acl != NULL) {
        acl_set_file(dest, ACL_TYPE_ACCESS, acl);
        acl_free(acl);
    }

    // Copy extended attributes
    char xattr_list[1024];
    ssize_t list_len = listxattr(source, xattr_list, sizeof(xattr_list));
    if (list_len > 0) {
        char *name = xattr_list;
        while (name < xattr_list + list_len) {
            char value[1024];
            ssize_t value_len = getxattr(source, name, value, sizeof(value));
            if (value_len > 0) {
                setxattr(dest, name, value, value_len, 0);
            }
            name += strlen(name) + 1;
        }
    }

    return 0;
}
# Fixed: Shell operations preserving permissions
#!/bin/bash

# Copy with permission preservation
cp -p /secure/sensitive.conf /backup/sensitive.conf

# Or use rsync with full preservation
rsync -aAX /source/data/ /dest/data/
# -a: archive mode (preserves permissions, ownership, timestamps)
# -A: preserve ACLs
# -X: preserve extended attributes

# Tar with extended attributes and ACLs
tar --xattrs --acls -czf backup.tar.gz /secure/data/

# Restore with permissions
tar --xattrs --acls -xzf backup.tar.gz -C /restore/

# Verify permissions after restore
find /restore/ -exec stat -c '%a %n' {} \; > restored_perms.txt

The fix ensures all permission metadata (basic permissions, ACLs, extended attributes) is preserved during copy and restore operations.


Exploited in the Wild

Backup Restoration ACL Loss (Enterprise Environments, Ongoing)

Backup restorations using tools that don't properly preserve ACLs have exposed previously protected data. CVE-2002-2323 documented backup restoration using symbolic links resulting in incorrect ACLs.

File Cloning Permission Degradation (Unix Systems, Historical)

CVE-2001-1515 documented file system inheritance causing unintended permission changes during copy operations, weakening security on cloned files.

Backup File Default Permissions (Various Applications, Ongoing)

CVE-2005-1920 documented backup files created with default permissions less restrictive than originals, exposing sensitive data from backup operations.


Tools to Test/Exploit

  • getfacl/setfacl — Tools for viewing and manipulating ACLs to verify preservation.

  • rsync — File synchronization tool with options for ACL and extended attribute preservation.

  • tar with ACL support — Archive tool with options for preserving extended attributes.


CVE Examples

  • CVE-2002-2323 — Backup restoration using symbolic links resulted in incorrect ACLs.

  • CVE-2001-1515 — File system inheritance causing unintended permission changes.

  • CVE-2005-1920 — Backup files created with default permissions less restrictive than originals.

  • CVE-2001-0195 — Files became world-readable during cloning operations.


References

  1. MITRE Corporation. "CWE-281: Improper Preservation of Permissions." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/281.html

  2. Linux man pages. "acl(5) - Access Control Lists." https://man7.org/linux/man-pages/man5/acl.5.html

  3. OWASP Foundation. "Broken Access Control." OWASP Top 10. https://owasp.org/Top10/A01_2021-Broken_Access_Control/