Insertion of Sensitive Information into Externally-Accessible File or Directory

Description

Insertion of Sensitive Information into Externally-Accessible File or Directory occurs when software places sensitive information into files or directories that are accessible to unauthorized actors. This includes storing sensitive data in web-accessible directories, publicly readable log files, backup files in document roots, temporary files with improper permissions, or configuration files accessible via the web. Even encrypted sensitive data in accessible locations may be vulnerable to offline attacks.

Risk

Sensitive files in accessible locations are a common source of data breaches. Backup files (.bak, .old, .sql) in web directories expose source code and database contents. Log files may contain credentials, session tokens, or personal data. Configuration files often contain database credentials, API keys, and encryption secrets. Git repositories (.git directories) in web roots expose entire version history. These issues are easily discovered through directory enumeration or search engine dorking.

Solution

Never store sensitive files in web-accessible directories. Configure web servers to deny access to sensitive file types (.sql, .bak, .log, .git, .env). Set proper file permissions restricting access to authorized users only. Use separate storage locations for sensitive data outside the web root. Implement proper access controls on log directories. Remove backup and temporary files from production systems. Use .gitignore and deploy without .git directories. Regularly audit accessible directories for sensitive content.

Common Consequences

ImpactDetails
ConfidentialityScope: Data Exposure

Sensitive data including credentials, PII, and business information is exposed.
AuthenticationScope: Credential Theft

Exposed configuration files reveal database and API credentials.
IntegrityScope: Source Code Disclosure

Backup files and .git directories expose application source code.

Example Code + Solution Code

Vulnerable Scenarios

# VULNERABLE: Apache configuration allowing access to sensitive files
# No restrictions on file types

DocumentRoot /var/www/html

# These files are accessible:
# /var/www/html/config.php.bak
# /var/www/html/database.sql
# /var/www/html/.env
# /var/www/html/.git/
# /var/www/html/debug.log
# VULNERABLE: Writing sensitive data to web-accessible directory
from flask import Flask
import os

app = Flask(__name__)

# Logs in web root!
LOG_FILE = '/var/www/html/app.log'

def log_request(request):
    with open(LOG_FILE, 'a') as f:
        # Logs contain sensitive data!
        f.write(f"User: {request.headers.get('Authorization')}\n")
        f.write(f"Session: {request.cookies.get('session')}\n")

# VULNERABLE: Backup file in web directory
def backup_database():
    import subprocess
    # Backup file accessible via web!
    subprocess.run([
        'mysqldump', 'mydb',
        '-o', '/var/www/html/backup/database.sql'
    ])

# VULNERABLE: Temp file with sensitive data in /tmp
def process_upload(file_data, user_password):
    temp_path = f'/tmp/upload_{user_password}.tmp'  # Password in filename!
    with open(temp_path, 'wb') as f:
        f.write(file_data)
    # File permissions not set - world readable!
// VULNERABLE: Java writing to accessible location
public class VulnerableFileHandling {

    // Log file in web directory
    private static final String LOG_PATH = "/var/www/html/logs/app.log";

