19

I have a directory where users can upload files.

To avoid security issues (e.g. somebody uploading a malicious php script), I currently change the files' extension by appending .data for example, but then when downloading the file, they have to manually remove the .data.

Another common solution is to upload the files in a directory that is not served by Apache, and have a php script manage all downloads by calling readfile().

What I'd like to do is to simply disallow execution of any scripts (php, perl, cgi scripts, whatever I may install in the future) in the upload folder. This SO answer suggests adding the following line in a .htaccess file in that folder:

SetHandler default-handler

However, in my case this has no effect (the example php script I put in that folder is still executed). What am I doing wrong?

Apache configuration

The machine is a VPS (Virtual Private Server) running Debian GNU/Linux 6.0.7 (squeeze), and as far as I can remember (I note down all commands I run on that server, so my "memory" should be pretty accurate), I dindn't change anything in apache2 configuration, appart from running sudo apt-get install php5, and creating the the file /etc/apache2/sites-enabled/mysite.com with the following contents:

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  ServerName  mysite.com
  ServerAlias www.mysite.com

  DocumentRoot /home/me/www/mysite.com/www/
  <Directory />
    Options FollowSymLinks
    AllowOverride All 
  </Directory>
  <Directory /home/me/www/mysite.com/www/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All 
    Order allow,deny
    allow from All
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log

  # Possible values include: debug, info, notice, warn, error, crit,
  # alert, emerg.
  LogLevel warn

  CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Community
  • 1
  • 1
Suzanne Soy
  • 3,027
  • 6
  • 38
  • 56
  • `SetHandler default-handler` works well to prevent execution of PHP code in a subdir. Make sure to put this as first line in `DOCUMENT_ROOTsubdir/.htaccess` – anubhava Sep 21 '13 at 14:35
  • @anubhava Thank you for your reply. In `/etc/apache2/sites-enabled/mysite.com`, my `DocumentRoot` is set to `/home/me/www`, and the file `/home/me/www/subdir/.htaccess` contains a single line `SetHandler default-handler`, and is readable by `www-data` (I checked using `sudo su www-data; whoami; cat /home/me/www/subdir/.htaccess`, so I suspect the problem has something to do with `AllowOverride` or something like that. – Suzanne Soy Sep 22 '13 at 15:08
  • 1
    Please make sure .htaccess is enabled by putting some garbage text in it and see if it generates 500 error or not. – anubhava Sep 22 '13 at 15:11
  • @anubhava Yes, I get an `Internal Server Error` when I put garbage in the `.htaccess`, thanks for the tip, I didn't know how to check if it was enabled or not. I tried setting `AllowOverride All` in `/etc/apache2/sites-enabled/mysite.com`, and now I get a weird behaviour: when accessing `http://mysite.com/subdir/` I get a `404 Not Found` error, but accessing `http://mysite.com/subdir/script.php` still runs the php script. – Suzanne Soy Sep 22 '13 at 15:19
  • I tried various options (directly in `/etc/apache2/sites-enabled/mysite.com`), and found that the only thing that disabled PHP scripts was to put `php_flag engine off`, not even `RemoveHandler .php .php3 .php4 .php5 .phtml` worked. That means to me that PHP seems to short-circuit `SetHandler` directives. Unfortunately, that means that if some day I install another CGI engine, e.g. `perl`, then I'll need to remember to disable it too, so using `php_flag engine off` is absolutely **not** a robust way to disable execution of scrits for a folder. – Suzanne Soy Sep 22 '13 at 15:40
  • Yes I agree `SetHandler` is definitely the more robust way to fix this. I tried that it worked for me, not sure why it is not working for you. – anubhava Sep 22 '13 at 16:18
  • Can you put these 2 lines one after another: `SetHandler None` and then `SetHandler default-handler` – anubhava Sep 22 '13 at 16:23
  • @anubhava Yes, I tried that too, and also tried putting both lines in a `` directive in `/etc/apache2/sites-enabled/mysite.com`, But it still didn't work. I finally found the solution: the directives were applied to the directory itself (so that's why I got a `404` when accessing the directory) and not to the files within, so I had to write `SetHandler default-handler`, and in that case the directive is applied to the folder itself, the files within, and any subfolders, recursively. Thanks for all the help! – Suzanne Soy Sep 22 '13 at 19:59
  • That's great, glad that it all worked out. +1 to your question and answer :) – anubhava Sep 23 '13 at 02:37

