How to restrict allowed MIME types in uploads from PHP in WordPress

Contents

Overview: Why restrict upload MIME types in WordPress

WordPress allows file uploads through the Media Library, REST API endpoints, custom upload handlers and third-party plugins. By default WordPress controls which file extensions and MIME types are acceptable, but server configuration, browser behavior and user-supplied data can make MIME type checks unreliable. Restricting allowed MIME types strictly in PHP provides a centralized, server-side defense that:

  • Reduces the risk of arbitrary code execution via uploaded files (for example files with .php content stored in public directories).
  • Prevents users from uploading dangerous or unexpected file types (SVG with embedded scripts, executables, etc.).
  • Ensures downstream processing (image resizing, meta extraction) only happens on known types.

Important concepts and terminology

  • MIME type — a media type reported by the browser, the OS, or detected by server-side tools (for example image/png or application/pdf).
  • Extension — the file name suffix (for example .jpg, .php). Extensions are easy to spoof.
  • Whitelisting vs blacklisting — prefer whitelisting allowed types (only allow a small set) instead of trying to block many dangerous types.
  • Client-provided MIME — the browser supplies a MIME type during upload, which cannot be trusted alone.
  • Server-side detection — PHPs finfo (fileinfo) or WordPress wp_check_filetype_and_ext give stronger validation than client-supplied data.

WordPress hooks you will use

  • upload_mimes — filter used to alter the list of allowed MIME types reported by WordPress. This is the primary hook to whitelist file types.
  • wp_handle_upload_prefilter — receives the uploaded file array before WordPress moves it good for additional checks and setting a human-readable error in file[error] to reject the upload immediately.
  • wp_check_filetype_and_ext() — WordPress function to check extension and MIME pass a temp file path and original filename for best guesses.
  • sanitize_file_name — can be used to normalize/clean file names and remove dangerous character sequences, but does not block MIME types.

Core approaches (from simple to robust)

  1. Simple whitelist with the upload_mimes filter (fast, suitable for many sites).
  2. Whitelist explicit runtime validation in wp_handle_upload_prefilter using WordPress checks (recommended).
  3. Above deep content checks using PHP fileinfo (finfo) or reading file headers to detect masqueraded content (strongest).
  4. Server-level restrictions (NGINX/Apache) and SAPI-level checks (deny execution in uploads folder) as defense-in-depth.

1) Basic whitelist via upload_mimes

This is the simplest and most common solution. Replace or prune the list of allowed MIME types so only expected types are available.

 image/jpeg,
        png           => image/png,
        gif           => image/gif,
        // Add more file types if you need them:
        // pdf => application/pdf,
    )
}

Notes:

  • Keys like jpgjpegjpe group multiple extensions to the same MIME type.
  • This changes only WordPress allowed list. It doesnt validate file content — an uploaded .jpg file could still contain non-image data unless you check content.

2) Rejecting disallowed uploads at upload time (wp_handle_upload_prefilter)

Use this when you want to actively block a file during upload and provide a specific error message. This example uses wp_check_filetype_and_ext for more reliable detection than the browser-supplied MIME.


Why this is better:

  • wp_check_filetype_and_ext inspects the file and cross-checks extension and known MIME maps.
  • You can present a clear error to the user rather than relying on upstream code to refuse the file silently.

3) Strong content validation using PHP fileinfo (finfo)

To detect files that have correct extensions but incorrect content (for example a PHP script renamed to image.jpg), examine the binary signature with fileinfo. This is especially important if you want to prevent any executable content uploaded with an allowed extension.


Notes:

  • finfo provides better content detection than relying solely on the extension or client-supplied type.
  • finfo may not be available on all hosts. Always check with function_exists(finfo_open) and fall back sensibly.

4) Preventing double extensions and disguised filenames

Attackers sometimes upload files named like image.jpg.php or report.pdf.zip. Validate and sanitize filenames, and look for multiple extensions. Reject filenames that contain deferred executable fragments.

 2 ) {
        // Example policy: allow only one dot (one extension)
        file[error] = Invalid filename: multiple extensions detected.
        return file
    }

    // Alternatively, check for known bad sequences:
    if ( strpos(filename, .php) !== false  strpos(filename, .phtml) !== false ) {
        file[error] = Invalid filename: executable extensions are not allowed.
        return file
    }

    return file
}

5) Handling SVG files carefully

SVG is an XML format and can embed scripts or external references. WordPress does not allow SVG by default. If you need to allow SVG uploads, do this cautiously:

  • Prefer sanitizing the SVG (remove scripts, external resources) server-side before inserting it into the media library. Use a well-maintained sanitization library.
  • Alternatively, allow SVG only for trusted user roles (administrator) and keep sanitization or editing disabled for other roles.

Complete plugin example: strict whitelist content check

Below is a self-contained plugin you can drop into wp-content/plugins/restrict-mimes/restrict-mimes.php. It enforces a strict whitelist, validates content using WordPress checks and finfo, rejects double extensions and prevents uploads of suspicious content.

 image/jpeg,
        png           => image/png,
        gif           => image/gif,
    )
}