    public void logSensitiveOperation(String userId, String action, String data) {
        try (FileWriter fw = new FileWriter(LOG_PATH, true)) {
            // Sensitive data in web-accessible log!
            fw.write(String.format("%s - User %s: %s - Data: %s%n",
                LocalDateTime.now(), userId, action, data));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // VULNERABLE: Export to accessible directory
    public void exportUserData(List<User> users) throws IOException {
        String exportPath = "/var/www/html/exports/users.csv";

        try (PrintWriter pw = new PrintWriter(exportPath)) {
            for (User user : users) {
                // PII in web-accessible file!
                pw.println(String.format("%s,%s,%s,%s",
                    user.getName(), user.getEmail(), user.getSSN(), user.getCreditCard()));
            }
        }
    }
}
// VULNERABLE: Node.js writing to public directory
const fs = require('fs');
const path = require('path');

// Log to public directory
const LOG_PATH = path.join(__dirname, 'public', 'logs', 'app.log');

function logRequest(req) {
    const logEntry = JSON.stringify({
        timestamp: new Date(),
        url: req.url,
        headers: req.headers,  // Includes auth tokens!
        body: req.body         // May include passwords!
    });

    fs.appendFileSync(LOG_PATH, logEntry + '\n');
}

// VULNERABLE: Storing uploads in public folder with original names
app.post('/upload', (req, res) => {
    const file = req.files.document;

    // Stored in publicly accessible location!
    const uploadPath = path.join(__dirname, 'public', 'uploads', file.name);

    file.mv(uploadPath, (err) => {
        if (err) return res.status(500).send(err);
        res.send('Uploaded to: ' + uploadPath);
    });
});

// VULNERABLE: Debug file in web root
function dumpDebugInfo() {
    const debugData = {
        env: process.env,  // All environment variables!
        config: require('./config'),
        dbConnection: db.connection.config
    };

    fs.writeFileSync('./public/debug.json', JSON.stringify(debugData, null, 2));
}

Fixed Code

# SAFE: Apache configuration blocking sensitive files
DocumentRoot /var/www/html

# Block access to sensitive file types
<FilesMatch "\.(sql|bak|old|log|env|git|config|ini|yml|yaml)$">
    Require all denied
</FilesMatch>

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

# Block common sensitive directories
<DirectoryMatch "(\.git|\.svn|\.hg|node_modules|vendor)">
    Require all denied
</DirectoryMatch>

# Specific blocks
<Files ".env">
    Require all denied
</Files>

<Files "*.sql">
    Require all denied
</Files>
# SAFE: Nginx configuration
server {
    root /var/www/html;

    # Block sensitive files
    location ~* \.(sql|bak|old|log|env|config|ini)$ {
        deny all;
        return 404;
    }

    # Block hidden files/directories
    location ~ /\. {
        deny all;
        return 404;
    }

    # Block git directory
    location ~ /\.git {
        deny all;
        return 404;
    }
}
# SAFE: Writing to non-web-accessible locations
from flask import Flask
import os
import tempfile

app = Flask(__name__)

# Logs outside web root with proper permissions
LOG_DIR = '/var/log/myapp'  # Not in web root!
LOG_FILE = os.path.join(LOG_DIR, 'app.log')

def setup_logging():
    os.makedirs(LOG_DIR, exist_ok=True)
    os.chmod(LOG_DIR, 0o750)  # Only owner and group

def log_request_safe(request):
    # Don't log sensitive headers
    safe_log = {
        'timestamp': datetime.utcnow().isoformat(),
        'method': request.method,
        'path': request.path,
        'ip': request.remote_addr
        # No auth headers, cookies, or body!
    }

    with open(LOG_FILE, 'a') as f:
        f.write(json.dumps(safe_log) + '\n')

# SAFE: Backup to secure location
def backup_database_safe():
    import subprocess

    backup_dir = '/var/backups/myapp'  # Not web accessible!
    os.makedirs(backup_dir, exist_ok=True)
    os.chmod(backup_dir, 0o700)  # Owner only

    backup_file = os.path.join(backup_dir, f'db_{datetime.now():%Y%m%d_%H%M%S}.sql')

    subprocess.run([
        'mysqldump', 'mydb',
        '-o', backup_file
    ])

    os.chmod(backup_file, 0o600)  # Owner read/write only

# SAFE: Secure temporary file handling
def process_upload_safe(file_data):
    # Use secure temp file
    with tempfile.NamedTemporaryFile(
        mode='wb',
        dir='/var/tmp/myapp',  # Dedicated temp directory
        delete=False,
        prefix='upload_',
        suffix='.tmp'
    ) as f:
        # Set permissions before writing
        os.chmod(f.name, 0o600)
        f.write(file_data)
        return f.name

# SAFE: Serving files through application (not direct access)
@app.route('/download/<file_id>')
@login_required
def download_file(file_id):
    # Files stored outside web root
    file_record = File.query.get(file_id)

    if not file_record or file_record.owner_id != current_user.id:
        abort(404)

    # Serve file through application with access control
    return send_from_directory(
        '/var/data/uploads',  # Not web accessible!
        file_record.stored_name,
        as_attachment=True,
        download_name=file_record.original_name
    )
// SAFE: Java with secure file handling
public class SecureFileHandling {

    // Logs outside web directory
    private static final String LOG_DIR = "/var/log/myapp";
    private static final Path LOG_PATH = Paths.get(LOG_DIR, "app.log");

    static {
        try {
            Files.createDirectories(Paths.get(LOG_DIR));
            // Set directory permissions
            Set<PosixFilePermission> dirPerms = PosixFilePermissions.fromString("rwxr-x---");
            Files.setPosixFilePermissions(Paths.get(LOG_DIR), dirPerms);
        } catch (IOException e) {
            throw new RuntimeException("Failed to create log directory", e);
        }
    }

    public void logOperation(String userId, String action) {
        try {
            // Don't log sensitive data
            String logEntry = String.format("%s - User %s: %s%n",
                LocalDateTime.now(), userId, action);

            Files.write(LOG_PATH, logEntry.getBytes(),
                StandardOpenOption.CREATE, StandardOpenOption.APPEND);

            // Set file permissions
            Set<PosixFilePermission> filePerms = PosixFilePermissions.fromString("rw-r-----");
            Files.setPosixFilePermissions(LOG_PATH, filePerms);

        } catch (IOException e) {
            // Handle error
        }
    }

    // SAFE: Export to secure location
    public void exportUserData(List<User> users) throws IOException {
        // Export to non-web-accessible location
        Path exportDir = Paths.get("/var/data/exports");
        Files.createDirectories(exportDir);

        String filename = "users_" + System.currentTimeMillis() + ".csv";
        Path exportPath = exportDir.resolve(filename);

        try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(exportPath))) {
            for (User user : users) {
                // Export only non-sensitive fields
                pw.println(String.format("%s,%s",
                    user.getId(), user.getDisplayName()));
            }
        }

        // Restrictive permissions
        Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
        Files.setPosixFilePermissions(exportPath, perms);
    }
}
// SAFE: Node.js with secure file handling
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// Logs outside public directory
const LOG_DIR = '/var/log/myapp';
const LOG_PATH = path.join(LOG_DIR, 'app.log');

