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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Data Exposure Sensitive data including credentials, PII, and business information is exposed. |
| Authentication | Scope: Credential Theft Exposed configuration files reveal database and API credentials. |
| Integrity | Scope: 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
-
GitTools — extract .git directories.
-
DirBuster/GoBuster — directory enumeration.
-
Google Dorks — find exposed files.
-
Nuclei — templates for sensitive file detection.
CVE Examples
-
CVE-2021-44228 — Log4j (related to log injection).
-
CVE-2019-11358 — Exposed configuration files.
-
Numerous application-specific file exposure CVEs.
References
-
MITRE. "CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory." https://cwe.mitre.org/data/definitions/538.html
-
OWASP. "Sensitive Data Exposure." https://owasp.org/www-project-web-security-testing-guide/