Forum

Drupal security and configuration for Image Styles

Joe Schmoe
18 January 2016, 17:51
While the URLToolkit rewrite rules are helpful for creating a working Drupal website, they do not include additional security features that have been added to Drupal through .htaccess files.

The main .htaccess files are:
/.htaccess - Remove access to files with certain extensions and rewrite the URL.
/sites/default/files/.htaccess - Turn off php engine so uploaded files cannot be executed.
/sites/default/files/private/.htaccess - Deny access since file access will be controlled internally by Drupal.

I believe I have recreated these security considerations in the hiawatha.conf file (see below).

However, now I am having a problem with image styles, which is a feature in Drupal where an image file is requested (that may not yet exist) and Drupal will apply image processing based on the URL.

The original file might be uploaded to:

/sites/default/files/banner/welcome.png

Then if a request is made for the following file Drupal will automatically generate a processed file (which might be cropped, scaled down, rotated, etc):

/sites/default/files/styles/thumbnail/public/banners/welcome.png

Even though the request should go through a rewrite which should be processed by Drupal (and the index.php), it appears because ExecuteCGI is turned off for the original file (based on DirectoryID) then the index.php script is not run.

If I turn off the UseDirectory directive below then the images are generated properly but I lose the security since uploaded files with a PHP extension may still be executed.

Thank you for your help!


=======

Here are the relevant configurations in hiawatha.conf:
# Files in the following directory should never be executed since they may have been uploaded by an unknown user.
Directory {
DirectoryID = public_files
Path = /sites/default/files
ExecuteCGI = no
}

# Files in the private directory should not be accessible since Drupal controls access.
Directory {
DirectoryID = private_files
Path = /sites/default/files/private
ExecuteCGI = no
AccessList = deny all
}

# Deny access to files with specific extensions to match .htaccess restrictions.
UrlToolkit {
ToolkitID = Drupal
Match \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$ DenyAccess
RequestURI isfile Return
Match ^/favicon.ico$ Return
Match /(.*)\?(.*) Rewrite /index.php?q=$1&$2
Match /(.*) Rewrite /index.php?q=$1
}

VirtualHost {
Hostname = www.example.com
WebsiteRoot = /home/example/html
AccessLogfile = /home/example/logs/access.log
ErrorLogfile = /home/example/logs/error.log
TimeForCGI = 5
UseFastCGI = PHP5
UseToolkit = Drupal
StartFile = index.php
ExecuteCGI = yes
UseDirectory = public_files, private_files
}

Additional links to Drupal .htaccess files for security considerations:

http://cgit.drupalcode.org/drupal/tree/.htaccess?h=7.x (See line 6)
https://www.drupal.org/SA-CORE-2013-003#cve-identifiers-issued (See section named "For Drupal 7")

Additionally, the .htaccess of the temporary files directory and private files directory (if used) should include this command:
Deny from all
Joe Schmoe
18 January 2016, 18:18
For now I have removed the UseDirectory directive and combined the configurations into the Drupal URLToolkit which I think solves my problem.
UrlToolkit {
ToolkitID = Drupal
Match /sites/default/files/private DenyAccess
Match /sites/default/files/(.*).php DenyAccess
Match \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$ DenyAccess
RequestURI isfile Return
Match ^/favicon.ico$ Return
Match /(.*)\?(.*) Rewrite /index.php?q=$1&$2
Match /(.*) Rewrite /index.php?q=$1
}
Hugo Leisink
18 January 2016, 20:26
I don't get it. Files in /sites/default/files should not be executed, but the file /sites/default/files/banner/welcome.png, which is located within the protected directory, should invoke PHP execution...
Joe Schmoe
18 January 2016, 21:41
Short answer, yes, once the URL has been rewritten based on the URLToolkit. Execution should be invoked on the rewritten URL, not on the requested URL.

1: UrlToolkit {
2: ToolkitID = Drupal
3: Match /sites/default/files/private DenyAccess
4: Match /sites/default/files/(.*).php DenyAccess
5: Match \.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$ DenyAccess
6: RequestURI isfile Return
7: Match ^/favicon.ico$ Return
8: Match /(.*)\?(.*) Rewrite /index.php?q=$1&$2
9: Match /(.*) Rewrite /index.php?q=$1
10: }


Forgive me for being verbose, but just so I understand how things work, if a requests comes in for the following url:

http://www.example.com/sites/default/files/styles/thumbnail/public/banners/welcome.png

If will try the following lines in the Drupal URLToolkit above:

Line 3: Does not match since URL does not contain "private"
Line 4: Does not match since URL does not end with the ".php" extension
Line 5: Does not match any of the listed extensions
Line 6: Checks to see if file exits, and /INITIALLY/ it does not.
Line 7: No match for favicon.ico
Line 8: Does not contain "?"
Line 9: It DOES match (or at least it should) and rewrites the URL (verified with wigwam) as:

/index.php?q=sites/default/files/styles/thumbnail/public/banners/welcome.png

Now, this is where Drupal steps in and recognizes that based on a fragment of the URL ("files/styles" <- may not be the exact fragment, but you get the idea) it needs to generate a new image based on a file that already exists:

/sites/default/files/banner/welcome.png <- uploaded previously

Drupal will then generate the requested file and save it at the appropriate location. All subsequent requests to the original URL will now stop at Line 6 above because the file now exists. No need for ExecuteCGI since it is just returning the file.

Anyway, just a guess, but it seems that somewhere along the way it sets ExecuteCGI = no too early in the process based on the original URL, not on the rewritten URL.

Hopefully that is clearer?

Thanks again!

Hugo Leisink
18 January 2016, 21:49
Got it. But this construction makes all kinds of security bells ringing in my head.
Joe Schmoe
18 January 2016, 22:01
I'm guessing that saying that "Everyone else does it that way!" would not make you feel any better.

As it is now, ExecuteCGI is turned on for all files in /sites/default/files which is where user uploaded files are stored. So I need to make sure to limit anything with a .php extension from being uploaded. And as long as I hopefully have the right regex to DenyAccess to files with the .php extension I should be fine?

Line 4 from Drupal URLToolkit above:

Match /sites/default/files/(.*)\.php DenyAccess


Hopefully the result of this thread is a more secure configuration for Drupal that can be used as an example on the following page:

https://www.hiawatha-webserver.org/howto/url_rewrite_rules

Really appreciate all your work on Hiawatha!
This topic has been closed.