// Ensure log directory exists with proper permissions
if (!fs.existsSync(LOG_DIR)) {
    fs.mkdirSync(LOG_DIR, { recursive: true, mode: 0o750 });
}

function logRequestSafe(req) {
    // Only log non-sensitive data
    const logEntry = JSON.stringify({
        timestamp: new Date().toISOString(),
        method: req.method,
        path: req.path,
        ip: req.ip,
        userAgent: req.headers['user-agent']
        // No auth tokens, cookies, or request bodies!
    });

    fs.appendFileSync(LOG_PATH, logEntry + '\n', { mode: 0o640 });
}

// SAFE: Uploads stored outside public directory
const UPLOAD_DIR = '/var/data/uploads';  // Not in public!

app.post('/upload', requireAuth, (req, res) => {
    const file = req.files.document;

    // Generate random filename
    const ext = path.extname(file.name);
    const storedName = crypto.randomUUID() + ext;
    const uploadPath = path.join(UPLOAD_DIR, storedName);

    file.mv(uploadPath, (err) => {
        if (err) return res.status(500).send('Upload failed');

        // Set restrictive permissions
        fs.chmodSync(uploadPath, 0o600);

        // Store metadata in database
        FileRecord.create({
            originalName: file.name,
            storedName: storedName,
            ownerId: req.user.id
        });

        res.json({ id: storedName });
    });
});

// SAFE: Serve files through application
app.get('/download/:fileId', requireAuth, async (req, res) => {
    const file = await FileRecord.findById(req.params.fileId);

    if (!file || file.ownerId !== req.user.id) {
        return res.status(404).send('File not found');
    }

    const filePath = path.join(UPLOAD_DIR, file.storedName);

    res.download(filePath, file.originalName);
});

// SAFE: Deployment without .git directory
// .dockerignore or deployment script:
/*
.git
.gitignore
*.log
*.sql
*.bak
.env
node_modules
*/

Exploited in the Wild

Git Directory Exposure

Numerous websites have exposed their .git directories, allowing attackers to download entire source code repositories including credentials and secrets.

Database Dump Exposure

SQL backup files left in web-accessible directories have exposed millions of records including user credentials and personal data.

Log File Data Breaches

Application logs containing sensitive information have been accessed through directory traversal or direct URL access.


Tools to test/exploit


CVE Examples

  • CVE-2021-44228 — Log4j (related to log injection).

  • CVE-2019-11358 — Exposed configuration files.

  • Numerous application-specific file exposure CVEs.


References

  1. MITRE. "CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory." https://cwe.mitre.org/data/definitions/538.html

  2. OWASP. "Sensitive Data Exposure." https://owasp.org/www-project-web-security-testing-guide/