function rm_prefilter_validate(file) {
    // 1) Reject obvious double extensions
    name = file[name]
    if ( preg_match(/.[^.] .([a-z0-9]{2,6})/i, name) ) {
        // e.g. file.jpg.php or file.zip.exe
        file[error] = Upload blocked: invalid filename.
        return file
    }

    // 2) Use WP and finfo to detect mime
    check = wp_check_filetype_and_ext( file[tmp_name], file[name] )
    detected = isset(check[type]) ? check[type] : 

    if ( function_exists(finfo_open) ) {
        finfo = finfo_open(FILEINFO_MIME_TYPE)
        if ( finfo ) {
            finfo_type = finfo_file(finfo, file[tmp_name])
            finfo_close(finfo)
            if ( finfo_type ) {
                detected = finfo_type
            }
        }
    }

    allowed = array(image/jpeg, image/png, image/gif)

    if ( empty(detected)  ! in_array(detected, allowed, true) ) {
        file[error] = Upload blocked: unsupported file type.
        return file
    }

    // 3) Extra check: ensure no PHP tags in file content
    contents = file_get_contents(file[tmp_name], false, null, 0, 4096) // read first 4KB
    if ( contents !== false  (strpos(contents, 

Other considerations and best practices

  • Defense in depth: Besides checking MIME types in PHP, configure your web server to prevent execution of scripts from the uploads directory. For Apache, disable PHP execution with an .htaccess file. For nginx, prevent PHP processing of files in the uploads path.
  • Media file permissions: Store files with non-executable permissions and avoid storing uploads in directories where PHP execution is allowed.
  • Sanitize filenames: Use sanitize_file_name() to clean suspicious characters and normalize names.
  • Least privilege: If you allow some risky types (SVG), only allow them for high-trust roles and sanitize before serving embed.
  • REST API / AJAX uploads: Filters like upload_mimes and wp_handle_upload_prefilter apply to uploads performed through the REST API and admin AJAX endpoints as long as WordPress core upload handler is used.
  • Multisite: The upload_mimes filter applies across sites for plugin code. Network-level settings (Network Admin → Settings → Upload Settings) may also control permitted filetypes stored in site options (upload_filetypes). When developing network-activated plugins, ensure behavior matches network policies.
  • Logging and alerts: Log rejected uploads so you can audit suspicious activity. Consider notifying admins of repeated blocked attempts.
  • Testing: Test uploads with real sample files, renamed files, files with spoofed MIME types and files with embedded scripts. Use both browser-based upload and REST endpoints to ensure filters apply consistently.

Server-level restrictions (Apache / nginx examples)

Application-layer checks are necessary but not sufficient. Add server-level rules to prevent execution of uploaded files in the uploads directory.

  • Apache (.htaccess) - place in wp-content/uploads:
    • Disable PHP handling in uploads: add RemoveHandler .php .phtml .php3 or deny access to those files.
  • nginx - configuration snippet:
    • Do not pass uploads folder to PHP-FPM. Return a 403 for script-like extensions, and serve static files only.

Common pitfalls and how to avoid them

  • Relying solely on client-supplied MIME types — always perform server-side validation.
  • Allowing many file types by default — favor minimal whitelists consistent with your application needs.
  • Trusting file extensions — validate content and use PHP fileinfo or WordPress helpers to detect real type.
  • Assuming one fix covers all upload paths — test theme, plugin and REST uploads to ensure filters are active.

Testing checklist

  1. Try uploading a valid allowed file (e.g., a standard PNG) — it should succeed.
  2. Upload a disallowed extension (e.g., .exe) — it should be blocked.
  3. Rename a .php file to .jpg and attempt upload — robust checks should block this.
  4. Upload an SVG with script content — either sanitize or block unless you allow SVG specially.
  5. Attempt upload via REST API as a privileged user and as a low-privilege user — ensure role-based policies are enforced.
  6. Verify that uploaded files cannot execute code on the server (server rules in place).

Summary

To restrict allowed MIME types effectively in WordPress from PHP:

  • Use upload_mimes to define a strict whitelist.
  • Implement wp_handle_upload_prefilter to validate content at upload time and return friendly errors.
  • Use wp_check_filetype_and_ext together with PHP fileinfo (finfo) when available for reliable MIME detection.
  • Guard against double extensions, inspect file contents for script tags, and handle SVG with care.
  • Complement PHP-level checks with server-level configuration to prevent execution from the uploads directory.

Combining these measures gives a robust, defense-in-depth approach that prevents most upload-based attacks while allowing legitimate media use.

Further reading: WordPress Codex and Developer Reference for functions used — for example upload_mimes, wp_check_filetype_and_ext, and WordPress plugin security best practices.



Acepto donaciones de BAT's mediante el navegador Brave :)



Leave a Reply

Your email address will not be published. Required fields are marked *