Skip to content

Instantly share code, notes, and snippets.

@phlbnks
Forked from bgallagh3r/wp.sh
Last active June 6, 2022 09:07
Show Gist options
  • Save phlbnks/37ef1f355ec5a7ecbb8f to your computer and use it in GitHub Desktop.
Save phlbnks/37ef1f355ec5a7ecbb8f to your computer and use it in GitHub Desktop.
#!/bin/bash -e
clear
echo "============================================"
echo "WordPress Install Script"
echo "============================================"
echo "Do you need to setup new MySQL database? (y/n)"
read -e setupmysql
if [ "$setupmysql" == y ] ; then
echo "MySQL Admin User: "
read -e mysqluser
echo "MySQL Admin Password: "
read -s mysqlpass
echo "MySQL Host (Enter for default 'localhost'): "
read -e mysqlhost
mysqlhost=${mysqlhost:-localhost}
fi
echo "WP Database Name: "
read -e dbname
echo "WP Database User: "
read -e dbuser
echo "WP Database Password: "
read -s dbpass
echo "WP Database Table Prefix [numbers, letters, and underscores only] (Enter for default 'wp_'): "
read -e dbtable
dbtable=${dbtable:-wp_}
echo "Do basic hardening of wp-config and htaccess? (y/n)"
read -e harden
if [ "$harden" == y ] ; then
echo "Key for updating: "
read -e hardenkey
fi
echo "Last chance - sure you want to run the install? (y/n)"
read -e run
if [ "$run" == y ] ; then
if [ "$setupmysql" == y ] ; then
echo "============================================"
echo "Setting up the database."
echo "============================================"
#login to MySQL, add database, add user and grant permissions
dbsetup="create database $dbname;GRANT ALL PRIVILEGES ON $dbname.* TO $dbuser@$mysqlhost IDENTIFIED BY '$dbpass';FLUSH PRIVILEGES;"
mysql -u $mysqluser -p$mysqlpass -e "$dbsetup"
if [ $? != "0" ]; then
echo "============================================"
echo "[Error]: Database creation failed. Aborting."
echo "============================================"
exit 1
fi
fi
echo "============================================"
echo "Installing WordPress for you."
echo "============================================"
#download wordpress
echo "Downloading..."
curl -O https://wordpress.org/latest.tar.gz
#unzip wordpress
echo "Unpacking..."
tar -zxf latest.tar.gz
#move /wordpress/* files to this dir
echo "Moving..."
mv wordpress/* ./
echo "Configuring..."
#create wp config
mv wp-config-sample.php wp-config.php
#set database details with perl find and replace
perl -pi -e "s'database_name_here'"$dbname"'g" wp-config.php
perl -pi -e "s'username_here'"$dbuser"'g" wp-config.php
perl -pi -e "s'password_here'"$dbpass"'g" wp-config.php
perl -pi -e "s/\'wp_\'/\'$dbtable\'/g" wp-config.php
#set WP salts
perl -i -pe'
BEGIN {
@chars = ("a" .. "z", "A" .. "Z", 0 .. 9);
push @chars, split //, "!@#$%^&*()-_ []{}<>~\`+=,.;:/?|";
sub salt { join "", map $chars[ rand @chars ], 1 .. 64 }
}
s/put your unique phrase here/salt()/ge
' wp-config.php
#create uploads folder and set permissions
mkdir wp-content/uploads
chmod 775 wp-content/uploads
if [ "$harden" == y ] ; then
echo "============================================"
echo "Hardening."
echo "============================================"
#remove readme.html
rm readme.html
#debug extras
perl -pi -e "s/define\('WP_DEBUG', false\);/define('WP_DEBUG', false);\n\/** Useful extras *\/ \nif (WP_DEBUG) { \n\tdefine('WP_DEBUG_LOG', true); \n\tdefine('WP_DEBUG_DISPLAY', false); \n\t\@ini_set('display_errors',0);\n}/" wp-config.php
# key access to mods
find="/* That's all, stop editing! Happy blogging. */"
replace="/** Disallow theme and plugin editor in admin. Updates only with query var */\ndefine( 'DISALLOW_FILE_EDIT', true );\nif ( \\$\_REQUEST['key'] == '$hardenkey' ) {\n\tsetcookie( 'updatebypass', 1 );\n} elseif ( ! \\$\_COOKIE['updatebypass'] ) {\n\tdefine( 'DISALLOW_FILE_MODS', true );\n}\n\n/* That's all, stop editing! Happy blogging. */"
perl -pi -e "s{\Q$find\E}{$replace}" wp-config.php
#create root .htaccess with some useful starters
cat > .htaccess <<'EOL'
# Protect this file
<Files ~ "^\.ht">
Order allow,deny
Deny from all
</Files>
# Prevent directory listing
Options -Indexes
## BEGIN 6G Firewall from https://perishablepress.com/6g/
# 6G:[QUERY STRINGS]
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (eval\() [NC,OR]
RewriteCond %{QUERY_STRING} (127\.0\.0\.1) [NC,OR]
RewriteCond %{QUERY_STRING} ([a-z0-9]{2000}) [NC,OR]
RewriteCond %{QUERY_STRING} (javascript:)(.*)(;) [NC,OR]
RewriteCond %{QUERY_STRING} (base64_encode)(.*)(\() [NC,OR]
RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC,OR]
RewriteCond %{QUERY_STRING} (<|%3C)(.*)script(.*)(>|%3) [NC,OR]
RewriteCond %{QUERY_STRING} (\\|\.\.\.|\.\./|~|`|<|>|\|) [NC,OR]
RewriteCond %{QUERY_STRING} (boot\.ini|etc/passwd|self/environ) [NC,OR]
RewriteCond %{QUERY_STRING} (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
RewriteCond %{QUERY_STRING} (\'|\")(.*)(drop|insert|md5|select|union) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REQUEST METHOD]
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_METHOD} ^(connect|debug|delete|move|put|trace|track) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REFERRERS]
<IfModule mod_rewrite.c>
RewriteCond %{HTTP_REFERER} ([a-z0-9]{2000}) [NC,OR]
RewriteCond %{HTTP_REFERER} (semalt.com|todaperfeita) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REQUEST STRINGS]
<IfModule mod_alias.c>
RedirectMatch 403 (?i)([a-z0-9]{2000})
RedirectMatch 403 (?i)(https?|ftp|php):/
RedirectMatch 403 (?i)(base64_encode)(.*)(\()
RedirectMatch 403 (?i)(=\\\'|=\\%27|/\\\'/?)\.
RedirectMatch 403 (?i)/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$
RedirectMatch 403 (?i)(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")
RedirectMatch 403 (?i)(~|`|<|>|:|;|,|%|\\|\s|\{|\}|\[|\]|\|)
RedirectMatch 403 (?i)/(=|\$&|_mm|cgi-|etc/passwd|muieblack)
RedirectMatch 403 (?i)(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)
RedirectMatch 403 (?i)\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$
RedirectMatch 403 (?i)/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php
</IfModule>
# 6G:[USER AGENTS]
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent ([a-z0-9]{2000}) bad_bot
SetEnvIfNoCase User-Agent (archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune) bad_bot
<limit GET POST PUT>
Order Allow,Deny
Allow from All
Deny from env=bad_bot
</limit>
</IfModule>
# 6G:[BAD IPS]
<Limit GET HEAD OPTIONS POST PUT>
Order Allow,Deny
Allow from All
# uncomment/edit/repeat next line to block IPs
# Deny from 123.456.789
</Limit>
## END 6G Firewall
## BEGIN htauth basic authentication
# STAGING
Require all denied
AuthType Basic
AuthUserFile /etc/apache2/wp-login
AuthName "Please Authenticate"
Require valid-user
# LIVE - prevent wp-login brute force attacks from causing load
#<FilesMatch "^(wp-login|xmlrpc)\.php$">
# AuthType Basic
# AuthUserFile /etc/apache2/wp-login
# AuthName "Please Authenticate"
# Require valid-user
#</FilesMatch>
# Exclude the file upload and WP CRON scripts from authentication
#<FilesMatch "(async-upload\.php|wp-cron\.php)$">
# Satisfy Any
# Order allow,deny
# Allow from all
# Deny from none
#</FilesMatch>
## END htauth
## BEGIN WP file protection
<Files wp-config.php>
order allow,deny
deny from all
</Files>
# WP includes directories
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
# note - comment out next line on multisite
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
## END WP file protection
# Prevent author enumeration
RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} author=\d
RewriteRule ^ /? [L,R=301]
EOL
#create .htaccess to protect uploads directory
cat > wp-content/uploads/.htaccess <<'EOL'
# Protect this file
<Files .htaccess>
Order Deny,Allow
Deny from All
</Files>
# whitelist file extensions to prevent executables being
# accessed if they get uploaded
order deny,allow
deny from all
<Files ~ ".(docx?|xlsx?|pptx?|txt|pdf|xml|css|jpe?g|png|gif)$">
allow from all
</Files>
EOL
fi
echo "Cleaning..."
#remove wordpress/ dir
rmdir wordpress
#remove zip file
rm latest.tar.gz
#remove bash script if it exists in this dir
[[ -f "wp.sh" ]] && rm "wp.sh"
echo "========================="
echo "[Success]: Installation is complete."
echo "========================="
else
exit
fi
@phlbnks
Copy link
Author

