最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

php - .htaccess rules for filesmatch to protect config files - Stack Overflow

programmeradmin6浏览0评论

I have a .htaccess file like so:

#protect config files
<FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^([^\.]+)$ $1.php [NC,L]
</IfModule>
<filesMatch "\.(html|css|js)$">
    AddDefaultCharset UTF-8
</filesMatch>
<filesMatch "\.(css|js)$">
    FileETag MTime Size
</filesMatch>
ErrorDocument 304 /error_pages/304.php   
ErrorDocument 400 /error_pages/400.php   
ErrorDocument 401 /error_pages/401.php   
ErrorDocument 403 /error_pages/403.php 
ErrorDocument 404 /error_pages/404.php    
ErrorDocument 409 /error_pages/409.php   
ErrorDocument 410 /error_pages/410.php   
ErrorDocument 500 /error_pages/500.php
<IfModule mod_expires.c>
    ExpiresActive On
    # Fonts
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    # Images
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/x-icon "access plus 6 year"
    # CSS, JavaScript
    ExpiresByType text/css "access plus 6 month"
    ExpiresByType application/javascript "access plus 6 month"
    ExpiresByType application/x-javascript "access plus 6 month"
    ExpiresByType application/x-shockwave-flash "access plus 6 month"
    # Other
    ExpiresByType application/pdf "access plus 6 month"
    ExpiresDefault "access plus 10 days"
</IfModule>
<IfModule mod_headers.c>
    # Jaar Public
    <FilesMatch "\.(jpg|jpeg|png|gif|svg|webp|ico|ttf|woff|woff2|JPG|JPEG|PNG|GIF|SVG|WEBP|ICO|TTF|WOFF|WOFF2)$">
        Header set Cache-Control "max-age=31536000, public"
    </FilesMatch>

    # Jaar
    <FilesMatch "\.(js|css|swf)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>
</IfModule>
# Disable directory browsing
Options All -Indexes

How can I still access json files in a subfolder? Some part of i18n is in a subfolder called assets and is now not loaded in because of the first directive. As I am a front-end person I don't know a lot about Apache. I also have problem with the JS routing from Vaadin (see: Vaadin/Router) On reload of a link within the page like so: /page/123 it doesn't load, instead returning a 404. When returning to /page it works as intended. I have found that /page.php/123 works as intended if configured so. Is it possible to hide the .php extension, but still request the page.php with the /123 URI?

I have a .htaccess file like so:

#protect config files
<FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^([^\.]+)$ $1.php [NC,L]
</IfModule>
<filesMatch "\.(html|css|js)$">
    AddDefaultCharset UTF-8
</filesMatch>
<filesMatch "\.(css|js)$">
    FileETag MTime Size
</filesMatch>
ErrorDocument 304 /error_pages/304.php   
ErrorDocument 400 /error_pages/400.php   
ErrorDocument 401 /error_pages/401.php   
ErrorDocument 403 /error_pages/403.php 
ErrorDocument 404 /error_pages/404.php    
ErrorDocument 409 /error_pages/409.php   
ErrorDocument 410 /error_pages/410.php   
ErrorDocument 500 /error_pages/500.php
<IfModule mod_expires.c>
    ExpiresActive On
    # Fonts
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    # Images
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/x-icon "access plus 6 year"
    # CSS, JavaScript
    ExpiresByType text/css "access plus 6 month"
    ExpiresByType application/javascript "access plus 6 month"
    ExpiresByType application/x-javascript "access plus 6 month"
    ExpiresByType application/x-shockwave-flash "access plus 6 month"
    # Other
    ExpiresByType application/pdf "access plus 6 month"
    ExpiresDefault "access plus 10 days"
</IfModule>
<IfModule mod_headers.c>
    # Jaar Public
    <FilesMatch "\.(jpg|jpeg|png|gif|svg|webp|ico|ttf|woff|woff2|JPG|JPEG|PNG|GIF|SVG|WEBP|ICO|TTF|WOFF|WOFF2)$">
        Header set Cache-Control "max-age=31536000, public"
    </FilesMatch>

    # Jaar
    <FilesMatch "\.(js|css|swf)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>
</IfModule>
# Disable directory browsing
Options All -Indexes

