Insecure Preserved Inherited Permissions

Description

Insecure Preserved Inherited Permissions is a vulnerability that occurs when a product inherits a set of insecure permissions for an object, for example when copying from an archive file, without user awareness or involvement. Unlike CWE-277 which deals with permissions set by the creating process, this weakness concerns permissions that are embedded in source objects (such as files within archives) and are preserved during extraction or copying operations. When archives, backups, or copied files contain objects with overly permissive settings, these dangerous permissions persist into the target environment, potentially creating security vulnerabilities.

Risk

Preserved insecure permissions create security risks when files are transferred between systems or restored from backups. Archives created on one system with specific permission requirements may be extracted on another system where those permissions are inappropriate. Backup restorations may reintroduce permission vulnerabilities that were previously corrected. Software distribution through archives may install files with the developer's local permissions rather than production-appropriate settings. The risk is amplified because users often assume extracted files will have safe default permissions, unaware that permissions are being preserved from the source. Attackers who can influence archive contents can embed files with dangerous permissions that will persist when victims extract the archive.

Solution

Configure extraction and copying tools to apply appropriate permissions rather than blindly preserving source permissions. Use archive formats and extraction options that allow specification of target permissions. Implement post-extraction verification to check and correct file permissions. When creating archives for distribution, explicitly set appropriate permissions within the archive rather than relying on source file permissions. Document permission requirements for distributed software and provide installation scripts that set correct permissions. Backup and restore procedures should include permission validation as part of the restoration process. Apply the principle of least privilege when determining what permissions should be preserved versus reset during file operations.

Common Consequences

ImpactDetails
Confidentiality, IntegrityScope: Confidentiality, Integrity

Files extracted with preserved insecure permissions may allow unauthorized reading of sensitive application data or unauthorized modification of critical files. World-readable configuration files may expose credentials, while world-writable executables may allow code injection.

Example Code

Vulnerable Code (Python)

The following examples demonstrate insecure preserved permission vulnerabilities:

# Vulnerable: Preserves permissions from archive
import tarfile
import zipfile
import os

class VulnerableExtractor:

    def extract_tarball(self, archive_path, dest_dir):
        # Vulnerable: extractall preserves permissions from archive
        with tarfile.open(archive_path, 'r:gz') as tar:
            tar.extractall(dest_dir)
        # If archive contains world-writable files, they remain world-writable
        # If archive contains setuid binaries, they remain setuid!

    def extract_zip(self, archive_path, dest_dir):
        # Vulnerable: extractall preserves Unix permissions from zip
        with zipfile.ZipFile(archive_path, 'r') as zf:
            zf.extractall(dest_dir)
        # External attributes containing permissions are preserved

    def copy_from_backup(self, backup_path, restore_path):
        # Vulnerable: shutil.copy2 preserves permissions
        import shutil
        shutil.copy2(backup_path, restore_path)
        # Backup may have had insecure permissions
# Vulnerable: Shell script that preserves archive permissions
#!/bin/bash

# Vulnerable: tar preserves permissions by default
tar -xzf software_package.tar.gz -C /opt/

# Vulnerable: cp -p preserves permissions
cp -p /backup/config.conf /etc/myapp/config.conf

# Vulnerable: rsync -p preserves permissions
rsync -ap /backup/data/ /var/lib/myapp/data/
// Vulnerable: Java extraction preserving permissions
import java.io.*;
import java.util.zip.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class VulnerableArchiveHandler {

    public void extractZip(String archivePath, String destDir) throws IOException {
        try (ZipInputStream zis = new ZipInputStream(
                new FileInputStream(archivePath))) {

            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                Path destPath = Paths.get(destDir, entry.getName());

                if (entry.isDirectory()) {
                    Files.createDirectories(destPath);
                } else {
                    Files.copy(zis, destPath, StandardCopyOption.REPLACE_EXISTING);

                    // Vulnerable: Preserves external attributes as permissions
                    int unixMode = (int) ((entry.getExtra() != null)
                        ? extractUnixMode(entry) : 0644);

                    // Applies whatever permissions were in the archive
                    setUnixPermissions(destPath, unixMode);
                }
            }
        }
    }
}

Fixed Code (Python)

# Fixed: Applies appropriate permissions during extraction
import tarfile
import zipfile
import os
import stat