2 Answers2

34

Put this in your .htaccess:

<Files *>
    # @mivk mentionned in the comments that this may break
    # directory indexes generated by Options +Indexes.
    SetHandler default-handler
</Files>

But this has a few security holes: one can upload a .htaccess in a subdirectory, and override these settings, and they might also overwrite the .htaccess file itself!

If you're paranoid that the behaviour of the option should change in the future, put this in your /etc/apache2/sites-enabled/mysite.com

    <Directory /home/me/www/upload/>
            # Important for security, prevents someone from
            # uploading a malicious .htaccess
            AllowOverride None

            SetHandler none
            SetHandler default-handler

            Options -ExecCGI
            php_flag engine off
            RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
            <Files *>
                    AllowOverride None

                    SetHandler none
                    SetHandler default-handler

                    Options -ExecCGI
                    php_flag engine off
                    RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
            </Files>
    </Directory>

If you can't modify the apache configuration, then put the files in a .htaccess with the following directory structure:

/home/me/www/
          |- myuploadscript.php
          |- protected/
              |- .htaccess
              |- upload/
                  |- Uploaded files go here

That way, nobody should be able to overwrite your .../protected/.htaccess file since their uploads go in a subdirectory of .../protected, not in protected itself.

AFAICT, you should be pretty safe with that.

Suzanne Soy
  • 3,027
  • 6
  • 38
  • 56
  • Good idea. A more simple solution would be to make .htaccess only readable for all. Off cause that will not work in all scenarios depending on who and how .htaccess files are edited. – Tillebeck Mar 14 '14 at 21:57
  • 4
    @Tilebeck While having a `chmod 444` (read-only) on the file will prevent modifying the file directly, it won't prevent replacing the file (deleting it and creating a new one), as [only the directory's permissions are involved in that operation](http://unix.stackexchange.com/a/48580/19059), so I wouldn't recommend relying on read-only files if you don't want their contents changed. – Suzanne Soy Mar 16 '14 at 21:46
  • 1
    Using "SetHandler default-handler" breaks directory indexes ("Options +Indexes") in some cases. Took me a while to narrow it down to that, so I mention it if anyone comes across the same problem. I was getting a 404 error, and log only said "[error] [client ...] Attempt to serve directory: /...". – mivk May 17 '15 at 20:07
2

My Godaddy setup wont allow me to edit the httpd.conf files, and the php_flag command doesn't work due to how they've implemented php for me.

I was able to use this in my .htaccess file:

SetHandler default-handler
AddType text/plain php

I put this in the directory above where my FTP user is allowed to access, which forces all PHP files in that directory, as well as all sub-directories to show php as plain text.

This will work for other file types as well. All you need to do is add another line with whatever extension of file you want to be forced to display in plain text. AddType text/plain cgi for example

Taylor
  • 61
  • 5
  • 1
    Take care of protecting your `.htaccess` file, though, so that one can't upload a file named `.htaccess` and overwrite yours. See the end of my answer for how to do that without access to `httpd.conf`. – Suzanne Soy Dec 06 '15 at 12:29
  • 1
    Seems I overlooked the end of your post. I don't think I quite understood it until I implemented it myself. Your suggestion is what I happen to be doing. htaccess is in "/protected", while the FTP user only has access to "/protected/user." I suppose I should test that an additional htaccess file in the users subdirectory will not overwrite the top levels restrictions. I would assume it wouldn't... But I don't know everything, and assuming has gotten me in trouble before. – Taylor Dec 07 '15 at 16:47
  • I think the `AllowOverride None` should take care of that, but I'm not 100% sure if it can't be overridden in any way. You're right, though, a hardened setup would require thinking it through and double-checking this kind of failure points. – Suzanne Soy Mar 02 '16 at 16:53