WordPress Security Tips – readme files

When an attacker arrives at your url (or a scripted bot, for that matter) one of the things it will look to discover is exploitable code

one of the methods of discovering this is to check paths for possible plugins, and have a look for a readme.txt (or similar) in order to discover the version number of the plugin/theme/code.

Once discovered, they will look to see if this version of the plugin/theme/code is exploitable..if it is; the games begin.

While recently looking through one of our servers which hosts a number of WordPress installations, we discovered 340 files matching the basic expression of readme*!!

$ sudo find /var/www/html -type f -iname "readme*" | wc -l

So, what does this mean?

This means that there are 340 opportunities to discover the version number of a given file, plugin, theme or piece of code. In fact, in our example..every single one of those files had a string matching “version” in them!

You can see from this, that its pretty undesirable to have these files in existence on the filesystem, ready to be looked at from a web browser.

In fact, you can test this idea by visiting http://{your_url}/readme.html …do you see your wordpress version number in the readme?  What if this was an exploitable version? Rhetorical questions ofcourse..we know what would happen if it was an exploitable version (and what might happen shortly afterward).

So, what to do?

In my case at least (and if you find an edge case, please feel free to comment..and we’ll come up with a solution/workaround) I simply do not want these files in the filesystem. I do not want would-be hackers/bots discovering the version numbers of the code in use on my website. Ofcourse, we look to protect the webroot in other ways, but if we can try tostop the attempt in the first place..then our security systems aren’t tested!

So, for me..I did (and yes, really do backup your files before proceeding!)

sudo find /var/www/html/yoursite/ -type f -iname "readme*" -exec rm -v {} \;

a few moments later, any file matching readme* will be removed from the webroot. Make sure you test your site thoroughly after running the above command..any problems restore from the backup you took(!!) and post a comment below.

Hope this is helpful!


WordPress Webroot Ownership / Permissions Script

Ok, so I’ve seen a fair amount of “run this script to fix your permissions” posts about, and for a number of reasons I don’t really like them.

Not saying that mine is perfect ofcourse, but the emphasis is trying to be around security.

With all security, you’re always compromising convenience. If you really want to secure your WordPress file ownership and permissions. Give it user:apache and 750/640 permissions throughout. But it won’t do you much good when it comes to installing plugins, etc.

So, here’s what you can do.

NOTE: The outcome of this script (should you read it and use it properly) is that your wordpress web root will be read only to apache (this is how we want it really) and wp-content and below will be writeable by apache (you need this if you want to install plugins, upload media). Having apache write to wp-content is the compromise here, because its undesirable for the web process to have write permissions…but we’re balancing security and convenience here. Because apache cannot write to a directory higher than wp-content, then it will NOT be able to update itself. So you’ll need to organise that when the times comes. I might write a post about that, but because it sucks to have apache writing to the webroot, I possibly won’t


## ok, you want to replace my ‘dcr226’ with whatever user you
## login to your server with. Change all of these settings to suit your system


chown -R $username:web_user $web_directory
find $web_directory -type d -exec chmod 750 {} \;
find $web_directory -type f -exec chmod 640 {} \;
find $web_directory/wp-content -type d -exec chmod 770 {} \;
find $web_directory/wp-content -type f -exec chmod 660 {} \;

#selinux stuff because…you should be using it

chcon -R -t httpd_sys_content_t $web_directory
chcon -R -t httpd_sys_rw_content_t $web_directory/wp-content
setsebool httpd_unified 0

SSL Certificates

Generating SSL Certificates With StartSSL

I’ve been meaning to do a how-to on generating your own SSL/TLS certificates using StartSSL ‘s free service.

These certificates are useful for a number of different things, most notably apache/nginx SSL security. But they are also widely used with other software, for example Bacula is much nicer when all of the various daemons are talking over SSL/TLS encrypted connections.

Step 1 – get an account at StartSSL.com

Its fairly self explanatory how to get you free account and validate domains with StartSSL..if anyone is struggling with it, make a comment below and I’ll do a how-to for that.

Step 2 – generate a rsa encrypted private key for the request.

In this example, we’re going to use the Triple DES cipher (168 bit key size) and our key is going to be 2048 bits in size. The default for OpenSSL is 512 bits but StartSSL won’t accept anything less than 2048 bits.

[plain]# openssl genrsa -des3 -out bruce.example.com.key 2048

Generating RSA private key, 2048 bit long modulus
e is 65537 (0x10001)
Enter pass phrase for bruce.example.com:[/plain]

So enter a passphrase (because you need to) to conclude this step. You should now have a file in your current working directory : bruce.example.com.key

Step 3 – make a passphrase-less version of your key

