Understanding the Security Risks of PHP’s cgi.fix_pathinfo with Nginx and PHP-FPM


3 views

The PHP directive cgi.fix_pathinfo controls how PHP interprets requests when PATH_INFO is present. By default (cgi.fix_pathinfo=1), PHP will:

  1. First try to find the exact file matching the request URI
  2. If not found, it will progressively "trim" path components to find a valid file

For example, with a request to /test.php/nonexistent.jpg:

1. First checks for /test.php/nonexistent.jpg (not found)
2. Then checks for /test.php (found and executed)
3. PATH_INFO becomes "/nonexistent.jpg"

The danger emerges from how Nginx passes requests to PHP-FPM. A typical vulnerable configuration looks like:

location ~ \.php$ {
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

Attackers can exploit this by crafting requests like:

GET /uploads/user_photo.jpg/evil.php HTTP/1.1

With cgi.fix_pathinfo=1, PHP will:

  1. Find /uploads/user_photo.jpg (existing file)
  2. Execute it as PHP code because of the .php suffix in PATH_INFO

The most secure approach is to set cgi.fix_pathinfo=0 in php.ini. However, if you need PATH_INFO support, use this Nginx configuration:

location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.+)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    include fastcgi_params;
    
    # Additional security measures
    try_files $uri =404;
    fastcgi_intercept_errors on;
}

Using Unix sockets instead of TCP connections adds filesystem permission checks, but doesn't eliminate the core vulnerability. The real protection comes from:

  • Proper file permissions (0750 for directories, 0640 for files)
  • Disabling cgi.fix_pathinfo
  • Using try_files $uri =404 in Nginx

Apache handles PATH_INFO differently by default, making it less vulnerable to this specific attack. However, similar issues can occur with misconfigured handlers. The key differences:

  1. Apache's mod_php doesn't use FastCGI
  2. Apache typically checks file existence before processing
  3. Most Apache configurations don't execute arbitrary files as PHP

While modern Nginx and PHP-FPM versions have improved security:

  • PHP 7.0+ has better default security settings
  • Nginx 1.10+ includes better FastCGI handling
  • The vulnerability still exists if misconfigured

Always test your configuration with:

curl -I http://yoursite.com/uploads/test.jpg/index.php

And verify that it either:

  1. Returns 404 (secure)
  2. Returns the JPEG with Content-Type: image/jpeg (acceptable)
  3. Does NOT return a 200 with PHP execution

The PHP directive cgi.fix_pathinfo controls how PHP interprets the PATH_INFO and SCRIPT_FILENAME variables in CGI environments. When enabled (set to 1), PHP will:


// Example of vulnerable interpretation:
Request URL: /script.php/non-existent-path
// PHP may execute script.php while ignoring the invalid trailing path

This becomes dangerous with Nginx's default FastCGI configuration when handling PHP files:


location ~ \.php$ {
    fastcgi_pass unix:/var/run/php-fpm.sock;
    include fastcgi_params;
    # Vulnerable if cgi.fix_pathinfo=1
}

Attack scenario:

  1. Attacker uploads malicious image with PHP code (e.g., evil.jpg)
  2. Requests the file as evil.jpg/script.php
  3. Nginx passes the request to PHP-FPM
  4. With cgi.fix_pathinfo=1, PHP executes evil.jpg as PHP code

Current best practices for Nginx configurations:


location ~ \.php$ {
    fastcgi_pass unix:/var/run/php-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_index index.php;
    try_files $uri =404; # Critical security measure
}

Essential security measures:

  • Set cgi.fix_pathinfo=0 in php.ini
  • Implement try_files $uri =404 to verify file existence
  • Use separate storage for user uploads with no execution permissions

Apache is generally less vulnerable because:

  • Its default handler for PHP files doesn't process PATH_INFO the same way
  • mod_php executes scripts based on actual file extensions
  • Typical .htaccess rules prevent execution of uploaded files

While using Unix sockets improves security by:

  • Eliminating network exposure
  • Providing filesystem permission controls

It doesn't fundamentally solve the pathinfo issue - you still need proper configuration.

Modern PHP versions (7.4+) have better default security:

  • New installations typically set cgi.fix_pathinfo=0
  • PHP-FPM has improved request validation
  • Nginx documentation now provides secure examples