User input in include/require or file read/write paths allows attackers to read wp-config.php, delete files, or execute arbitrary PHP via ../ sequences. [CWE-22 · A01:2021]
arbitrary file read, write, or deletion on the server including wp-config.php
BeforeMerge scans your pull requests against this rule and 5+ others. Get actionable feedback before code ships.
Impact: CRITICAL (arbitrary file read, write, or deletion on the server including wp-config.php)
Any file operation that uses user-supplied input — include, require, readfile, file_get_contents, unlink — without path validation is vulnerable to traversal attacks. An attacker sends ../../wp-config.php to read database credentials, or ../../.htaccess to modify access rules.
CVE-2024-10470 (WPLMS theme, CVSS 9.8) — unauthenticated path traversal via a download parameter used directly in readfile() and unlink(), allowing attackers to delete wp-config.php and trigger a WordPress reinstall. CVE-2024-9047 (WordPress File Upload plugin) — unauthenticated file read and deletion via path traversal.
Incorrect (user input in file paths):
// ❌ Template inclusion with user-controlled value
$template = $_GET['template'];
include WP_CONTENT_DIR . '/plugins/my-plugin/templates/' . $template . '.php';
// Attacker: ?template=../../../../wp-config → reads wp-config.php
// ❌ File download with user-supplied filename
$file = $_GET['download'];
$path = WP_CONTENT_DIR . '/uploads/' . $file;
if ( file_exists( $path ) ) {
readfile( $path ); // Reads any file on the server
}
// ❌ File deletion with user input
$filename = $_POST['file'];
unlink( WP_CONTENT_DIR . '/exports/' . $filename );
// Attacker: file=../../../wp-config.php → deletes wp-config.phpCorrect (allowlist or realpath validation):
// ✅ Allowlist approach — only permit known values
$allowed_templates = [ 'header', 'footer', 'sidebar', 'content' ];
$template = sanitize_key( $_GET['template'] );
if ( ! in_array( $template, $allowed_templates, true ) ) {
wp_die( 'Invalid template.' );
}
include WP_CONTENT_DIR . '/plugins/my-plugin/templates/' . $template . '.php';// ✅ Realpath validation — verify the resolved path stays within allowed directory
$base_dir = realpath( WP_CONTENT_DIR . '/uploads/exports/' );
$requested = sanitize_file_name( $_GET['download'] );
$full_path = realpath( $base_dir . '/' . $requested );
// Three checks: path resolved, is within base dir, file exists
if ( false === $full_path || 0 !== strpos( $full_path, $base_dir . DIRECTORY_SEPARATOR ) ) {
wp_die( 'Access denied.' );
}
if ( ! is_file( $full_path ) ) {
wp_die( 'File not found.' );
}
// Safe to serve
header( 'Content-Type: application/octet-stream' );
header( 'Content-Disposition: attachment; filename="' . basename( $full_path ) . '"' );
readfile( $full_path );
exit;// ✅ sanitize_file_name() strips path separators and dangerous characters
$filename = sanitize_file_name( $_POST['file'] );
// "../../wp-config.php" becomes "wp-config.php"
// But ALWAYS combine with realpath check for defense-in-depth
// ✅ For file deletion, use lookup table instead of user-supplied paths
$export_id = absint( $_POST['export_id'] );
$export = get_post( $export_id );
if ( ! $export || 'export' !== $export->post_type ) {
wp_die( 'Invalid export.' );
}
$file_path = get_post_meta( $export_id, '_file_path', true );
if ( $file_path && file_exists( $file_path ) ) {
wp_delete_file( $file_path ); // WordPress's safe file deletion
}Key defenses:
realpath() + prefix check — verify the resolved path is within the allowed directorysanitize_file_name() — strips path separators (/, \, ..) but use as defense-in-depth, not sole protectionwp_delete_file() — WordPress's safe deletion function with hooks for cleanupDetection hints:
# Find include/require with user input
grep -rn "include.*\\\$_\|require.*\\\$_" wp-content/plugins/ --include="*.php"
# Find file operations with user input
grep -rn "readfile.*\\\$_\|file_get_contents.*\\\$_\|unlink.*\\\$_" wp-content/plugins/ --include="*.php"
# Find file operations without realpath validation
grep -rn "readfile\|file_get_contents" wp-content/plugins/ --include="*.php" | grep -v "realpath"Reference: sanitize_file_name() · CWE-22: Path Traversal