.htaccess 中的白名单

发布于 2024-10-28 05:45:12 字数 1857 浏览 2 评论 0原文

我想使用白名单,而不是将不可访问的目录列入黑名单(如拒绝所有)。基本上,我需要这个功能:

  1. 如果 uri 请求 /public 目录中存在的文件,则显示它;
  2. 否则将请求路由到/public/index.php;
  3. 请求字符串中不需要“public”字符串:http://site.com/flower.jpg 显示文件系统中的DOCUMENT_ROOT/public/flower.jpg 文件;

示例:

目录结构:

 public\
   flower.jpg
   index.php
 data\
   secret_file.crt

请求字符串和预期结果:

  • site.com/flower.jpg
    • 显示flower.jpg
  • site.com/data/secret_file.crt
  • site.com/public/flower.jpg
  • site.com/public
  • site.com/data
  • site.com/any/random_url
    • 请求被路由到 public/index.php

我现在拥有的:(

甚至那个在外部帮助下)

# the functionality described in #1 above
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* public%{REQUEST_URI} [L]

# I'd like to take out the following line so ALL other requests route to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php

如果我删除

RewriteCond %{ REQUEST_FILENAME} !-f

行,它开始工作,我尝试了无数的配置,阅读了 modRewrite 文档,但不明白为什么这个简单的东西拒绝简单地运行。

谁能帮助我或指出正确的方向?


完整的最终解决方案供参考


RewriteEngine On

# following line stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* /public%{REQUEST_URI} [L]

# don't apply this rule if the first rule has been applied
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule .* /public/index.php [L]

当应用程序位于子目录中时会稍微复杂一些,例如http://site.com/uk/,但这效果很好。

Instead of blacklisting inaccessible directories (like with deny all) I want to use a whitelist. Basically, I need this functionality:

  1. If the uri requests a file that exists in /public directory, display it;
  2. Otherwise route the request to /public/index.php;
  3. 'public' string is not needed in request string: http://site.com/flower.jpg displays DOCUMENT_ROOT/public/flower.jpg file from the file system;

Example:

Directory structure:

 public\
   flower.jpg
   index.php
 data\
   secret_file.crt

Request string and expected result:

  • site.com/flower.jpg
    • flower.jpg is displayed
  • site.com/data/secret_file.crt
  • site.com/public/flower.jpg
  • site.com/public
  • site.com/data
  • site.com/any/random_url
    • request is routed to public/index.php

What I have now:

(and even that with outside help)

# the functionality described in #1 above
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* public%{REQUEST_URI} [L]

# I'd like to take out the following line so ALL other requests route to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php

If I remove the

RewriteCond %{REQUEST_FILENAME} !-f

line, it seizes to work, I've experimented countless configurations, read the modRewrite docs but can't figure out why this simple thing refuses to simply function.

Can anyone help me out or point in the right direction?


Complete final solution for reference


RewriteEngine On

# following line stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* /public%{REQUEST_URI} [L]

# don't apply this rule if the first rule has been applied
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule .* /public/index.php [L]

It's a little more complicated when the application is in a subdirectory, like http://site.com/uk/, but this works great.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

楠木可依 2024-11-04 05:45:12

好吧,解释起来有点令人困惑。您遇到的问题是,当 mod_rewrite 重写某些内容时,如果没有 [R] 或 [P],它会在内部重定向,并且所有重写规则都会再次应用。这种情况不断发生,直到重写的 uri 与未重写的 uri 相同。因此,您拥有的第一条规则将被第二条规则重写。你需要防止这种情况发生。

首先,让我们看看第一条规则。您所拥有的完全没问题,只是您需要为将 site.com/public/flower.jpg 重新路由到 public/index.php 的警告添加一个条件。这意味着如果请求本身包含 /public/ ,它将不会为该请求提供服务(并让第二条规则处理)。这里需要额外注意的是,如果“/public”内有一个目录“public”,如 DOCUMENT_ROOT/public/public/ 中,它将无法访问。

# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule ^ /public%{REQUEST_URI} [L]

在这里,我们对以 GET /public/flower.jpg 开头的请求进行了额外检查,如果匹配,我们完全跳过此规则。此外,如果您尝试访问 /public/ 中的目录,此规则也会被破坏。例如,如果您在“/public”中有一个目录“stuff”,并尝试通过请求 site.com/stuff/ 访问它,则此规则将不允许您查看该内容(甚至如果 /stuff/ 中有一个 index.html 文件),因为您没有检查目录是否存在。您可以通过为 -d 添加此条件来实现此目的,如下所示:

# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename/directory exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -d
RewriteRule ^ /public%{REQUEST_URI} [L]