This is going to be important in most cases, the last thing you want to do when apache starts..is login to the console and enter a passphrase..so its handy to have a passwordless key

[bash] openssl rsa -in bruce.example.com.key -out bruce.example.com.nopass.key [/bash]

You will be asked for the passphrase you previously entered, after which you will have a second file in your cwd : bruce.example.com.nopass.key

Step 4 – generate a certificate signing request

Because we’re going to want a signed certificate (by StartSSL) then we’re going to need to generate a signing request.

Note: You’re going to need to answer some questions here, make sure you change my responses for yours. Also, the Organization Name must be your machine’s FQDN

[plain]openssl req -new -sha256 -key bruce.example.com.nopass.key -out bruce.example.com.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
Country Name (2 letter code) [XX]:GB
State or Province Name (full name) []:London
Locality Name (eg, city) [Default City]:London
Organization Name (eg, company) [Default Company Ltd]:Xreflow
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server’s hostname) []:bruce.example.com
Email Address []:

Please enter the following ‘extra’ attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:[/plain]

You should now have bruce.example.com.csr sat in your cwd.

Step 5 – get your certificate!

Sign in to your StartSSL control panel, and authenticate..you should be here

StartSSL Control Panel

Select “Certificates Wizard” then in the dropdown select “Web Server SSL/TLS Certificate”, you should be here.

StartSSL Control Panel

Click “Continue” and go to the next step in the wizard, where you will be given a choice to Skip This Step. You should skip that step because you’ve already generated a csr request. Now, you should be at the following screen..

Copy and paste the contents of your bruce.example.com.csr into the text area box, and click “Continue”..this information looks something like


and so on…paste this into the text area, and click “Continue”, a few seconds later you will be presented with your signed SSL certificate.

Open a new file with your favourite text editor bruce.example.com.cert, and paste the copied content into that file.

Step 5 – You’re done! go use your newly created, signed SSL/TLS certificate!

Bash Security Exploit

Bash Remote Code Injection Exploit

A remote exploit vulnerability in bash has been discovered in Linux, which will allow attackers to execute arbitrary code.

from http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6271

GNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which allows remote attackers to execute arbitrary code via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution.

The severity of this exploit is high, and you should look to update bash on all of your linux machines as soon as possible.

Please contact us if you require any support in updating your servers or machines

This affects all versions of Bash through version 4.3. Check and update to a patched version.

Additional Sources




WordPress IPtables – Security Tips – How To

WordPress IPTables

Please note, this is a working document. If you see anything thats missing, or needs more info..please do let me know

We really don’t want much in the way of outbound traffic coming from File our WordPress install. Malicious scrips and plugins will try to “phone home” or potentially deny someone else’s webserver of service, or possibly download more malicious scripts to be used on your website.

Unfortunately, in some cases we *want* wordpress to connect outbound, for example in the case of installing new plugins.

Now, if you want to install your plugins and themes manually you don’t need to do any of this, you can just upload the zip file onto your webserver, and unzip it into the wp-content/plugins directory. However, in the event that you want to search and install plugins from the dashboard, here’s how you can configure iptables.

The following assumes that your default firewall policy is to DROP outbound packets (OUTPUT -P DROP)