phlbnks commented Jul 27, 2015

Be my guest @bgallagh3r - it was your script that got this going in the first place after all!

@jonatasrenan
Copy link

great @emirpprime! thanks!

@maiorano84
Copy link

This is a fantastic script. One oddity I've noticed and this occurs on both your version and bgallagh3r/wp.sh:

It seems like the password field being written to wp-config.php is stripping out meta characters. So if I enter a password that looks like: wordp355u$3r, the dollar sign and the character following it are stripped out becoming:

wordp355ur

I've tried something like:

perl -pi -e "s/password_here/\\Q${dbpass}/g" wp-config.php

But that doesn't solve the issue, and my Perl isn't very good. Any thoughts?

@phlbnks
Copy link
Author

phlbnks commented Sep 16, 2015

@maiorano84 - I haven't had time to test it yet, but this might do it: perl -pi -e "s/password_here/\Q$dbpass\E/g" wp-config.php or perhaps perl -pi -e "s{password_here}{\Q$dbpass\E}g" wp-config.php (they should be identical in function but the latter is a bit easier to read)

@phlbnks
Copy link
Author

phlbnks commented Dec 9, 2015

@maiorano84 @bgallagh3r If you're still using this / something similar and getting the 'special characters in passwords' problems, the latest revision should have that fixed now. Apparently using quotes as sed delimiters prevents interpretation of special characters. I'm still not preventing breaking characters being used in the user input, but it's a lot better than it was.