How can I still access json files in a subfolder? Some part of i18n is in a subfolder called assets and is now not loaded in because of the first directive. As I am a front-end person I don't know a lot about Apache. I also have problem with the JS routing from Vaadin (see: Vaadin/Router) On reload of a link within the page like so: /page/123 it doesn't load, instead returning a 404. When returning to /page it works as intended. I have found that /page.php/123 works as intended if configured so. Is it possible to hide the .php extension, but still request the page.php with the /123 URI?

Share Improve this question edited Mar 24 at 9:31 Sean asked Mar 21 at 10:37 SeanSean 213 bronze badges 8
  • "On reload of a link within the page like so: /page/123 it doesn't load, instead returning a 404." - You have no "routing" directives in your .htaccess file. (Except for an "incorrect" rewrite to .php files) How do you expect such a request to be handled (server-side)? You have other inconsistencies in your .htaccess file to do with caching. Is mod_headers installed (normally it is)? – MrWhite Commented Mar 21 at 11:07
  • "Is the RewriteRule overwriting the FilesMatch directive?" - shouldn't be, because the preceding RewriteCond makes it apply only if the requested URL does not match any existing file. Not sure why you wrapped the FilesMatch into <IfModule mod_headers.c> though - that directive is not provided by mod_headers, but part of Apache core. So try and remove that If-Wrapper, and see what happens then. – C3roe Commented Mar 21 at 11:08
  • But your other issue is the result of this rewrite: /page/123 gets requested, doesn't match an existing file - so you append .php to it, but I'm pretty sure /page/123.php won't actually exist in your system either. – C3roe Commented Mar 21 at 11:09
  • I edited the original question. Removed the IfModule directive that was interfering with the FilesMatch directive. Apparently the RewriteCond directive is necessary for the way other php scripts are requested (without the extension). For clarification I don't have access to the server itself. – Sean Commented Mar 21 at 13:51
  • You've removed too much... you've also removed the closing </FilesMatch> directive, which would effectively disable the remainder of your .htaccess file (if not an error). But did that resolve the issue of still being able to access .json files or not? Do you have any other .htaccess files (in subfolders)? "I have found that /page.php/123 works as intended" - But that's routing the request to a PHP script, I thought /page/123 was supposed to be handled by JS? – MrWhite Commented Mar 21 at 16:17
 |  Show 3 more comments

1 Answer 1

Reset to default 0
#protect config files
<FilesMatch "\.(env|json|lock|xml|yml|htaccess|gitignore|gitattributes)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>

The fact that this started working after removing the <IfModule mod_headers.c> container implies that mod_headers is not installed on your system*1. Which is a little unusual. (Although it made no sense to contain that block in such an <IfModule> directive anyway. In fact, you should generally avoid <IfModule> directives in your config, unless the contained rules are optional and the same config is intended to be used unaltered on many different systems. Otherwise, the <IfModule> directive simply masks the error.)

You are also mixing both Apache 2.2 (Order and Deny) directives with Apache 2.4 (Require all denied) directive. This is bad practice and can result in unexpected conflicts (not to mention that Order and Deny are formerly deprecated and not available by default on Apache 2.4). So, using this same config on other systems is likely to fail. Assuming you do not have other Order, Deny and/or Allow directives in your config (anywhere) then you should remove the Order and Deny directives here.

(*1 If mod_headers is not installed then the later section that sets some Cache-Control headers is also not processed. Although this is probably a good thing, since this section conflicts with the mod_expires section directly above it!?)

Json files are blocked, but then also in subfolders. I only want to block project configuration files in the root, for security reasons.

("for security reasons"?! Presumably it's because your application needs client-side access to .json files in subdirectories for some reason? Not a security thing, as allowing access can only be less secure?)

The <FilesMatch> directive matches against the filename only, not the file-path, so applies to all subdirectories (which is generally what you want in this scenario).

If you want to be able to access .json files in subdirectories then you need to remove |json from the above alternation section in the regex. And create a separate rule that blocks requests for .json files in the root only. For example:

# Block requests for ".json" files in the root only
<If "%{REQUEST_URI} =~ m#^/[^/]+\.json$#">
    Require all denied
</If>

This uses an Apache expression to examine the requested URL. Note that this blocks URLs, regardless of whether a .json file actually exists. (Which is actually the same as the <FilesMatch> directive.)

The regex ^/[^/]+\.json$ will only match /<something>.json and not /<something>/<something>.json etc.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]