During my logging, it seems that the dashboard uses the same host/IP address when querying plugins ( which does indeed have a wordpress PTR record ( domain name pointer wordpress.org.) and when used in the browser resolves to wordpress.org. We need to allow outbound packets on port 80 from our server to this ip address.

the basic rule is:

OUTPUT -p tcp –dport 80 -d -m conntrack –ctstate NEW –syn -j ACCEPT

Now there are a couple of ways you Dinner can do this, you can insert this rule into the top of the OUTPUT chain with

iptables -I OUTPUT -p tcp --dport 80 -d -m conntrack --ctstate NEW --syn -j ACCEPT

or you can cheap NBA jerseys hand edit /etc/sysconfig/iptables and add the rule into the chain wholesale jerseys there.

Personally, I wanted to keep it a little tidier so created a new chain for wordpress (OUTPUT_WP) and Year added it there.


Turns out WordPress will try port 80 first (http) and then fall onto port 443 (https). Seeing as SSL is much more desirable in this context, we can actually allow to the destination port (–dport) 443.

Also, it would seem that WordPress have more ip addresses in that range, and although it will still work with just the one IP address..seems to make sense to Markierung allow it to failover onto another quickly enough. I’ll add more of these as time goes by.

So now we have:

OUTPUT -p tcp --dport 443 -m iprange --dst-range -m conntrack --ctstate NEW --syn -j ACCEPT

A word about email

In order for your wordpress instance to send email, it not only needs to have a MTA (postfix or sendmail perhaps) but needs to egress on port 25 (TCP)

OUTPUT -p tcp cheap MLB jerseys --dport smtp -m conntrack --ctstate NEW --syn -j ACCEPT


WordPress Security – File Permissions

We’ve been working recently on security, in this particular document we’re focussing on wordpress file and directory permissions.

This is a working document, which should be subject to updates and revisions

Firstly, there is no reason that I can find..that any of the files or directories in a wordpress installation should be world readable, so to that effect we can assume all permissions will end in a “0”, removing any world-readable permissions.

Secondly, we shouldn’t have the web user (apache, http, www-data for example) owning any of the files. In the very least a human should own the files in the web root, and the web process user should be accessing based on group permissions. The reason for this is, the owner can change file permissions as he wishes..which may well be undesirable in the event that a malicious script is executed for example. Additionally, we want the user to be able to write to files..whereas the web server process usually only a needs to *read* files. So in the very first place we’d do About something like this

[plain]chown -R (your username):(web server group) /path/to/webroot/[/plain]

so in the case of my system, I’d perhaps do something like this:

[plain]chown -R dcr226:apache /var/webroot/www[/plain]

So onto actual permissions for these user/group combinations. As stated before, there is no good wholesale mlb jerseys reason to have anything in the webroot world readable. So from an overarching perspective, files can How be 640 and directories 750. To explain how this works:

Files – 640 permission

Owner has read/write access (in this case a human owner, *not* the web server) (rw)
Group members have read access (in this case the web server’s user – perhaps apache, httpd or www-data) (r)
Everyone else has no access whatsoever (-)

Directories – 750 permission

Basically the same as above, with the exception that directories need to execute in wholesale jerseys order to change directory, etc. So both the owner and group get execution permissions (rwx)

Fixing the file permissions:

So, now we know what file permissions we want to issue..its time to do the work. This can be done as follows:

[plain]find /path/to/webroot -type d -exec chmod 750 {} ;[/plain]

the above command searches (find) through the defined web root, looking for directories (-type d), then executes chmod 750 on each of them. Should take a few seconds to complete.

[plain]find /path/to/webroot -type f -exec chmod 640 {} ;[/plain]

the above command does the same, but this time for actual files, instead of directories. ‘find’ in this case will recurse through directories.


so it looks like centos6 at least ships with the httpd_unified boolean set. With the boolean on, Apache processes can read/write/execute all httpd_sys_content* labels. This isn’t what we want to achieve.

[plain]setsebool -P httpd_unified 0[/plain]

then, we can create read only selinux labelling for the web root using:

[plain]semanage fcontext -a -t httpd_sys_content_t “/path/to/www(.*)?”
restorecon -Rv www[/plain]

now, apache simply isn’t allowed to write to any files, or execute any files in the webroot.


you’ve got a choice here, you can either manually copy/paste and .htaccess settings into the file yourself, or allow apache to write to the file using the following permissions

[plain]chown dcr226:apache /path/to/www/.htaccess
chmod wholesale jerseys 660 /path/to/www/.htaccess[/plain]

then set the correct selinux context..

[plain]semanage fcontext -a -t httpd_sys_rw_content_t /path/to/www/.htaccess
restorecon -v /path/to/www/.htaccess[/plain]

some things need write contexts and permissions…

Apache needs to write to the wp-content/ directory in the very least to add new media, themes and plugins, so we need to make that happen with file permissions and a selinux context.

[plain]find /path/to/webroot/wp-content -type d -exec chmod 770 {} ;

find /path/to/webroot/wp-content -type f -exec chmod 660 {} ;
semanage fcontext -a -t httpd_sys_rw_content_t “/path/to/www/wp-content(.*)?”
restorecon -Rv /path/to/www/wp-content[/plain]

Optional – protect xmlrpc.php

unless you’re posting using a desktop/mobile application, or connecting to the xmlrpc api, then you likely want to prevent access to xmlrpc.php. This can be done a couple of ways, one of which is using your httpd.conf (or relevant config file in the drop directory) as such

Inside of your <Directory>…</Directory> directive

[plain]<Files xmlrpc.php>
Order allow,deny
Deny from all


When Its Time To Update The Whole Thing

When you need to update wordpress core files, if you want wordpress to update itself, then you’re going to need to give the web process writeable permissions cheap jerseys throughout the webroot..

[plain]find /path/to/webroot -type d -exec chmod 770 {} wholesale jerseys ;

find /path/to/webroot -type f -exec chmod 660 {} ;

and if you’re using selinux..

[plain] chcon -R -t httpd_sys_rw_content_t /path/to/webroot


Not forgetting to Breach re-set Welcome them again afterwards!