fix-permissions.sh

Public

Copy the code above to a file, name it "fix-permissions.sh" and run it as follows:
sudo bash fix-permissions.sh --drupal_path=your/drupal/path --drupal_user=your_user_name

Note: The server group name is assumed "www-data", if it differs use the --httpd_group=GROUP argument.

If you have sufficient privileges on your server

Place the file in /usr/local/bin
sudo chown root:root /usr/local/bin/fix-permissions.sh
sudo vi /etc/sudoers.d/fix-permissions and enter the following line in the file
user1, user2 ALL = (root) NOPASSWD: /usr/local/bin/fix-permissions.sh
Save the file and then sudo chmod 0440 /etc/sudoers.d/fix-permissions
Note: Substitute your desired comma separated list of users where you see user1, user2 above. Alternatively, you could enter an ALIAS for a user list. Run man sudoers for more information on formatting the line.

What the /etc/sudoers.d/fix-permissions accomplishes is making the script available to a set of users via the sudo command without having to enter a password.

Assuming that /usr/local/bin is in the user's path (and it should be), then the script can be run from anywhere using

sudo fix-permissions.sh \
--drupal_path=/path/to/the/drupal/install \
--drupal_user=your_desired_user
.

Note: The "\" is being used here to help avoid bad line breaks in the display of the page.