-d 条件与 -f 的 [OR] 一起表示:if %{DOCUMENT_ROOT}/public%{REQUEST_URI} is常规文件或目录。 (请参阅 RewriteCond 文档

现在介绍第二条规则,以及这看起来有点令人困惑,因为我们必须处理第一条规则条件的否定。如果第一条规则通过并且 URI 被重写,则会发生 2 件事:

  1. 请求不是以以下内容开头: GET /public/
  2. uri 被重写为“/public/[something]”

因此,我们有 2 个条件处理这个问题。如果第一条规则重写了 URI,我们不想再次触及它。这解决了我在第一段中提到的问题。此外,我们不希望 URI 被重新重写,从而导致重写循环。因此,如果第二条规则已经应用,我们需要添加一个条件来停止重写,这意味着 URI 现在是 /public/index.php。以下是这些条件的组合:

# stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
# don't apply this rule if the first rule has been applied
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public/  [OR]
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^ /public/index.php [L]

Ok, this is going to be a little confusing to explain. The problem you are having is that when mod_rewrite rewrites something, without the [R] or [P], it redirects internally, and all the rewrite rules get applied again. This keeps happening until the rewritten uri is the same as the un-rewritten uri. So the first rule you have is getting rewritten by the second rule. You need to prevent that from happening.

First, let's look at the first rule. What you had is totally fine, except you need to add a condition for the caveat site.com/public/flower.jpg rerouted to public/index.php. This means if the request itself has a /public/ in it, it will not serve the request (and let the 2nd rule handle things). An additional caveat here is if you have a directory "public" inside "/public", as in DOCUMENT_ROOT/public/public/, it will be inaccessible.

# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule ^ /public%{REQUEST_URI} [L]

Here we've done the extra check for a request starting with something like GET /public/flower.jpg, if it matches, we skip this rule entirely. Also, this rule will break if you try to access a directory in /public/. For example, if you have a directory "stuff" inside "/public" and try to access it via the request site.com/stuff/, this rule will not allow you to see the contents (even if there is an index.html file in /stuff/) because you are not checking if directories exist. You can do that by adding this condition for -d, like so:

# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename/directory exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -d
RewriteRule ^ /public%{REQUEST_URI} [L]

The -d condition along with the [OR] of the -f says: if %{DOCUMENT_ROOT}/public%{REQUEST_URI} is a regular file OR a directory. (See the RewriteCond docs)

Now for the second rule, and this is going to look a bit confusing because we have to handle the negation of the first rule's conditions. If the first rule passes and the URI is rewritten, 2 things happen:

  1. The request doesn't start with something like: GET /public/
  2. The uri got rewritten to "/public/[something]"

So we'll have 2 conditions to deal with that. If the first rule rewrote the URI, we don't want to touch it again. This solves the problem that I mentioned in the first paragraph. Additionally, we don't want to URI to get re-rewritten, causing a rewrite loop. So we need to add a condition to stop rewriting if the 2nd rule has already been applied, which means the URI is now /public/index.php. Here are the combination of those conditions:

# stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
# don't apply this rule if the first rule has been applied
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public/  [OR]
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^ /public/index.php [L]
随风而去 2024-11-04 05:45:12

这可能有效:

RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php

优化版本也可能有效,但我不确定:

RewriteCond %{DOCUMENT_ROOT}(/public|public|)%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php

顺便说一句,你的逻辑很奇怪:以下规则:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php

意思是:“如果请求不是文件,则重写为 public/index.php”。问题来了:如果是一个文件,那是怎么回事?没有什么。 RewriteRule 被忽略。这是不安全的,想象一下如果这是一个您可能不希望用户访问的文件?去掉这个规则就行了,没啥用,没有它就更安全了(从我的角度来看)。


请问优化后的版本是否有效?


请尝试使用RewriteLog指令:它可以帮助您追踪此类问题:

# Trace:
# (!) file gets big quickly, remove in prod environments:
RewriteLog "/web/logs/mywebsite.rewrite.log"
RewriteLogLevel 9
RewriteEngine On

告诉我它是否有效。

This may work:

RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php

The optimized version may work too but I'm not sure:

RewriteCond %{DOCUMENT_ROOT}(/public|public|)%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php

By the way your logic is weird: the following rule:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php

Means: "if the request is not a file, rewrite to public/index.php". The problem is here: if it's a file, what's going on? Nothing. The RewriteRule is ignored. This is not safe, imagine if it's a file that you may not want the user to access? Just remove this rule, it's useless, and without it, it's safer (from my point of view).


May I ask you to tell me if the optimized version worked?


Please try to use the RewriteLog directive: it helps you to track down such problems:

# Trace:
# (!) file gets big quickly, remove in prod environments:
RewriteLog "/web/logs/mywebsite.rewrite.log"
RewriteLogLevel 9
RewriteEngine On

Tell me if it works.

脸赞 2024-11-04 05:45:12

我对你的第一组规则有点困惑,因为如果我没有弄错的话 %{REQUEST_URI} 将是 /public/flower.jpg 。我会这样做:

RewriteCond public/%{REQUEST_FILENAME} -f
RewriteRule ^.*$ public/%{REQUEST_FILENAME} [L] 

RewriteCond public/%{REQUEST_FILENAME} !-f
RewriteRule ^.*$ public/index.php [L]

我不确定 %{REQUEST_FILENAME} 为空时的行为,但基本上规则说:

如果文件名公开存在,则将所有 URI 重写到该文件,如果它不重写到索引.php

这对你有用吗?

I'm a bit confused with your first set of rules, since %{REQUEST_URI} would be /public/flower.jpg if I'm not mistaking. I would have done it this way :

RewriteCond public/%{REQUEST_FILENAME} -f
RewriteRule ^.*$ public/%{REQUEST_FILENAME} [L] 

RewriteCond public/%{REQUEST_FILENAME} !-f
RewriteRule ^.*$ public/index.php [L]

I'm not sure of the behaviour if %{REQUEST_FILENAME} is empty but basically the rules says:

If the filename exists in public, rewrite all URI to that file, if it does not rewrite to index.php

Would that work for you?

趁微风不噪 2024-11-04 05:45:12

您是否考虑过以编程方式创建 .htaccess 文件,以将不在您用于创建它的任何文件中设置的白名单中的任何内容列入黑名单?如果你问我,你会发现再简单不过了。

Have you considered programmatically creating your .htaccess file to blacklist anything that isn't on a whitelist that you set in whatever file you use to create it? If you ask me, you can't get much simpler.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文