@GabrielDancause
Copy link

@emirpprime Nice job. I'm wondering, what are all you hardening ? Looks good, but I like to understand what I'm executing. :-)

Thanks.

@phlbnks
Copy link
Author

phlbnks commented Jul 28, 2016

@GabrielDancause sorry i missed your comment. Hardening will:

  • disallow directly editing plugin / theme files in the admin area
  • only allow plugin and core updates to be done if you supply the key (?key=YOUR_KEY_FOR_UPDATING); e.g. go to sites.com/wp-admin/?key=YOUR_KEY_FOR_UPDATING - then you will be able to update plugins etc.
  • protect the .htaccess / .htpasswd and wp-config.php files
  • remove a couple of files that tell people what version of WordPress you are running
  • setup the 6G 'firewall' from Perishable Press
  • prevent author enumeration
  • stop people directly accessing the wp-includes directory
  • only allow people to access certain file extensions in /uploads

It definitely needs some configuration for your setup. But it would run ok if you use it straight off as long as you either create a htpasswd file called wp-login and put it at /etc/apache2/ on your server, or just comment out the Staging section of the htauth config in the .htaccess file in the wordpress root directory.

@bakmanhans
Copy link

Great script! Works on my Synology (XPEnology) DSM 5.2
Thank you both @emirpprime and @bgallagh3r

@ShahrozeAli
Copy link

I am using this script to create a web app ...
To download and install wordpress..
i am new to shell..
(Problem 1) one thing i don't understand why you set db pass and local pass to null..

at first it works fine but now
(Problem 2) it does not read harden key and won't download and install wordpress ..

i am using this script to access localhost and after few tries my phpmyadmin won't allow me to access it.
(Problem 3) i have to uninstall and reinstall my server..

(Problem 4) though i managed to download wordpress and create database but it does not install wordpress and do not create tables in database..

(ignore grammar mistakes -- thank You :) )

@ethosteam
Copy link

same here, wordpress is downloaded & installed but tables are missing in the DB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment