Files or Directories Accessible to External Parties

Description

Files or Directories Accessible to External Parties is a vulnerability where a product makes files or directories accessible to unauthorized actors when they should not be. This occurs when sensitive files are stored in publicly accessible locations without proper access controls. Common scenarios include web servers storing sensitive files under the document root, cloud storage buckets misconfigured for public access, archive files inadvertently including sensitive content, and backup files left in accessible directories. The result is unauthorized access to confidential data, source code, configuration files, or other sensitive information.

Risk

Exposed files and directories create critical data exposure risks. Configuration files may contain database credentials, API keys, and encryption secrets. Source code exposure reveals application logic and potential vulnerabilities. Database backups contain all user data and authentication information. Log files may include sensitive user actions, session tokens, or error details. Git repositories expose complete version history including deleted secrets. Cloud storage misconfigurations have led to massive data breaches affecting millions of users. The damage compounds because exposed files often contain credentials that unlock additional systems.

Solution

Store sensitive files outside web-accessible directories. Configure web servers to deny access to sensitive file types and directories. Use cloud provider controls to disable public access on storage buckets. Implement proper access controls with authentication requirements. Regularly audit file permissions and directory access configurations. Remove or relocate backup files, log files, and development artifacts from production. Use .htaccess or equivalent to block access to sensitive files. Employ security scanners to detect accidentally exposed files. Implement least-privilege file permissions.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Files or Directories - Unauthorized access to sensitive files including configuration, source code, backups, and user data.
IntegrityScope: Integrity

Modify Files or Directories - In some cases, misconfigured permissions allow writing or modifying files, enabling content manipulation or malware injection.

Example Code

Vulnerable Code

# Vulnerable: Apache configuration exposing sensitive files
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html

    # Vulnerable: No restrictions on sensitive files
    # These files are accessible to anyone:
    # - /var/www/html/.git/          (Git repository)
    # - /var/www/html/backup/        (Backups)
    # - /var/www/html/.env           (Environment variables)
    # - /var/www/html/config.php     (Configuration)
    # - /var/www/html/database.sql   (Database dump)

    <Directory /var/www/html>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# Vulnerable directory structure:
# /var/www/html/
# ├── index.php
# ├── .env                    <- API keys, DB credentials
# ├── .git/                   <- Full repository history
# ├── config/
# │   └── database.php        <- DB connection details
# ├── backup/
# │   └── users.sql.gz        <- User database backup
# ├── logs/
# │   └── app.log             <- Application logs
# └── storage/
#     └── uploads/            <- User uploaded files
# Vulnerable: Azure Storage with public access
# Allows anonymous/public read access to blobs

az storage account create \
    --name mystorageaccount \
    --resource-group myResourceGroup \
    --allow-blob-public-access true  # Vulnerable!

az storage container create \
    --account-name mystorageaccount \
    --name sensitive-data \
    --public-access blob  # Vulnerable: Public read access!

# Anyone can now access:
# https://mystorageaccount.blob.core.windows.net/sensitive-data/customers.csv
# https://mystorageaccount.blob.core.windows.net/sensitive-data/credentials.json
# Vulnerable: AWS S3 bucket with public access
# Creates a bucket that allows public read

aws s3api create-bucket \
    --bucket my-company-backups \
    --region us-east-1

# Vulnerable: Setting public read ACL
aws s3api put-bucket-acl \
    --bucket my-company-backups \
    --acl public-read

# Vulnerable: Policy allowing public access
aws s3api put-bucket-policy --bucket my-company-backups --policy '{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::my-company-backups/*"
    }]
}'

# Anyone can now access:
# https://my-company-backups.s3.amazonaws.com/database-backup.sql
# https://my-company-backups.s3.amazonaws.com/user-exports/all-users.csv
<?php
// Vulnerable: PHP application storing files in web root

class VulnerableFileStorage {
    // Vulnerable: Storing sensitive files in web-accessible directory
    private $uploadDir = '/var/www/html/uploads/';
    private $configDir = '/var/www/html/config/';
    private $backupDir = '/var/www/html/backup/';

    public function saveUpload($file, $userId) {
        // Vulnerable: No access control on uploaded files
        $filename = $file['name'];  // Also vulnerable to path traversal
        move_uploaded_file($file['tmp_name'], $this->uploadDir . $filename);

        // Anyone can access: /uploads/sensitive_document.pdf
        return '/uploads/' . $filename;
    }

