简体   繁体   English

Nginx 将所有流量重定向到 index.php,但不允许任意文件访问

[英]Nginx redirect all traffic to index.php, but don't allow arbitrary file access

I have a legacy PHP app (Zend Framework) that I'm moving from Apache to Nginx.我有一个遗留的 PHP 应用程序(Zend 框架),我正在从 Apache 迁移到 Nginx。 Except for things like web files (JS, CSS, images, fonts, etc.), all traffic needs to go through the index.php file in the root of the project folder.除了网页文件(JS、CSS、图片、字体等)外,所有流量都需要经过项目文件夹根目录下的index.php文件。 So I need to redirect all traffic to index.php , except for web files.所以我需要将所有流量重定向到index.php ,除了 web 文件。

There are a lot of solutions to this that use try_files as a catch-all.有很多使用try_files作为包罗万象的解决方案。 Basically if a file is not found on the filesystem, nginx will send the request to index.php .基本上,如果在文件系统上找不到文件,nginx 会将请求发送到index.php

The problem with this is that the app stores its configuration file in a web-accessible path (ie in a subdirectory of the folder that holds index.php ).问题在于应用程序将其配置文件存储在网络可访问的路径中(即在包含index.php的文件夹的子目录中)。 So you could point your browser to the configuration file path and read it (eg https://example.com/config/app.xml ).因此,您可以将浏览器指向配置文件路径并读取它(例如https://example.com/config/app.xml )。 Since the file exists on the filesystem, nginx will serve it.由于文件存在于文件系统中,nginx 会为它提供服务。 There are actually a few more project config files that can be accessed in this way too.实际上还有一些项目配置文件也可以通过这种方式访问​​。

So, how do I send all requests to index.php , except for web files, and also not allow arbitrary files to be read from the web?那么,如何将所有请求发送到index.php ,除了 web 文件,也不允许从 web 读取任意文件?

Yes, I know I could update the app to access the config files in another location, but it's legacy and not super crucial.是的,我知道我可以更新应用程序以访问另一个位置的配置文件,但它是遗留的,并不是非常重要的。 I don't want to spend more time on it than I have to and I don't want to break it.我不想花更多的时间在它上面,我不想破坏它。

I could also forbid access to any extensions that happen to be config files (eg xml , yml , etc.), but it's a big project and I don't want to risk missing something.我还可以禁止访问碰巧是配置文件的任何扩展(例如xmlyml等),但这是一个大项目,我不想冒险丢失某些东西。

I figured out a solution.我想出了一个解决办法。 I didn't find anything that worked exactly the way I wanted online, so I'm asking this question in order to answer it.我没有找到任何完全符合我在网上想要的方式工作的东西,所以我问这个问题是为了回答它。

Here's the configuration I came up with (leaving out listen , server_name , etc. since they aren't important):这是我想出的配置(省略listenserver_name等,因为它们并不重要):

server {
  root /path/to/www;

  # this just catches all web files and allows them through
  location ~* \.(js|ico|gif|jpg|png|svg|css|jpeg|wav|mp3|eot|woff|ttf)$ {}

  # send all other traffic to index.php. this is important because it
  # blocks access to things like configuration.xml
  location / {
    rewrite ^ /index.php last;
  }

  location = /index.php {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
  }
}

I'll go through each block and explain it's purpose.我将遍历每个块并解释其目的。

location ~* \.(js|ico|gif|jpg|png|svg|css|jpeg|wav|mp3|eot|woff|ttf)$ {}

The first block catches all web files.第一个块捕获所有 Web 文件。 The body of the block is empty, which just lets nginx send the matched files directly.块的主体是空的,它只是让 nginx 直接发送匹配的文件。 You could add caching directives in here if you want.如果需要,您可以在此处添加缓存指令。

location / {
  rewrite ^ /index.php last;
}

The second block sends all other traffic to index.php .第二个块将所有其他流量发送到index.php Since the first block matches all the web files, this block won't apply to them.由于第一个块匹配所有 Web 文件,因此该块不适用于它们。 This is the block that prevents arbitrary files from being accessed from the web, since all of those file requests will be sent to index.php which will respond with a 404.这是阻止从 Web 访问任意文件的块,因为所有这些文件请求都将发送到index.php ,它将以 404 响应。