</> CopyGet raw version
bash
  1. #!/bin/bash
  2. # Help menu
  3. print_help() {
  4. cat <<-HELP
  5. This script is used to fix permissions of a Drupal installation
  6. you need to provide the following arguments:
  7.  
  8.  
  9.   1) Path to your Drupal installation.
  10.   2) Username of the user that you want to give files/directories ownership.
  11.   3) HTTPD group name (defaults to www-data for Apache).
  12.  
  13.  
  14. Usage: (sudo) bash ${0##*/} --drupal_path=PATH --drupal_user=USER --httpd_group=GROUP
  15. Example: (sudo) bash ${0##*/} --drupal_path=/usr/local/apache2/htdocs --drupal_user=john --httpd_group=www-data
  16. HELP
  17. exit 0
  18. }
  19.  
  20.  
  21. if [ $(id -u) != 0 ]; then
  22. printf "**************************************\n"
  23. printf "* Error: You must run this with sudo or root*\n"
  24. printf "**************************************\n"
  25. print_help
  26. exit 1
  27. fi
  28.  
  29.  
  30. drupal_path=${1%/}
  31. drupal_user=${2}
  32. httpd_group="${3:-www-data}"
  33.  
  34.  
  35. # Parse Command Line Arguments
  36. while [ "$#" -gt 0 ]; do
  37. case "$1" in
  38. --drupal_path=*)
  39. drupal_path="${1#*=}"
  40. ;;
  41. --drupal_user=*)
  42. drupal_user="${1#*=}"
  43. ;;
  44. --httpd_group=*)
  45. httpd_group="${1#*=}"
  46. ;;
  47. --help) print_help;;
  48. *)
  49. printf "***********************************************************\n"
  50. printf "* Error: Invalid argument, run --help for valid arguments. *\n"
  51. printf "***********************************************************\n"
  52. exit 1
  53. esac
  54. shift
  55. done
  56.  
  57.  
  58. if [ -z "${drupal_path}" ] || [ ! -d "${drupal_path}/sites" ] || [ ! -f "${drupal_path}/core/modules/system/system.module" ] && [ ! -f "${drupal_path}/modules/system/system.module" ]; then
  59. printf "*********************************************\n"
  60. printf "* Error: Please provide a valid Drupal path. *\n"
  61. printf "*********************************************\n"
  62. print_help
  63. exit 1
  64. fi
  65.  
  66.  
  67. if [ -z "${drupal_user}" ] || [[ $(id -un "${drupal_user}" 2> /dev/null) != "${drupal_user}" ]]; then
  68. printf "*************************************\n"
  69. printf "* Error: Please provide a valid user. *\n"
  70. printf "*************************************\n"
  71. print_help
  72. exit 1
  73. fi
  74.  
  75.  
  76. cd $drupal_path
  77. printf "Changing ownership of all contents of "${drupal_path}":\n user => "${drupal_user}" \t group => "${httpd_group}"\n"
  78. chown -R ${drupal_user}:${httpd_group} .
  79.  
  80.  
  81. printf "Changing permissions of all directories inside "${drupal_path}" to "rwxr-x---"...\n"
  82. find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
  83.  
  84.  
  85. printf "Changing permissions of all files inside "${drupal_path}" to "rw-r-----"...\n"
  86. find . -type f -exec chmod u=rw,g=r,o= '{}' \;
  87.  
  88.  
  89. printf "Changing permissions of "files" directories in "${drupal_path}/sites" to "rwxrwx---"...\n"
  90. cd sites
  91. find . -type d -name files -exec chmod ug=rwx,o= '{}' \;
  92.  
  93.  
  94. printf "Changing permissions of all files inside all "files" directories in "${drupal_path}/sites" to "rw-rw----"...\n"
  95. printf "Changing permissions of all directories inside all "files" directories in "${drupal_path}/sites" to "rwxrwx---"...\n"
  96. for x in ./*/files; do
  97. find ${x} -type d -exec chmod ug=rwx,o= '{}' \;
  98. find ${x} -type f -exec chmod ug=rw,o= '{}' \;
  99. done
  100. echo "Done setting proper permissions on files and directories"

Securing file permissions and ownership

Last updated July 1, 2015. Created on April 10, 2008.
Edited by likewhoa, kenorb, generalconsensus, vegantriathlete. Log in to edit this page.

The server file system should be configured so that the web server (e.g. Apache) does not have permission to edit or write the files which it then executes. That is, all of your files should be 'read only' for the Apache process, and owned with write permissions by a separate user.

As a quick test to confirm whether your site is secure or not you can run the Security Review module. However, this module may from time to time report false positives. To avoid being confused by these you need to check the module's issue queue to see if there any open issues that is about false positives.

Note that this whole article is about "defense in depth." Drupal can run quite safely with permissions a little "looser" than they should be. But if an administrator account is compromised by an attacker or an attacker gains the ability to execute arbitrary code then the configuration below will limit their ability to further exploit your site.

Configuration examples

For example, on many systems the Apache process runs as a user called "www-data" that is in a group called "www-data". This user should be able to read all of the files in your Drupal directory either by group permissions or by "other" permissions. It should not have write permissions to the code in your Drupal directory. If you use features of Drupal which require the "files" directory, then give the www-data user the permission to write files only in that directory.

The following is an example file listing of a safe configuration showing two files in a site where uploaded files are stored in the "files" directory. In order to see the file permissions set for your setup, go to the command line and type: ls -al.

drwxrwx--- 7 www-data greg-group 4096 2008-01-18 11:02 files/
drwxr-x--- 32 greg-user www-data 4096 2008-01-18 11:48 modules/
-rw-r----- 1 greg-user www-data 873 2007-11-13 15:35 index.php
In the above example, the web server user has the ability to write in the files directory, any users in the group "greg" can read and write the data as well, but other users are not allowed to interact with that data. The "index.php" file (representative of all code files) can be edited by "greg" and can be read by the www-data group (we assume the www-data user is in the www-data group). No other users can read that file. This is a fairly secure method of configuring your site. You generally don't want random users who have the ability to read files on your server to see inside those files, hence the last three permissions are --- instead of r-x.

Below is an insecure setup:

NOTE: THIS IS AN INSECURE CONFIGURATION
drwxrwx--- 7 www-data www-data 4096 2008-01-18 11:02 files/
drwxrwx--- 32 greg-user www-data 4096 2008-05-16 11:48 modules/
-rw-rw-rw- 1 www-data www-data 873 2007-11-13 15:35 index.php
This configuration allows the www-data user to edit the index.php file (and since it is representative of other files, we assume every file that makes up the Drupal site). This configuration is dangerous for the reasons outlined below. If you are using a configuration like this, please change it immediately to be more like the first version. Instructions for making the necessary changes are given below for Linux Servers.

Quick lesson in permission's numeric equivalents

On Unix-like servers (including Linux), permissions for files and directories can be specified using letters (e.g., "r" for read, "w" for write, and "x" for execute bits) or numbers. The numbers 4, 2, and 1 are conversions of these bits from binary, and correspond directly to read, write, and execute. The instructions below use the alphabetic notation because it is easier to read, but you often will see permissions given in numeric notation in the Drupal Handbook and many other How-Tos. Below are examples of the alphabetic notation and the numeric equivalent:

rwx == 111 binary == 7
r-- == 100 binary == 4
--- == 000 binary == 0
Unix filesystems assign permissions to three different categories of possible users -- owners, members of a security group defined on the server (e.g., using the groupadd command in Linux, the pw command in FreeBSD or the dseditgroup command in OSX), and everyone else. Each of these categories is assigned permissions using the alphabetic or numeric notation explained above via the chmod command.

The permissions for a file or directory can be displayed using the ls -l command. The display shows file or directory permissions in the first column in alphabetic notation. The first character indicates the type of directory item, with a "d" whether the file system entity is a directory; a "-" is shown if the entity is a file. The next three letters show the permissions for the user-owner of the file or directory. The next three letters show permissions for the group-owner of the file or directory. The last three letters show the permissions for everyone who is not the user-owner or a member of the group that owns the file or directory. For example:

drwxr-x--- 10 joe www-data 4096 Oct 15 14:15 ./
drwxr-xr-x 13 root root 4096 Oct 11 14:50 ../
-rw-r----- 1 joe www-data 6553 Aug 1 12:27 authorize.php
-rw-r----- 1 joe www-data 70700 Aug 1 12:27 CHANGELOG.txt
-rw-r----- 1 joe www-data 5267 Oct 12 22:47 .htaccess
drwxr-x--- 4 joe www-data 4096 Oct 15 14:23 includes/
-rw-r----- 1 joe www-data 529 Aug 1 12:27 index.php
So, in the listing above, the index.php file is owned by the user joe and the group www-data (usually the name of the Apache user and group on Debian-based systems). The permissions allow joe to read and write the file but allow users in the www-data group only to read the file (except joe, of course, who has owner permissions).

Addendum on chmod Non-Numeric Permission Notation
When used in the chmod command, the symbols below have the meanings given:

"+" = add a permission to the ones already assigned
"-" = revoke a given permission maintaining the others already assigned
"=" = ignores the already assigned permissions and assigns the permissions specified
"u" = user
"g" = group
"o" = others
"a" = everybody / all (user, group, others)

For files:

r = read
w = write
x = execute

For directories:
r = list (read directory contents)
w = write
x = can access the directory (i.e., cd to the directory)

Using chmod without numeric values makes it more human-readable. These are some examples of how to use it:

chmod commands and results for a file with permissions rwxrwx--- (770)
chmod human chmod numeric resulting permission
ugo=rwx 777 rwxrwxrwx
u-wx 470 r--rwx---
o+r 774 rwxrwxr--
g-wx,o+r 744 rwxr--r--
u-w,g-wx,o+r 544 r-xr--r--
g=,o=r 704 rwx---r--
a-wx 440 r--r-----

Note: Because of the way Unix filesystems work, all files and directories created by the Apache server will be created with an owner that is the same user as is running httpd. In Linux, you can handle this with a useful tool called fsniper. It uses iNotify to check for newly created/modified directories and files and you can apply whatever action you want through customized scripts on them in real time. So you can create scripts to change ownership of files and directories automatically. This solution doesn't work (and isn't required) in Microsoft Windows because of NTFS "permission inheritance".

How insecure permissions are a problem

If you allow your site to modify the files which form the code running your site, you make it much easier for someone to take over your server.

Worst case scenario: a file upload tool in Drupal allows users to upload a file with any name and any contents. This allows a user to upload a mail relay PHP script to your site, which they can place wherever they want to turn your server into a machine to forward unsolicited commercial email. This script could also be used to read every email address out of your database, or other personal information.

Undesirable scenario: if the malicious user can upload a file with any name but not control the contents, then they could easily upload a file which overwrites your index.php (or another critical file) and breaks your site.

Undesirable scenario: if the code allows users to see the contents of files, attackers could see information which might reveal potential attack vectors.

If you are a sysadmin

Disclaimer: Don't risk following these instructions blindly; each system has its own peculiarities and, because of that, the instructions here MUST be altered to suit your needs. All of the instructions here are aimed at people who are familiar with filesystem permissions and know exactly what all the commands written here mean. If you try to follow these instructions without paying full attention to what you are doing, you are very likely to get into trouble. These instructions are designed to alter filesystem permissions as a root-level user, so tread carefully!

Note for hosted Drupal installations
This installation method presumes one shared drupal core and many subdomains installed under it. So suppose you are the owner of a hosting service that has a Drupal installation already pre-configured and you want to sell a hosted Drupal site with pre-configured Drupal core to your clients. So when you sell it, you create their user on the server and let them configure their own Drupal site just entering their site's address to open the install page. The way this document suggests the configuration, it will prevent customers from modifying and accessing the Drupal core files and other customer's sites files and directories.

It is important to notice that the user ownership of Drupal's core directories/subdirectories and files is given to the user who administers Drupal (usually root) and group ownership is given to the group your apache is running on. For files and directories inside the "sites" directory the user who is hosting the site on your server is their owner. One way to do this is to delete the "sites" directory under Drupal's root directory and make it a symbolic link to /home or another path you use to store user's home directories. That way, if you create user names that matches the customer's site URL, no permission will need to be changed. The only thing to be done is to change the directory group ownership to the group your apache is running on.

# ----------------------------- 1 ------------------------

The permission required by Apache is given through group permission and others have no access at all to any files and directories on a Drupal installation. Don't give any permissions to "others", otherwise if your system is hacked through a user's weak password, the hacker will be able to access all files and directories of all installed sites on your server, not only the one invaded. Even a read-only permission must be avoided. Remember that the user name and password to connect to the database for each site are stored in settings.php. Worse still, if you give write permission to others, the hacker will be able to alter files to damage your site or upload malicious scripts to your server.

The instructions in this guide assume a non-hosted installation, so modify the ownership to match your situation as necessary, e.g. where you see "greg" in these instructions, replace "greg" with the name of the user (often root, but not necessarily) who administers your installation of Drupal.

Linux servers

Permissions for files and directories on a Linux system are adjusted using the chmod command. User-owner and group-owner identity for files and directories are adjusted using the chown command.

The code below demonstrates one method for changing the ownership and permissions of files and directories in the Drupal Root directory to conform ownership and permissions to the recommendations above. We assume in the example below that the user greg is part of the greg group and that user greg is the site owner. We also assume that you are running Drupal on a server that is not in a hosting environment which provides website hosting services to multiple customers.

Make sure you run the following commands from inside Drupal's root directory! If you run these commands from any other directory, you either will not make changes to all of the Drupal installation's files and directories or you will make changes to files and directories other than those in the Drupal installation. Neither alternative is your goal.

# ----------------------------- 2 ------------------------

The second command makes user greg the user-owner and group www-data the group-owner of all files and directories in Drupal's root directory and all subdirectories and files in those subdirectories (the -R switch means recursive). Note that in a multiple-customer hosting environment, the user-owner of the Drupal files and directories should be root.

The third command in the example finds all directories and subdirectories in Drupal's root directory and executes the chmod command on all of those directories and subdirectories (-type d means filesystem entities that are directories). The command changes the permissions to read, write and access for user greg and read and access for users in the www-data group. Users who are not greg and not in the www-data group cannot read, write, or access the directories or subdirectories in the Drupal root directory. In numeric notation, the permission assigned to these directories and subdirectories is 750.

The fourth command finds all files in the Drupal root directory and its subdirectories and changes the permissions on those files to read and write for the user greg and read only for the www-data group. Other users have no access to these files. The numeric notation for this set of permissions is 640.

For the "files" directory in the sites/default directory and any other site directories in a multi-site installation, the permissions are slightly different because the www-data user must have write permission to the directory:
# ----------------------------- 3 ------------------------

The second command above finds all subdirectories named files below the sites directory and changes the permissions for the user-owner and the group-owner to read, write, and access. All other users cannot read, write to, or access these files subdirectories.

The "for" loop above is written for an sh-style shell (sh, bash,ksh). If you use csh or tcsh, type bash before executing the command. These commands in the loop give read, write, and access permissions to user greg and group www-data to all subdirectories and files within the files but not access to other users. The numeric permissions code is 770.

Remember that any newly installed module/theme or whatever add-on must have its permissions changed too. It's better to do this BEFORE installing the module, theme, or add-on in its appropriate Drupal directory.

Windows servers
By default, Apache runs in the built in SYSTEM account. So all you have to do is change the permission in a way that only the SYSTEM account, the administrators group and the user greg have access to the Drupal root directory, subdirectories and files (assuming greg is the site owner).

You should exclude all other users and groups from the ACL. For the SYSTEM account give read only access to all Drupal directories, subdirectories and files except for the "files" directories which require write permission. For the user greg give read, modify and write permissions. And for the administrators group give complete access. Go to the Drupal root directory, right click on it, go to properties and then security.

Make use of permission inheritance to make things easier for yourself. And remember that any newly installed module/theme or add on must have its permissions changed too. It's better to do this BEFORE installing it in its appropriate Drupal directory. Permission inheritance is done automatically by Windows.

Special considerations for settings.php

The settings.php file contains the database password and username in plain text and, once created, must be set so that only appropriate users can read it. That usually means removing read permissions for the "other" user.

Summarizing the permissions

drupal_admin: the user on the server that administrates Drupal, not necessarily is the root.
site_admin: the owner of the hosted site (a customer)
Ownership
Core modules/themes files and directories: drupal_admin:www-data
Hosted sites modules/themes/files files and directories: site_admin:www-data

Permissions
Core modules/themes directories: rwxr-x---
Core modules/themes files: rw-r-----
Hosted sites modules/themes directories: rwxr-x---
Hosted sites modules/themes files: rw-r-----
Hosted sites "files" directory: rwxrwx---
Hosted sites files under "files" directories: rw-rw----
Hosted sites subdirectories under "files" directories: rwxrwx---

Follow this guide exactly as it is to make your Drupal installation as secure as possible. This guide was tested and works. If something goes wrong with your installation, review the steps — possibly you missed something. If it really doesn't work, post a comment with your issue, and someone will fix this guide.

Script based on guidelines given above

If you need to fix permissions repeatedly then the following script will help you, it is based on the guidelines given above and performs some checks before any modification to ensure it is not applied on files/directories outside your Drupal installation.

</> CopyGet raw version
bash
  1. # TIP: The following comand may also deal with different 'x' for files and dirs:
  2. # The uppercase X will add the execute permission only to directories leaving regular files
  3. #untouched. Both files and dirs would get read permission added.
  4. [root@localhost]chmod go+rX
  5.  
  6. # ----------------------------- 1 ------------------------
  7.  
  8. cd /path_to_drupal_installation
  9. mv sites/* /home
  10. rmdir sites
  11. ln -s /home sites
  12. useradd www.example.com
  13. chown -R www.example.com:www-data /home/www.example.com
  14.  
  15. # ----------------------------- 2 ------------------------
  16.  
  17. [root@localhost]cd /path_to_drupal_installation
  18. [root@localhost]chown -R greg:www-data .
  19. [root@localhost]find . -type d -exec chmod u=rwx,g=rx,o= '{}' \;
  20. [root@localhost]find . -type f -exec chmod u=rw,g=r,o= '{}' \;
  21.  
  22. # ----------------------------- 3 ------------------------
  23. [root@localhost]cd /path_to_drupal_installation/sites
  24. [root@localhost]find . -type d -name files -exec chmod ug=rwx,o= '{}' \;
  25. [root@localhost]for d in ./*/files
  26. do
  27. find $d -type d -exec chmod ug=rwx,o= '{}' \;
  28. find $d -type f -exec chmod ug=rw,o= '{}' \;
  29. done
  30.  
  31. # ----------------------------- 4 ------------------------