    public function createBackup() {
        // Vulnerable: Database backup in web root
        $backupFile = $this->backupDir . 'db_' . date('Y-m-d') . '.sql';
        exec("mysqldump -u root -pPassword123 mydb > $backupFile");

        // Accessible at: /backup/db_2024-01-15.sql
        return $backupFile;
    }

    public function generateReport($userId) {
        // Vulnerable: Sensitive reports in public directory
        $reportFile = '/var/www/html/reports/user_' . $userId . '_report.pdf';
        $this->createPdfReport($userId, $reportFile);

        // Anyone who guesses the URL can access other users' reports
        return '/reports/user_' . $userId . '_report.pdf';
    }
}
?>
# Vulnerable: Nginx configuration exposing files
server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # Vulnerable: No protection for sensitive paths
    location / {
        try_files $uri $uri/ =404;
    }

    # Vulnerable: Logs directory exposed
    location /logs/ {
        # No access restrictions
    }

    # Vulnerable: Backup files served
    location ~ \.(sql|bak|old|backup)$ {
        # No deny directive
    }

    # Vulnerable: Git directory accessible
    location /.git/ {
        # Should be: deny all;
    }
}

Fixed Code

# Fixed: Apache configuration protecting sensitive files
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/public  # Fixed: Only public folder is web root

    # Fixed: Deny access to sensitive file patterns
    <FilesMatch "\.(sql|bak|old|backup|log|ini|conf|env|config|git)$">
        Require all denied
    </FilesMatch>

    # Fixed: Block hidden files and directories
    <DirectoryMatch "/\.">
        Require all denied
    </DirectoryMatch>

    # Fixed: Block common sensitive directories
    <DirectoryMatch "/(\.git|\.svn|\.hg|backup|logs|config|storage)">
        Require all denied
    </DirectoryMatch>

    # Fixed: Specific protection for sensitive files
    <Files ".env">
        Require all denied
    </Files>

    <Files "*.php.bak">
        Require all denied
    </Files>

    <Directory /var/www/html/public>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

# Fixed directory structure:
# /var/www/
# ├── html/
# │   └── public/           <- Only this is web-accessible
# │       ├── index.php
# │       ├── css/
# │       └── js/
# ├── config/               <- Outside web root
# │   └── database.php
# ├── storage/              <- Outside web root
# │   └── uploads/
# ├── logs/                 <- Outside web root
# │   └── app.log
# └── backup/               <- Outside web root
#     └── db_backup.sql
# Fixed: Azure Storage with private access only
# Disable public access at account level

az storage account create \
    --name mystorageaccount \
    --resource-group myResourceGroup \
    --allow-blob-public-access false  # Fixed: No public access

az storage container create \
    --account-name mystorageaccount \
    --name sensitive-data \
    --public-access off  # Fixed: Private access only

# Fixed: Use SAS tokens for authorized access
az storage blob generate-sas \
    --account-name mystorageaccount \
    --container-name sensitive-data \
    --name report.pdf \
    --permissions r \
    --expiry $(date -u -d "1 hour" '+%Y-%m-%dT%H:%MZ')

# Fixed: Remove public access from existing containers
gsutil iam ch -d allUsers gs://my-bucket
gsutil iam ch -d allAuthenticatedUsers gs://my-bucket
# Fixed: AWS S3 bucket with private access
# Block all public access at bucket level

aws s3api create-bucket \
    --bucket my-company-backups \
    --region us-east-1

# Fixed: Block all public access
aws s3api put-public-access-block \
    --bucket my-company-backups \
    --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Fixed: Private bucket policy with authenticated access only
aws s3api put-bucket-policy --bucket my-company-backups --policy '{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::123456789012:role/BackupServiceRole"},
        "Action": ["s3:GetObject", "s3:PutObject"],
        "Resource": "arn:aws:s3:::my-company-backups/*"
    }]
}'

# Fixed: Enable default encryption
aws s3api put-bucket-encryption \
    --bucket my-company-backups \
    --server-side-encryption-configuration '{
        "Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]
    }'
<?php
// Fixed: PHP application with secure file storage

class SecureFileStorage {
    // Fixed: Store files outside web root
    private $baseStoragePath = '/var/www/storage/';  // Outside /var/www/html/
    private $uploadDir;
    private $backupDir;