location = /index.php {
  include snippets/fastcgi-php.conf;
  fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

The last block is just to process PHP requests through FPM.最后一个块只是通过 FPM 处理 PHP 请求。 Though, as Ivan helpfully pointed out , it's important to only target index.php otherwise any PHP file could be directly executed if someone knew the URL for it.不过,正如Ivan 有用地指出的那样,仅针对index.php很重要,否则如果有人知道它的 URL,则可以直接执行任何 PHP 文件。

With all of this, I get:有了这一切,我得到:

  1. If I access a JS, CSS, images, etc. they are served directly.如果我访问 JS、CSS、图像等,它们会被直接提供。
  2. If I try to access a configuration file, I get a 404.如果我尝试访问配置文件,我会得到 404。
  3. If I try to access one of the actions that the app knows about, it works.如果我尝试访问应用程序知道的操作之一,它会起作用。
  4. Query parameters work as expected.查询参数按预期工作。

EDIT: After seeing Ivan's answer I was able to eliminate one block and simplify another.编辑:看到伊万的回答后,我能够消除一个块并简化另一个块。 I updated the config to reflect the change.我更新了配置以反映更改。

Looking at your configuration I have a strong desire to comment it, however this would be much beyond the single comment format so writing this as an answer.看着您的配置,我强烈希望对其进行评论,但这将远远超出单一评论格式,因此将其写为答案。

 location ~* \.(js|ico|gif|jpg|png|svg|css|jpeg|wav|mp3|eot|woff|ttf)$ {}

Of course, this would work.当然,这会奏效。 But in a real life this would effectively blocking the files such as robots.txt , sitemap.xml , preventing SSL certificate validation via the /.well-known/pki-validation/<signature>.txt etc.但在现实生活中,这将有效地阻止robots.txtsitemap.xml等文件,阻止通过/.well-known/pki-validation/<signature>.txt等进行 SSL 证书验证。

 location ~* ^/(?!(index\.php)) { rewrite ^ /index.php$is_args$args; }

Both rewrite and location directives works with the normalized URI (which doesn't include the query part of the request). rewritelocation指令都适用于规范化的 URI(不包括请求的查询部分)。 While the try_files or return directives usually require this $is_args$args suffix to be used, with the rewrite directive all the query arguments are preserved and follows the rewritten URI.虽然try_filesreturn指令通常需要使用这个$is_args$args后缀,但使用rewrite指令,所有查询参数都被保留并遵循重写的 URI。 This means the /users?blah=1 request is being rewrited to - surprise, surprise - /index.php?blah=1?blah=1 .这意味着/users?blah=1请求被重写为 - 惊喜,惊喜 - /index.php?blah=1?blah=1 This makes nginx consider the last ?blah=1 to be a query string and the preceding /index.php?blah=1 to be the filename giving you "HTTP 404 Not found" error.这使得 nginx 认为最后一个?blah=1是一个查询字符串,前面的/index.php?blah=1是给你“HTTP 404 Not found”错误的文件名。 The right way to write this location would be the写这个位置的正确方法是

location ~* ^/(?!index\.php) {
  rewrite ^ /index.php last;
}

and it can be even more simple:它可以更简单:

location / {
  rewrite ^ /index.php last;
}

location = /index.php { # instead of ~ \.php$
  ...
}

However if you want to pass all the other requests to your index.php controller it could be done in a much more effective way.但是,如果您想将所有其他请求传递给您的index.php控制器,则可以以更有效的方式完成。 I assume the snippets/fastcgi-php.conf file you are using is the default one from some Debian-based Linux distro:我假设您使用的snippets/fastcgi-php.conf文件是某些基于 Debian 的 Linux 发行版的默认文件:

 # regex to split $uri to $fastcgi_script_name and $fastcgi_path fastcgi_split_path_info ^(.+\.php)(/.+)$; # Check that the PHP script exists before passing it try_files $fastcgi_script_name =404; # Bypass the fact that try_files resets $fastcgi_path_info # see: http://trac.nginx.org/nginx/ticket/321 set $path_info $fastcgi_path_info; fastcgi_param PATH_INFO $path_info; fastcgi_index index.php; include fastcgi.conf;

You don't need it at all, as you don't need the location ~ \.php$ { ... } since the only PHP file you want to allow access to is the index.php .您根本不需要它,因为您不需要location ~ \.php$ { ... }因为您要允许访问的唯一 PHP 文件是index.php Use the following:使用以下内容:

location / {
  # define the default FastCGI parameters
  include fastcgi.conf;
  # always use the 'index.php' as the FastCGI script
  fastcgi_param SCRIPT_FILENAME $document_root/index.php;
  # pass the request to the FastCGI backend
  fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

The whole configuration will be the整个配置将是

server {
  root /path/to/www;

  location ~* \.(js|ico|gif|jpg|png|svg|css|jpeg|wav|mp3|eot|woff|ttf)$ {}

  location / {
    include fastcgi.conf;
    fastcgi_param SCRIPT_FILENAME $document_root/index.php;
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
  }
}

Update @ 2022.05.17更新@ 2022.05.17

Since the new media file types and formats comes to the scene time to time, requiring you to add those types and change the nginx configuration every time it happens, if one day you'd decide to switch from the list all the allowed to list all the denied approach, the most optimal way to do it can be the following:由于新的媒体文件类型和格式不时出现,需要您添加这些类型并在每次发生时更改 nginx 配置,如果有一天您决定从允许的列表切换到列出所有被拒绝的方法,最优化的方法可以是以下:

# deny hidden files and files with the extensions listed below
location ~ /\.|\.(?:xml|yml|php|phar|inc)$ {
    deny all;
}

location / {
    try_files $uri /index.php$is_args$args;
    # cache policy for the static files can be added here
}

location = /index.php {
    include fastcgi.conf;
    fastcgi_param SCRIPT_FILENAME $document_root/index.php;
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

Since the exact match locations takes precedence over the regex match ones, this will effectively block any PHP (or other listed types) file access while the index.php one will remain allowed.由于精确匹配位置优先于正则表达式匹配位置,这将有效地阻止任何 PHP(或其他列出的类型)文件访问,而index.php将保持允许。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM