Deny direct access to files or directory using .htaccess

15,864

Solution 1

If I've understood your requirements correctly, you're looking to do something like this:

# This is a real directory...
RewriteCond %{REQUEST_FILENAME} -f [OR]
# Or it's a real file...
RewriteCond %{REQUEST_FILENAME} -d
# And it's not 404.php...
RewriteCond $0 !=404.php
# And it's not the root
RewriteCond $0 !=""
# And it's not any of the above due to an internal redirect...
RewriteCond %{ENV:REDIRECT_STATUS} ^$
# So cause a 404 response (you could redirect to 404.php if you want)
RewriteRule ^.*$ - [R=404,L]

# Set the 404 error document
ErrorDocument 404 /~my_user/my_base/404.php

Keep in mind that this blocks everything that exists, so any images, stylesheets, or scripts will be sent to the 404 page too. If you just want to block access to the PHP files, Gumbo's solution is more appropriate. I think in that case you'll need another RewriteCond though to prevent looping:

# Make sure the reason this request has a .php is because it was requested
# by the user (and not due to a redirect)
RewriteCond %{THE_REQUEST} ^[A-Z]+\s/[^\s]+\.php
# Make sure we aren't on 404.php already
RewriteRule %{REQUEST_URI} !404\.php$
# We aren't, so redirect to 404.php
RewriteRule ^ 404.php [L]

Solution 2

Try this rule:

RewriteCond %{THE_REQUEST} ^[A-Z]+\ /[^?\ ]*\.php[/?\ ]
RewriteRule .*\.php$ 404.php [L]

This will rewrite all requests whose paths contain a .php internally to 404.php.

Share:
15,864
dierre
Author by

dierre

I'm learning.

Updated on June 18, 2022

Comments

  • dierre
    dierre almost 2 years

    I'm playing with .htaccess and I was wondering if with just an .htaccess inside the root directory is possible to block all the request from a browser directed on existing files or directories.

    Let's try this example:

    RewriteEngine On
    
    RewriteBase /~my_user/my_base/
    
    RewriteRule ^list/$ list.php [L]
    RewriteRule ^element_of_list/([a-zA-Z0-9\-]+)/$ element.php?elem_id=$1 [L]
    

    Now, if I write http://127.0.0.1/~my_user/my_base/list/, this is wroking fine but if I write http://127.0.0.1/~my_user/my_base/list.php it's still working. I don't want that. I want the user to obtain a 404 error in the last case.

    We have /etc/apache2/mods-enabled/userdir.conf

    <IfModule mod_userdir.c>
            UserDir public_html
            UserDir disabled root
    
            <Directory /home/*/public_html>
                    AllowOverride All
                    Options Indexes FollowSymLinks
                    <Limit GET POST OPTIONS>
                            Order allow,deny
                            Allow from all
                    </Limit>
                    <LimitExcept GET POST OPTIONS>
                            Order deny,allow
                            Deny from all
                    </LimitExcept>
            </Directory>
    </IfModule>
    

    My first try was to use RewriteCond:

    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^(.*)$ 404.php [L]
    

    But it's not working. Every request ends up redirected to 404.php

    UPDATE

    So I've managed to create the filter for directories:

    RewriteCond %{REQUEST_FILENAME} -d
    RewriteCond %{REQUEST_URI} !^/~my_user/my_base/$ [NC]
    RewriteRule ^(.*)$ 404.php [L]
    

    What it does is to check if the requested path (REQUEST_FILENAME) exists and it's a directory AND if it's not my RewriteBase which is basically index.php, then redirect to 404.php

    I'm still trying to find something that does the same thing for files. I know I can selectively do that using extensions filename but I want an universal filter for files.

  • dierre
    dierre over 13 years
    That's ok, but what about directory? Is it possible to do it without using a php file to check them? The -d flag is quite weird. I think I'm not getting it.
  • dierre
    dierre over 13 years
    Actually, why do you need the RewriteCond? What about RewriteRule ^(.*)\.php$ 404.php [L]
  • dierre
    dierre over 13 years
    Yeah I want to block everything but just out of curiosity. Could you explain me the use of $0 in there? Does it contain ${REQUEST_FILENAME}? And you chained 5 RewriteCond, how does apache understand you need to confront the last three with the result of the first two? Does it just evaluate the first two before everything (in order of appearance)?
  • Tim Stone
    Tim Stone over 13 years
    @dierre: The $0 is a backreference to the RewriteRule test pattern (it contains the entire match from ^.*$). It would have been possible to condense those two RewriteCond into the test pattern of the RewriteRule, I suppose, like !^(|404\.php)$. As for the chaining, there is an implicit AND that joins all RewriteCond together, unless you specifiy the [OR] flag.
  • Tim Stone
    Tim Stone over 13 years
    Relatedly, mod_rewrite processes the test pattern of the RewriteRule first, then tests each RewriteCond that is attached to the current rule (directly above it), starting at the top and working downwards. If all of that passes, then it performs the substitution in the RewriteRule.
  • dierre
    dierre over 13 years
    I was thinking, you use in ErrorDocument the full path of RewriteBase, is there any env:variable containing the value of RewriteBase?
  • Tim Stone
    Tim Stone over 13 years
    @dierre: Not that I'm aware of. There's also not any way to have ErrorDocument use the file relative to the current directory, as far as I know. In that case, it might be better to just redirect to 404.php directly, like the RewriteRule in the second example, since then you wouldn't need to write the whole path again. Just be sure that 404.php returns a 404 status header in that case.