    public function __construct() {
        $this->uploadDir = $this->baseStoragePath . 'uploads/';
        $this->backupDir = $this->baseStoragePath . 'backups/';
    }

    public function saveUpload(array $file, int $userId): string {
        // Fixed: Validate file type
        $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
        if (!in_array($file['type'], $allowedTypes)) {
            throw new InvalidArgumentException('File type not allowed');
        }

        // Fixed: Generate secure filename
        $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
        $safeExtension = preg_replace('/[^a-z0-9]/', '', strtolower($extension));
        $filename = bin2hex(random_bytes(16)) . '.' . $safeExtension;

        // Fixed: Store in user-specific directory outside web root
        $userDir = $this->uploadDir . $userId . '/';
        if (!is_dir($userDir)) {
            mkdir($userDir, 0750, true);
        }

        move_uploaded_file($file['tmp_name'], $userDir . $filename);

        // Fixed: Return identifier, not path
        return $filename;
    }

    public function getFile(string $filename, int $requestingUserId): ?string {
        // Fixed: Access control check
        $filePath = $this->uploadDir . $requestingUserId . '/' . basename($filename);

        // Fixed: Validate path is within allowed directory
        $realPath = realpath($filePath);
        if ($realPath === false ||
            strpos($realPath, realpath($this->uploadDir)) !== 0) {
            return null;  // Path traversal attempt or file not found
        }

        return $filePath;
    }

    public function createBackup(): string {
        // Fixed: Backup stored outside web root with restricted permissions
        $backupFile = $this->backupDir . 'db_' . date('Y-m-d_His') . '.sql.gz';

        // Fixed: Use environment variables for credentials
        $dbUser = getenv('DB_USER');
        $dbPass = getenv('DB_PASS');
        $dbName = getenv('DB_NAME');

        // Fixed: Pipe through gzip, don't store plain SQL
        exec("mysqldump -u " . escapeshellarg($dbUser) .
             " -p" . escapeshellarg($dbPass) . " " .
             escapeshellarg($dbName) . " | gzip > " .
             escapeshellarg($backupFile));

        // Fixed: Restrict file permissions
        chmod($backupFile, 0600);

        return $backupFile;
    }
}

// Fixed: Serve files through authenticated PHP script
// download.php
session_start();

$storage = new SecureFileStorage();
$filename = $_GET['file'] ?? '';
$userId = $_SESSION['user_id'] ?? null;

if (!$userId) {
    http_response_code(401);
    die('Unauthorized');
}

$filePath = $storage->getFile($filename, $userId);
if (!$filePath) {
    http_response_code(404);
    die('File not found');
}

// Fixed: Serve file with proper headers
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
?>
# Fixed: Nginx configuration with access controls
server {
    listen 80;
    server_name example.com;
    root /var/www/html/public;  # Fixed: Only public folder

    # Fixed: Deny access to hidden files and directories
    location ~ /\. {
        deny all;
        return 404;
    }

    # Fixed: Deny access to backup and sensitive file types
    location ~* \.(sql|bak|old|backup|log|ini|conf|config)$ {
        deny all;
        return 404;
    }

    # Fixed: Deny access to sensitive directories
    location ~ ^/(backup|logs|config|storage|vendor|node_modules)/ {
        deny all;
        return 404;
    }

    # Fixed: Deny Git directory access
    location ~ ^/\.git {
        deny all;
        return 404;
    }

    # Fixed: Only serve allowed file types from uploads
    location /uploads/ {
        # Only images and PDFs
        location ~* \.(jpg|jpeg|png|gif|pdf)$ {
            expires 30d;
            add_header X-Content-Type-Options nosniff;
        }
        # Deny everything else
        deny all;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

CVE Examples

  • CVE-2019-5420: Rail Development Mode with exposed secrets allowed remote code execution through accessible development files.
  • CVE-2017-9841: PHPUnit had a remote code execution vulnerability through exposed test scripts.
  • CVE-2023-34362: MOVEit Transfer had SQL injection through publicly accessible endpoint.

References

  1. MITRE Corporation. "CWE-552: Files or Directories Accessible to External Parties." https://cwe.mitre.org/data/definitions/552.html
  2. OWASP. "OWASP Top Ten 2021 - A05:2021 Security Misconfiguration."
  3. AWS. "Blocking public access to your Amazon S3 storage."