├── .gitignore ├── lib └── security-scan.lib.sh ├── readme.md └── security-scan.config.sample.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | test.sh -------------------------------------------------------------------------------- /lib/security-scan.lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## WordPress Security Scan Script 4 | ## Copyright by Peter Chester of Modern Tribe, Inc. 5 | ## Permission to copy and modify is granted under the GPL3 license 6 | ## Version 2013.01.11 7 | ## 8 | ## For more information see: 9 | ## http://github.com/moderntribe/WordPress-Security-Shell-Script 10 | 11 | # Check for required variables. 12 | REQUIRED_VARS=( "ROOT_DIR" ) 13 | for REQUIRED_VAR in ${REQUIRED_VARS[@]} 14 | do 15 | # Test that vars are set 16 | REQUIRED_VAR_TEST=$(eval echo "\$${REQUIRED_VAR}") 17 | if [ -z "${REQUIRED_VAR_TEST+x}" ] 18 | then 19 | echo "ERROR: '$REQUIRED_VAR' is required." 20 | exit 21 | fi 22 | done 23 | 24 | # Change directories to the webroot (ROOT_DIR). 25 | if [ -d $ROOT_DIR ] 26 | then 27 | CURRENT_DIR=$(pwd) 28 | cd "$ROOT_DIR" 29 | else 30 | echo "Error: the directory specified in ROOT_DIR does not exist." 31 | echo "$ROOT_DIR" 32 | exit 33 | fi 34 | 35 | [ $VERBOSE ] && echo "Running scan on $ROOT_DIR..."; 36 | 37 | # SVN Scan 38 | if [ -z $BYPASS_SVN ] && [ -d ".svn" ] 39 | then 40 | [ $VERBOSE ] && echo "Detected SVN environment" 41 | 42 | [ $VERBOSE ] && echo "Cleaning SVN..." 43 | svn cleanup 44 | 45 | [ $VERBOSE ] && echo "Updating SVN..." 46 | svn up 47 | 48 | [ $VERBOSE ] && echo "Reverting SVN..." 49 | svn revert -R . 50 | 51 | [ $VERBOSE ] && echo "Pruning unversioned files..." 52 | rm -rf `svn status . | grep '^?' | awk '{ print $2 }' | xargs` 53 | fi 54 | 55 | # GIT Scan 56 | if [ -z $BYPASS_GIT ] && [ -d ".git" ] 57 | then 58 | [ $VERBOSE ] && echo "Detected GIT environment" 59 | 60 | if [ $KILL_GITLOCK ] && [ -f ".git/index.lock" ] 61 | then 62 | [ $VERBOSE ] && echo "GIT appears to be LOCKED. Deleteing .git/index.lock." 63 | rm -rf ".git/index.lock" 64 | fi 65 | 66 | [ $VERBOSE ] && echo "Make sure GIT ignores file perms..." 67 | git config core.filemode false 68 | 69 | [ $VERBOSE ] && echo "Resetting GIT..." 70 | git reset --hard HEAD 71 | 72 | [ $VERBOSE ] && echo "Pulling GIT..." 73 | git pull 74 | 75 | [ $VERBOSE ] && echo "Attempting to update Submodules..." 76 | git submodule foreach --recursive git reset --hard 77 | git submodule update --init --recursive 78 | 79 | [ $VERBOSE ] && echo "Cleaning GIT..." 80 | git clean -df 81 | fi 82 | 83 | # Adjusting directory permissions 84 | if [ -z $DIR_PERM ]; then DIR_PERM="755"; fi 85 | [ $VERBOSE ] && echo "Updating file permissions on all directories to be $DIR_PERM" 86 | find "$ROOT_DIR" -type d ! -perm "$DIR_PERM" -exec chmod "$DIR_PERM" {} \; 87 | 88 | # Adjust file permissions 89 | if [ -z $FILE_PERM ]; then FILE_PERM="644"; fi 90 | [ $VERBOSE ] && echo "Updating file permissions on all files to be $FILE_PERM" 91 | find "$ROOT_DIR" -type f ! -perm "$FILE_PERM" -exec chmod "$FILE_PERM" {} \; 92 | 93 | # Update all file owners 94 | if [ $CODE_OWNER ] 95 | then 96 | [ $VERBOSE ] && echo "Update all files to be owned by $CODE_OWNER" 97 | chown -R "$CODE_OWNER" "$ROOT_DIR" 98 | fi 99 | 100 | # Process scrub directories 101 | if [ "$SCRUB_DIRS" ] 102 | then 103 | for DIR in ${SCRUB_DIRS[@]} 104 | do 105 | # Scan directory 106 | if [ -d "$DIR" ] 107 | then 108 | [ $VERBOSE ] && echo "Scanning and removing PHP files from $DIR..." 109 | find "$DIR" -name '*php' 110 | find "$DIR" -name '*php' | xargs rm -rf 111 | 112 | [ $VERBOSE ] && echo "Scanning and removing HTML files from $DIR..." 113 | find "$DIR" -name '*html' 114 | find "$DIR" -name '*htm' 115 | find "$DIR" -name '*html' | xargs rm -rf 116 | find "$DIR" -name '*htm' | xargs rm -rf 117 | 118 | if [ $WEB_OWNER ] 119 | then 120 | [ $VERBOSE ] && echo "Updating file ownership to $WEB_OWNER $DIR..." 121 | chown -R $WEB_OWNER $DIR 122 | fi 123 | else 124 | echo "Error: $ROOT_DIR/$DIR not found." 125 | fi 126 | done 127 | fi 128 | 129 | # Process writable directories 130 | if [ "$WRITEABLE_DIRS" ] 131 | then 132 | for DIR in ${WRITEABLE_DIRS[@]} 133 | do 134 | # Scan directory 135 | if [ -d "$DIR" ] 136 | then 137 | if [ $WEB_OWNER ] 138 | then 139 | [ $VERBOSE ] && echo "Updating file ownership to $WEB_OWNER $DIR..." 140 | chown -R $WEB_OWNER $DIR 141 | fi 142 | else 143 | echo "Error: $ROOT_DIR/$DIR not found." 144 | fi 145 | done 146 | fi 147 | 148 | # Process writable files 149 | if [ "$WRITEABLE_FILES" ] 150 | then 151 | for WFILE in ${WRITEABLE_FILES[@]} 152 | do 153 | # Scan file 154 | if [ -f "$WFILE" ] 155 | then 156 | if [ $WEB_OWNER ] 157 | then 158 | [ $VERBOSE ] && echo "Updating file ownership to $WEB_OWNER $WFILE..." 159 | chown $WEB_OWNER $WFILE 160 | fi 161 | 162 | [ $VERBOSE ] && echo "Updating file permissions to $FILE_PERM on $WFILE..." 163 | chmod $FILE_PERM $WFILE 164 | else 165 | echo "Error: $ROOT_DIR/$WFILE not found." 166 | fi 167 | done 168 | fi 169 | 170 | [ $VERBOSE ] && echo "Scan complete!" 171 | cd "$CURRENT_DIR" -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WordPress Security and GIT/SVN Deployment Script 2 | 3 | A shell script for cron based version control deployments and security scans of WordPress. 4 | 5 | ## Summary 6 | 7 | This script was written to quickly and easily set up automated deployment and security scanning of WordPress installs. The idea is that if you have your WordPress install set up on GIT or SVN, you can use this script to regularly update and secure your site(s). 8 | 9 | * Uses SVN or GIT to make sure no rogue files exist in an install 10 | * Removes PHP,HTML and HTM files from writable directories such as blogs.dir and uploads. 11 | * Updates and maintains file and folder permissions and ownership. 12 | * Fully configurable. 13 | * Effectively acts as a deployment system. Every time you run the script, your version controlled code is deployed. 14 | 15 | Version 2013.01.11 16 | 17 | ## Important Warning!!! 18 | 19 | This script will likely delete files and folders. If you do not configure your repository and this script correctly you can irreparably damage your website. We are not responsible for catastrophes caused by the mis-use (or use) of this script. 20 | 21 | To best ensure that your system does not suffer from damage due to file deletions, make sure that you BACK UP YOUR ENTIRE WEBROOT before running this script. 22 | 23 | Also, it is essential that your GIT/SVN ignore configuration is maintained. This script ASSUMES THAT UNVERSIONED FILES THAT ARE NOT IGNORED SHOULD BE DELETED. 24 | 25 | For example, it is essential that at a minimum, the following is in your ignore configuration: 26 | 27 | * wp-config.php 28 | * wp-content/uploads 29 | 30 | Depending on your configuration, you may also need to ignore other files & folders such as .htaccess, wp-content/cache, wp-content/blogs.dir, wp-content/advanced-cache.php, etc... 31 | 32 | ## Usage 33 | 34 | `bash mydomain.security-scan.sh` 35 | 36 | In some cases, 'bash' doesn't work and in stead you would invoke the script like this: 37 | 38 | `. mydomain.security-scan.sh` 39 | 40 | It is recommended that you set this up on a crontab so that the scan is regularly performed. Ideally the script lives outside of your webroot directory. See installation for more details. 41 | 42 | ## Todo 43 | 44 | * Possibly scan wp-config.php and .htaccess for security 45 | * Possibly update the lib script to also accept parameters as arguments so that the config file is optional and the lib script can be executed directly. 46 | 47 | ## Installation 48 | 49 | These instructions assume that you have basic *nix systems skills. If you read through this and feel totally confused, then you should probably seek some professional help in installing this. 50 | 51 | Although this script is flexible enough to be configured and used in a variety of ways. Here's how we prefer to install it: 52 | 53 | # Install your Versioned Code 54 | 55 | Before we do anything with this script, we're making some assumptions about your code setup. In particular, we're assuming that you've got your WordPress code in either GIT or SVN version control and that you've checked out that code into your webroot on your server. 56 | 57 | As per our Important Warning above, we're also assuming that you've got proper GIT/SVN ignore configurations so that when the site is running, there are NO UNVERSIONED FILES aside from ones that you've explicitly ignored. 58 | 59 | This script supports traditional WordPress configurations as well as @markjaquith's Skeleton configuration. 60 | 61 | # Download the Script onto Your Server 62 | 63 | Change directories to the folder above the webroot. 64 | 65 | `cd /path/above/your/webroot` 66 | 67 | Make a folder called scripts to hold this script and any others you might want to run. 68 | 69 | `mkdir scripts` 70 | 71 | Change directories to your scripts folder. 72 | 73 | `cd scripts` 74 | 75 | Initialize GIT. 76 | 77 | `git init` 78 | 79 | Checkout the security scripts code. 80 | 81 | `git checkout git://github.com/moderntribe/WordPress-Security-Shell-Script.git security-script` 82 | 83 | # Set up a Local Config 84 | 85 | While still in the scripts folder, copy the sample script to a new file that you can configure for your website. 86 | 87 | `cp security-script/security-scan.config.sample.sh mydomain.security-scan.sh` 88 | 89 | Edit the file (I prefer to use Nano for this. You can choose to use whatever or even FTP and use your favorite file editor). The various configuration options are documented in the sample config file. 90 | 91 | `nano mydomain.security-scan.sh` 92 | 93 | Define at least ROOT_DIR, WRITEABLE_DIRS and the last line in the script where we include the library. You might also want to define KILL_GITLOCK as true to ensure that this script never gets locked out of making GIT updates. 94 | 95 | # Test the Script 96 | 97 | Backup your webroot incase the script messes things up. 98 | 99 | `cp -rfp /path/to/your/webroot /path/to/your/webroot.bk` 100 | 101 | Run the script (not this should be running the copy you made). 102 | 103 | `bash /path/above/your/webroot/scripts/mydomain.security-scan.sh` 104 | 105 | # Set up a Crontab 106 | 107 | Open your cron for editing: 108 | 109 | `crontab -e` 110 | 111 | Run this every 5 minutes or so. You can also choose to output the results to a log file. 112 | 113 | */5 * * * * bash /path/above/your/webroot/scripts/mydomain.security-scan.sh > /dev/null 2>&1 114 | 115 | Tip: If you want to get emailed when there are errors, you can set up the cron to email you the output and comment out the VERBOSE parameter in the config so that it only returns an output if there is an error. 116 | 117 | ## License 118 | 119 | Copyright Modern Tribe, Inc. (@moderntribeinc) 2012 120 | 121 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 122 | 123 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 124 | 125 | You should have received a copy of the GNU General Public License along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). 126 | 127 | ## Authors and Contributors 128 | 129 | Authored by Chaim Peter Chester (@peterchester) with contributions from Daniel Dvorkin (@mzaweb) and Jonathan Brinley (@jbrinley). 130 | 131 | ## Support or Contact 132 | 133 | This code is unsupported and offered on an "as is" basis. If you would like support we recommend either asking about it on [Stack Overflow](http://stackoverflow.com/search?q=wordpress+security+scan) or entering an issue on [the github tracker](https://github.com/moderntribe/WordPress-Security-Shell-Script/issues) -------------------------------------------------------------------------------- /security-scan.config.sample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## WordPress Security Scan Script 4 | ## Executable Config File 5 | ## 6 | ## Copy and modify this file with the following variables and execute this 7 | ## file manually or via cron to run the script. Ideally you'll want to run 8 | ## this as root using 'sudo'. For more information see: 9 | ## http://github.com/moderntribe/WordPress-Security-Shell-Script 10 | ## 11 | ## This file can be placed anywhere on your server as long as the references 12 | ## back to the ROOT_DIR and the library (last line) are absolute. 13 | 14 | 15 | # Root web directory. This is the full path to your WordPress root folder. 16 | 17 | ROOT_DIR="/absolute/path/to/the/web/root/folder" 18 | 19 | # Optional list of all PHP writeable directories that should be purged of PHP, 20 | # HTML, and HTM files and where their file permissions will be updated such 21 | # that PHP can write to the files. 22 | # Note that scrip array items are separated by spaces, not commas. 23 | 24 | # SCRUB_DIRS=( "wp-content/uploads" "wp-content/blogs.dir" ) 25 | 26 | 27 | # Optional list of all PHP writeable directories. These directories will also 28 | # have their file permissions updated such that PHP can write to the files. 29 | # Note that scrip array items are separated by spaces, not commas. 30 | 31 | # WRITEABLE_DIRS=( "wp-content/uploads" "wp-content/blogs.dir" "wp-content/cache" ) 32 | 33 | 34 | # Optional list of all PHP writeable files. These files will have their file 35 | # permissions updated such that PHP can write to them. 36 | # Note that scrip array items are separated by spaces, not commas. 37 | 38 | # WRITEABLE_FILES=( "wp-config.php" "wp-content/advanced-cache.php" "wp-content/cache-config.php" ) 39 | 40 | 41 | # Optional PHP writeable user:group. This should be whatever the user:group 42 | # is that PHP is using otherwise php may be unable to write to the files and 43 | # folders. 44 | 45 | # WEB_OWNER="www-data:www-data" 46 | 47 | 48 | # Optional private code user:group. In some cases, such as on many shared 49 | # hosting environments, this is the same as the writeable user:group setting. 50 | # But ideally, we want to make code files inaccessible to PHP so that hackers 51 | # are prevented from gaining access to functional files. 52 | 53 | # CODE_OWNER="www:www" 54 | 55 | 56 | # Optional permissions for directories (defaults to 755). 57 | 58 | # DIR_PERM="755" 59 | 60 | 61 | # Optional permissions for files (defaults to 644). 62 | 63 | # FILE_PERM="644" 64 | 65 | 66 | # Optional flag to skip SVN processing. If there are no .svn files in your 67 | # code, SVN will be skipped anyway. But if for some reason you want to 68 | # explicitely bypass SVN updating and resetting, then you can set this to 69 | # true. 70 | 71 | # BYPASS_SVN=true 72 | 73 | 74 | # Optional flag to skip GIT processing. If there are no .git files in your 75 | # code, GIT will be skipped anyway. But if for some reason you want to 76 | # explicitely bypass GIT updating and resetting, then you can set this to 77 | # true. 78 | 79 | # BYPASS_GIT=true 80 | 81 | 82 | # Optional flag to kill GIT locking. Sometimes, we've encountered a case where 83 | # GIT is locked and we can no longer use this script to update or reset git. 84 | # To prevent this, we've implemented a crude workaround of forcing a removal 85 | # of the lock file. Caution: it is possible that removing this lock file while 86 | # a legitimate GIT process is running may corrupt your repository. You should 87 | # only use this option if you are sure that noone is manually pulling from git 88 | # and that there is no chance of another GIT process running when this script 89 | # runs. 90 | 91 | # KILL_GITLOCK=true 92 | 93 | 94 | # Optional flag to toggle script output. If $VERBOSE is set to true then the 95 | # script will output all messages, otherwise it only outputs errors. 96 | 97 | VERBOSE=true 98 | 99 | 100 | # Include the actual script (use the full path). Up to this point all we've 101 | # been doing is configuring the script. All this configuration will do nothing 102 | # if you don't actually then run the script :) 103 | 104 | . /absolute/path/to/this/folder/lib/security-scan.lib.sh --------------------------------------------------------------------------------