class SecureExtractor:

    # Define safe permission limits
    MAX_FILE_PERMS = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH  # 0644
    MAX_DIR_PERMS = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH  # 0755
    MAX_EXEC_PERMS = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH  # 0755

    # Permissions to explicitly deny
    DENIED_PERMS = stat.S_ISUID | stat.S_ISGID  # No setuid/setgid

    def extract_tarball_safely(self, archive_path, dest_dir):
        with tarfile.open(archive_path, 'r:gz') as tar:
            for member in tar.getmembers():
                # Sanitize permissions
                self._sanitize_tar_member(member)

                # Also prevent path traversal
                if member.name.startswith('/') or '..' in member.name:
                    raise SecurityError(f"Unsafe path in archive: {member.name}")

                tar.extract(member, dest_dir)

    def _sanitize_tar_member(self, member):
        # Remove setuid/setgid bits
        member.mode &= ~self.DENIED_PERMS

        if member.isdir():
            # Limit directory permissions
            member.mode = member.mode & self.MAX_DIR_PERMS
        elif member.mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
            # Executable file - limit to max executable permissions
            member.mode = member.mode & self.MAX_EXEC_PERMS
        else:
            # Regular file - limit to max file permissions
            member.mode = member.mode & self.MAX_FILE_PERMS

    def extract_zip_safely(self, archive_path, dest_dir, default_mode=0o644):
        with zipfile.ZipFile(archive_path, 'r') as zf:
            for info in zf.infolist():
                # Prevent path traversal
                if info.filename.startswith('/') or '..' in info.filename:
                    raise SecurityError(f"Unsafe path: {info.filename}")

                # Extract file
                extracted = zf.extract(info, dest_dir)

                # Apply safe permissions regardless of archive contents
                if info.is_dir():
                    os.chmod(extracted, 0o755)
                else:
                    os.chmod(extracted, default_mode)

    def copy_from_backup_safely(self, backup_path, restore_path, mode=0o640):
        """Copy file content without preserving source permissions"""
        import shutil

        # Copy content only, not permissions
        shutil.copy(backup_path, restore_path)  # copy, not copy2

        # Apply explicit safe permissions
        os.chmod(restore_path, mode)

        # Set appropriate ownership
        import pwd
        app_user = pwd.getpwnam('myapp')
        os.chown(restore_path, app_user.pw_uid, app_user.pw_gid)
# Fixed: Shell script with permission sanitization
#!/bin/bash

# Extract without preserving permissions
tar --no-same-permissions --no-same-owner -xzf software_package.tar.gz -C /opt/

# Set appropriate permissions after extraction
find /opt/extracted -type f -exec chmod 644 {} \;
find /opt/extracted -type d -exec chmod 755 {} \;
find /opt/extracted -name "*.sh" -exec chmod 755 {} \;

# Copy content without preserving permissions
cp /backup/config.conf /etc/myapp/config.conf
chmod 640 /etc/myapp/config.conf
chown root:myapp /etc/myapp/config.conf

# rsync without permission preservation
rsync -a --no-perms /backup/data/ /var/lib/myapp/data/
# Or explicitly set permissions
rsync -a --chmod=D755,F644 /backup/data/ /var/lib/myapp/data/
// Fixed: Java extraction with permission enforcement
import java.io.*;
import java.util.zip.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;

public class SecureArchiveHandler {

    private static final Set<PosixFilePermission> SAFE_FILE_PERMS =
        PosixFilePermissions.fromString("rw-r--r--");
    private static final Set<PosixFilePermission> SAFE_DIR_PERMS =
        PosixFilePermissions.fromString("rwxr-xr-x");
    private static final Set<PosixFilePermission> SAFE_EXEC_PERMS =
        PosixFilePermissions.fromString("rwxr-xr-x");

    public void extractZipSecurely(String archivePath, String destDir) throws IOException {
        Path destPath = Paths.get(destDir).toRealPath();

        try (ZipInputStream zis = new ZipInputStream(
                new FileInputStream(archivePath))) {

            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                // Prevent path traversal
                Path entryPath = destPath.resolve(entry.getName()).normalize();
                if (!entryPath.startsWith(destPath)) {
                    throw new SecurityException("Path traversal detected: " + entry.getName());
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(entryPath,
                        PosixFilePermissions.asFileAttribute(SAFE_DIR_PERMS));
                } else {
                    // Ensure parent exists
                    Files.createDirectories(entryPath.getParent());

                    // Extract content
                    Files.copy(zis, entryPath, StandardCopyOption.REPLACE_EXISTING);

                    // Apply safe permissions - ignore archive permissions
                    Set<PosixFilePermission> perms = isExecutable(entry.getName())
                        ? SAFE_EXEC_PERMS : SAFE_FILE_PERMS;
                    Files.setPosixFilePermissions(entryPath, perms);
                }
            }
        }
    }

    private boolean isExecutable(String filename) {
        return filename.endsWith(".sh") || filename.endsWith(".py")
            || filename.equals("bin") || filename.contains("/bin/");
    }
}

The fix applies safe permissions during extraction rather than preserving potentially dangerous permissions from the archive source.


Exploited in the Wild

Archive-Based Permission Attacks (Software Distribution, Ongoing)

Malicious archives distributed through compromised repositories or MITM attacks have included files with setuid bits or world-writable permissions. When victims extracted these archives, the dangerous permissions persisted, enabling privilege escalation or backdoor installation.

Backup Restoration Vulnerabilities (Enterprise Environments, Ongoing)

Backup restorations that preserved permissions from older backups have reintroduced permission vulnerabilities that had been previously remediated. Organizations restored from backups created before security hardening, returning systems to vulnerable states.

Export/Import Permission Leakage (Application Migration, Historical)

CVE-2005-1724 documented software that failed to obey specified permissions during export operations, causing insecure permissions to be preserved when data was imported to new systems.


Tools to Test/Exploit


CVE Examples

  • CVE-2005-1724 — Software failed to obey specified permissions during export operations, preserving insecure permissions.

References

  1. MITRE Corporation. "CWE-278: Insecure Preserved Inherited Permissions." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/278.html

  2. GNU tar manual. "Handling File Permissions." https://www.gnu.org/software/tar/manual/

  3. OWASP. "Zip Slip Vulnerability." https://snyk.io/research/zip-slip-vulnerability