php / php-src

The PHP Interpreter
https://www.php.net
Other
38.17k stars 7.75k forks source link

PHP-FPM + Apache: special characters in SCRIPT_FILENAME are URL encoded #15246

Open abgandar opened 3 months ago

abgandar commented 3 months ago

Description

Problem

I use Apache + PHP-FPM to serve PHP files sitting in the server's document root. Some PHP files have spaces and UTF-8 encoded special characters (umlauts, accents) in their file names. Files without spaces or umlauts are served correctly, but files with any of those lead to a 404 with PHP-FPM responding 'Primary script unknown'.

This appears to be due to the way Apache passes the SCRIPT_FILENAME variable: it seems to prefix the physical file name by proxy:fcgi:// and then URL encodes the file name following. So the correctly resolved file name "/docroot/my file.php" gets passed as "proxy:fcgi://my.server/docroot/my%20file.php". PHP-FPM removes the prefix and host name, but then tries to find the file "/docroot/my%20file.php", which doesn't exist.

This seems to me to be related to #12996: the fix to that bug was to URL decode PATH_INFO, but it does not cover the case where the physical script file (SCRIPT_FILENAME) is also URL encoded. So it just URL decodes the bit after the file name.

Symptoms

Using the following PHP-FPM access log format

access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{ORIG_SCRIPT_FILENAME}e"

when requesting the URL

https://my.server/files/test%20me.php

I get logs like this

- -  04/Aug/2024:21:35:48 +0200 "GET /files/test me.php" 404 - /var/htdocs/files/test%20me.php

Note the request (%r) is URL decoded, but the ORIG_SCRIPT_FILENAME is not. The file "/var/htdocs/files/test me.php" exists and is readable by the php-fpm user. Files without the space work fine. I use ORIG_SCRIPT_FILENAME because PHP-FPM unsets SCRIPT_FILENAME when reporting a 404 error (hence the empty %f), but saves the value in ORIG_SCRIPT_FILENAME.

Setup

I'm using PHP 8.3.10 with Apache/2.4.62 (FreeBSD) OpenSSL/3.2.2 on FreeBSD 14.1-STABLE (both built from ports).

Relevant Apache config:

    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/var/run/php-fpm.sock|fcgi://php-fpm"
    </FilesMatch>

    <Proxy "fcgi://php-fpm" enablereuse=off max=18>
    </Proxy>

    # PHP Apache FCGI hack to pass HTTP AUTH info
    ProxyFCGISetEnvIf "%{HTTP:Authorization} =~ /(.+)/" HTTP_AUTHORIZATION "$1"
    ProxyFCGISetEnvIf "%{REMOTE_USER} =~ /(.+)/" PHP_AUTH_USER "$1"

My PHP-FPM setup is stock with UNIX socket at /var/run/php-fpm.sock and access logging as per above.

I have had this setup unchanged for several years and PHP and Apache versions. I'm pretty sure this worked about half a year ago, but can't say when precisely this broke (and if it was a change on the Apache or PHP side).

Workaround

As a workaround, I can make everything run just fine by forcing Apache to unescape the SCRIPT_FILENAME variable before passing it to PHP-FPM:

    ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "%{unescape:%{SCRIPT_FILENAME}}"

I don't know what that does to PATH_INFO stuff as I don't use that.

Solution(?)

Since fpm_main.c already does a lot of gymnastics to fix Apache's botched inputs, it may be better to unescape the SCRIPT_FILENAME when removing the "proxy:fcgi://" prefix in fpm_main.c.

Unfortunately, I don't fully understand all the logic there, and am not familiar enough with PHP sources to confidently propose a fix.

PHP Version

PHP 8.3.10

Operating System

FreeBSD 14.1-STABLE

devnexen commented 3 months ago

sorry @cmb69 was unaware you were editing labels too :)

bukka commented 3 months ago

I have been just reading about this on Apache devel mailing list and it is actually Apache httpd regression in 2.4.61 - https://bz.apache.org/bugzilla/show_bug.cgi?id=69203 .

I'm just checking if there's anything we can do on PHP-FPM side, test the recent patches for this issue and potentially agree on some better solution for the future. In addition I will be setting up some integration tests to prevent this getting released in the future.

fvlasie commented 2 months ago

I have two almost identical systems. One has the problem, one does not. I fixed the issue using abgandar's workaround for which I am very grateful! (Later note: i realised that one server is ARM and the other is x86.)

The only difference in the Apache versions is the build date.

System 1 ARM (no issue)

Rocky Linux 9.4

Server version: Apache/2.4.57 (Rocky Linux)
Server built:   Feb  7 2024 00:00:00

PHP 8.0.30 (fpm-fcgi) (built: Aug  3 2023 17:13:08)
Zend Engine v4.0.30, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.30, Copyright (c), by Zend Technologies

System 2 x86 (has issue)

Rocky Linux 9.4

Server version: Apache/2.4.57 (Rocky Linux)
Server built:   Aug  5 2024 00:00:00

PHP 8.2.13 (fpm-fcgi) (built: Nov 21 2023 09:55:59)
Zend Engine v4.2.13, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.13, Copyright (c), by Zend Technologies