For example the route { path: "/tickets.php/:id", component: "ticket-component" } works when the hotlinks are updated to tickets.php/4509 instead of tickets/4509

This simply appends the .php extension to any URL-path that does not contain a dot and that does not map to a file*2. (It should at least check that the target .php file exists.)

(*2 Note that this does not necessarily test the full URL-path since REQUEST_FILENAME is a calculated value based on your filesystem.)

With the above rule, if you request /tickets/4509 (and /tickets is not a physical subdirectory) then the RewriteRule directive first matches against tickets/4509 (which is saved in the $1 backreference). The preceding condition then tests if /tickets (not /tickets/4509) is not a physical file (which I assume it's not, so the condition is successful) so ends up rewriting the request to tickets/4509.php - which naturally fails with a 404.

Assuming all your relevant .php files exist in the document root only (not subdirectories) then you would need to write the rule something like the following instead to test for .php files in root and append the remaining URL-path as path-info (which seems to be what you are expecting).

For example:

# Rewrite requests of the form "/page/id" to "/page.php/id"
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^./]+)(/\d+)?$ $1.php$2 [L]

Note that I included a slash in the negated regex character class so it only matches the first URL-path segment, not the entire URL-path.

I'm assuming (based on your examples) that only numeric IDs are being passed. eg. /<page>/<numeric-id>. But I've allowed for the /<numeric-id> part to be entirely optional, so /tickets only could be requested and is simply rewritten to /tickets.php.

The preceding condition (RewriteCond directive) now checks that the corresponding .php file actually exists before attempting to rewrite the request.

No need to backslash-escape literal dots when used inside a regex character class. And the NC flag is superfluous, since the regex itself is not case-specific.

Summary

So, bringing the above points together, your complete .htaccess file would now look like this:

# Disable directory browsing
Options All -Indexes

# Block requests for ".json" files in the root only
<If "%{REQUEST_URI} =~ m#^/[^/]+\.json$#">
    Require all denied
</If>

# Protect other config files (anywhere)
<FilesMatch "\.(env|lock|xml|yml|htaccess|gitignore|gitattributes)$">
    Require all denied
</FilesMatch>

<FilesMatch "\.(html|css|js)$">
    AddDefaultCharset UTF-8
</FilesMatch>
<FilesMatch "\.(css|js)$">
    FileETag MTime Size
</FilesMatch>

ErrorDocument 304 /error_pages/304.php   
ErrorDocument 400 /error_pages/400.php   
ErrorDocument 401 /error_pages/401.php   
ErrorDocument 403 /error_pages/403.php 
ErrorDocument 404 /error_pages/404.php    
ErrorDocument 409 /error_pages/409.php   
ErrorDocument 410 /error_pages/410.php   
ErrorDocument 500 /error_pages/500.php

<IfModule mod_expires.c>
    ExpiresActive On
    # Fonts
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    # Images
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/x-icon "access plus 6 year"
    # CSS, JavaScript
    ExpiresByType text/css "access plus 6 month"
    ExpiresByType application/javascript "access plus 6 month"
    ExpiresByType application/x-javascript "access plus 6 month"
    ExpiresByType application/x-shockwave-flash "access plus 6 month"
    # Other
    ExpiresByType application/pdf "access plus 6 month"
    ExpiresDefault "access plus 10 days"
</IfModule>

<IfModule mod_rewrite.c>
    RewriteEngine On

    # Rewrite requests of the form "/page/id" to "/page.php/id"
    RewriteCond %{DOCUMENT_ROOT}/$1.php -f
    RewriteRule ^([^./]+)(/\d+)?$ $1.php$2 [L]
</IfModule>

I also adjusted the logical order of the rule blocks, which helps readability and maintenance (although this does not strictly affect functionality).

Ordinarily, the <IfModule mod_rewrite.c> wrapper should probably be removed, although your system is technically usable without this section, providing you use (the less pretty) URLs of the form /page.php/<id>.

Although this is still strictly not complete as URLs of the form /page.php/<id> are still accessible, potentially creating a duplicate content issue (whether this would become a real problem for your site is another matter). You could implement this as a canonical redirect in .htaccess (or your PHP code) or simply block such requests with a 404. This is left as an exercise for the reader.

发布评论

评论列表(0)

  1. 暂无评论