Posts Tagged #Linux

Posted on by Arnon Erba in Server Logs Explained

(Editor’s note: This post has been updated since publication.)

There are a couple different ways that crackers will try to get into your WordPress installation, and one of them is by using a plain old brute-force attack. This kind of attack requires nothing more than a freely available exploit toolkit, and is not difficult to detect in the server logs. In the first section of this post, I’m going to give an example of what a brute force attack looks like, and then to make things more interesting I’ll discuss some techniques used to mitigate them using Nginx.

The Logs

As you would guess, when one computer makes hundreds of requests for a resource in quick succession, it leaves some pretty serious traces in the server logs (these are real logs, but I removed the server name): - - [22/Jun/2016:19:18:58 -0700] "POST /wp-login.php HTTP/1.1" 200 3848 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" - - [22/Jun/2016:19:18:59 -0700] "POST /wp-login.php HTTP/1.1" 200 3848 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" - - [22/Jun/2016:19:18:59 -0700] "POST /wp-login.php HTTP/1.1" 429 0 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" - - [22/Jun/2016:19:19:00 -0700] "POST /wp-login.php HTTP/1.1" 429 0 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" - - [22/Jun/2016:19:19:00 -0700] "POST /wp-login.php HTTP/1.1" 429 0 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)" - - [22/Jun/2016:19:19:00 -0700] "POST /wp-login.php HTTP/1.1" 429 0 "" "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; 125LA; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)"

Here’s a couple things about these requests that make it obvious that this is a brute-force attack (other than the fact that they go on for about half an hour).

  1. The HTTP method is POST, which indicates data is being sent to the server (i.e. the actual password guesses).
  2. The resource requested is /wp-login.php, which is the default WordPress login page and should rarely be requested, even by legitimate users.

If you look more closely, however, you’ll see something interesting: the HTTP response code that the server returns starts off as 200 OK, but quickly transitions to 429 Too Many Requests. This is one method of fending off brute force attacks with Nginx.

Mitigating WordPress Brute-Force Attacks

Fortunately, WordPress brute-force attacks are not that difficult to defend against without the use of plugins or additional software. We can:

  1. Restrict access to the login page to a curated list of IP addresses,
  2. Explicitly block the IP addresses of known brute-force offenders with Nginx or with a firewall,
  3. Password-protect the login page using HTTP Basic Authentication,
  4. Or, my personal favorite: set up rate-limiting with Nginx to cut down on how many requests attackers can make in a certain period of time.

Restrict Access to Certain IP Addresses

Arguably, the best way to mitigate brute-force attacks is to restrict access to the WordPress login page to only known good IP addresses. Here’s what that looks like with Nginx:

location = /wp-login.php {
    deny all;
    # add your PHP fastcgi config here

This location block explicitly targets the /wp-login.php page and only allows clients using the IP addresses and to access it. All other requests will be met with a 403 Forbidden error message. Keep in mind you will need to add your PHP fastcgi config to this location block as well so that Nginx knows to pass legitimate requests back to PHP. If you’re not familiar with how to do this, either consult the Nginx docs regarding PHP or keep an eye out for a newer post.

This method ensures that attackers will never get access to the login page, but is difficult to maintain if legitimate WordPress users do not have static IP addresses.

Deny Access from Certain IP Addresses

Another solution is to explicitly block brute-force offenders. You can block certain IP addresses from accessing the login page with:

location = /wp-login.php {
    # add your PHP fastcgi config here

If you are familiar with configuring firewalls, you can use firewall commands to block the IP address from accessing anything on your server at all.

While blocking specific IP addresses can be useful, I don’t recommend using this as your only line of defense. For one, any IP address used in a brute force attack is almost certainly a VPN, proxy, or bot IP address. By blocking these, you risk denying access to legitimate users, even if that risk is slight. The main concern is that maintaining a list of IP addresses is tedious and unwieldy and is not a good long-term solution. That’s not to say this approach is useless, however, as you may want to use it in tandem with another one.

With that in mind, the next possible solution is adding a second layer of protection to the WordPress login page with HTTP Basic Auth.

Restrict Access Using HTTP Basic Auth

There are two steps to using HTTP Basic Auth with WordPress and Nginx.

  1. Create the password file
  2. Configure Nginx

I am going to skip the first step in this post, as there are many good existing guides on using openssl or apache2-utils to create a password file (see here or here).

The second step, configuring Nginx, is fairly simple. Just add two lines to your wp-login location block:

location = /wp-login.php {
    auth_basic "Restricted Content";
    auth_basic_user_file /path/to/.password_file;
    # add your PHP fastcgi config here

You can change “Restricted Content” to any phrase you want, as it will be the message that end-users see when they attempt to access the login page. Make sure you enter the correct path to your password file you created as well.

While password-protecting the login page is a valid solution, it has the potential to overly complicate the login process for legitimate users.

Using Rate Limiting in Nginx

Nginx has some great documentation on how to implement rate limiting, but I am going to provide an example of how to optimize it for WordPress. Setting up rate limiting in Nginx is simple, and only requires two components:

  1. We must define a zone in the main nginx.conf file.
  2. We must implement that zone in the WordPress login location block.

To define the zone, we use limit_req_zone and, optionally, limit_req_status. These directives go inside the http block of the main nginx.conf configuration file.

http {
     limit_req_zone $binary_remote_addr zone=wordpress:10m rate=15r/m;
     limit_req_status 429;

The above snippet defines a 10 MB zone named “wordpress” that allows a maximum of 15 requests per minute from any one IP address. The limit_req_zone requires a variable, or key. In this case, the key is $binary_remote_addr, or the IP address of the client. Nginx will use a maximum of 10 MB of memory to store the keys, and if a key exceeds the maximum number of allowed requests, Nginx will terminate the connection and return the status code defined in limit_req_status. The default code is 503 Service Unavailable, but I prefer the more specific 429 Too Many Requests response. Keep in mind that Nginx will display a blank page to the client for non-standard HTTP codes if you have not set a custom error page using the error_page directive.

You can name the zone anything you want (it is named “wordpress” in the example above) and you can also define any rate limit you feel is appropriate. I found that allowing a maximum of 15 requests per minute is restrictive enough to hamper a brute-force attack but is permissive enough not to interfere with end-users who legitimately mistyped their passwords.

To actually use the zone, we must implement it by adding this code to the WordPress login location block:

location = /wp-login.php {
    limit_req zone=wordpress;
    # add your PHP fastcgi config here

This tells Nginx to limit requests to the /wp-login.php page using the parameters specified in the zone we defined above. Make sure you replace “wordpress” with whatever you named your zone in the previous step. Restart or reload Nginx and rapidly refresh your login page to test if the new brute-force protection is working. If you refresh faster than the rate you defined in limit_req_zone, the server will return the status code defined in limit_req_status.

Obligatory note: if you’ve read other guides on how to set up rate limiting with Nginx, you may have seen other syntaxes used, such as limit_req zone=one burst=1 nodelay. The burst and nodelay options are more complex and allow you to control what happens to excess requests. They are not necessary in this context, since we want any excess brute-force attempts to be immediately rejected, but I would highly encourage you to read the documentation for them here.


This is by no means an exhaustive list for preventing brute-force attacks. Other solutions exist in the form of WordPress plugins or intrusion prevention systems such as Fail2ban. However, a lot can be accomplished by correctly configuring Nginx, and the less WordPress plugins you have installed, the better.

Posted on by Arnon Erba in Server Logs Explained

(Editor’s note: The IP addresses in this post have been replaced with reserved IP addresses for documentation purposes. This post has been updated since publication to include more information about the w00tw00t scan.)

What is

If you read last week’s post, you’ll remember that I promised to post a more interesting log excerpt this week. This one is from a pretty common bot scan that you’ll see if you’re running a web server for any length of time, and while it looks scary at first, you likely don’t need to worry if your server is configured properly. - - [21/Jun/2016:06:35:55 -0400] "GET / HTTP/1.1" 400 0 "-" "ZmEu"

In this log excerpt, we see that an IP address that maps to the Netherlands made a GET request for /, a nonexistent resource. However, the server returned a 400 Bad Request error rather than a 404 Not Found.

What This Means for You

Because I didn’t grab the accompanying error log entry that explains why Nginx returned a 400 error, I’m going to skip right to the explanation (spoiler alert). The w00tw00t entries are created by the ZmEu or DFind vulnerability scanners as part of an attempt at banner grabbing. Banner grabbing is an enumeration technique, and in this case the scanner was searching for information about my server that could reveal possible exploits. The process goes something like this: a bot, possibly an infected computer or a proxy server, sends an HTTP GET request with a bogus URI in the hope that the targeted server will respond with some information about its configuration. In my case, Nginx determined that the HTTP request was malformed in some way, so it rejected it with a 400 Bad Request status code. Most likely, the request was missing the Host header, in the hope that my server would fill it in or provide some other information.

The bottom line is that if you’re running a web server, you’re going to come across these requests in your server logs at some point. The Internet is frequently scanned by script kiddies looking for various vulnerabilities, but as long as your server returns a 400 error for any w00tw00t requests, you shouldn’t have to worry. There are a few other variants of this scan as well, including one that makes a request for /

Further Reading

If you want to read more about the w00tw00t scan, here’s some extra resources for more information:

Posted on by Arnon Erba in Server Logs Explained

(Editor’s note: This post has been updated since publication.)


In this post, I’m going to go over some background information about the web server logs I’ll be posting, and then I’ll go over a basic example of what traffic from Googlebot should look like.

The logs I’ll be posting will be in the default combined format for Nginx, which looks like this:

$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"

Let’s break that down. Each section that’s prefaced by a dollar sign indicates one piece of information that will be logged. Nginx will collect this information and then concatenate it so that it is easy to scan. If any of the requested pieces of information are not provided, Nginx will replace it with a hyphen. Here is what each bit of information we’ll be collecting means:

$remote_addr: The IP address of the client.
- (The hyphen here is just a placeholder for readability)
$remote_user: The authenticated user, if one exists.
$time_local: The time the request was processed, based on the server’s time zone settings.
$request: The requested resource. This will include the HTTP method used.
$status: The status code that the web server returned.
$bytes_sent: The number of bytes the server sent to the client.
$http_referer: The referrer URL, or the webpage that sent the visitor to your server using a link.
$http_user_agent: Information regarding what browser or operating system was used to make the request.

Fun fact: You may have noticed that “referer” is spelled incorrectly. This is a misspelling in the actual HTTP specification and you can learn more about that here.

Googlebot Traffic

Now let’s break down a real log, taken from one of my servers. I’m going show how each piece of information in the log matches up to the reference above. - - [18/Jun/2016:08:36:33 -0400] "GET /about-us HTTP/1.1" 200 2233 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +"

$remote_addr is This is a real Googlebot IP address, so we can be fairly certain this is a legitimate request.
$remote_user: Googlebot was not logged into the server, so this field is blank.
$time_local: The request came in at 8:36 AM on June 18th, 2016.
$request: Googlebot requested the /about-us page using a GET request.
$status: The server responded with an HTTP 200 OK status code, indicating that it was able to successfully complete the request.
$bytes_sent: The server sent back 2,233 bytes.
$http_referer: Googlebot did not list a referrer URL.
$http_user_agent: This custom user agent listing tells us that the request was made by Googlebot and not by a typical web browser.

Join us next week for an explanation of some less benign requests.

Posted on by Arnon Erba in General

I recently set up a fresh installation of Ubuntu 14.04 alongside Windows 7 on my laptop. The last release of Ubuntu that I used regularly was the previous long-term release, 12.04, and I’ve avoided the in-between releases because they didn’t run as smoothly on the older Pentium 4-based hardware I had. Also, I sold my dedicated Core 2 Duo-powered Ubuntu 12.04 laptop a number of months ago, leaving me with only Pentium 4 systems to experiment with Ubuntu on. With the release of 14.04, however, I decided to dual-boot the new flavor of Ubuntu on my previously Windows-only Core i3-based laptop. I recently finished upgrading the laptop to a 240 GB solid state drive (a worthwhile performance upgrade, if I may say) and hadn’t wanted to mess around with the drive until I had let it work in for a while. Given that 14.04 has been out for a while now and has received some updates, I figured that now was the time to install it and to find out from first hand experience what’s improved in Ubuntu since 12.04. I don’t intend for this to be a full review of Ubuntu; instead, I would like to cover some improvements in the latest LTS release and how it operates on newer hardware.

My first move in dual-booting Windows 7 and Ubuntu was to shrink my Windows partition to accommodate a new 20 GB partition for Ubuntu. I chose to create an empty partition beforehand instead of having the Ubuntu installer partition the disk, for whatever reason. This doesn’t really affect the installation process, but it did surprise me when I chose “install Ubuntu alongside Windows 7”. Instead of asking me where I wanted to put my Ubuntu installation, it automatically found and chose the empty partition. Luckily, this was where I wanted Ubuntu, but I was surprised at the lack of options the installer gave me.

I am impressed at the improvement in Ubuntu’s graphics driver support. Granted, my notebook does not have dedicated Nvidia or AMD graphics – good for Ubuntu, not so great for gaming – but Ubuntu detected the on-chip graphics as Sandybridge Mobile without any additional configuration. Unity seems faster overall, and window animations are more polished. There are also a few new UI improvements, such as the added option to show window menus in the window title bar instead of in the top bar. Despite these improvements, I had to make some custom changes to get the system running just how I like it.

This is one of the great things about Linux, though. It may not work perfectly at first, but there’s plenty of ways to get it working just how you want it to. Most of the tweaks I made related to Grub and dual-booting, but I also set up a custom script to prevent the brightness from returning to 100% whenever the system rebooted, fixed an issue with Chrome not opening from the Dash, shrank the menu and title bars to 0.875 scale, and turned off the Amazon search suggestions in the Dash. That may sound like a fairly small amount of adjustments, but that was about all I had to do. Overall, Ubuntu 14.04 worked well straight out of the box and I was able to get on to using it within a day after only a few hundred megabytes of updates. Here’s looking at you, four-year-old Windows installation disc.

In the end, Ubuntu 14.04 is even smoother and more refined than 12.04 and is a perfectly good candidate for everyday use. LibreOffice 4.2, included by default, is better than ever, and if you don’t need legacy Windows software Ubuntu is the way to go.

Posted on by Arnon Erba in General

This year I had the opportunity to do something I’ve been wanting to do for a while. I was able to use my Ubuntu laptop full-time as my main computer both in and out of the house. It was an interesting experience, especially as everyone I was working with had either Windows or Mac OS X. Here’s what happened over the month I used Ubuntu full-time, and here’s what I thought of it.

The first question you’ll probably have after reading the introductory paragraph is, “Why did you only make it one month? Doesn’t that mean you decided Ubuntu wouldn’t work full-time?” The simple answer is, no. The main reason I stopped using my Ubuntu laptop full-time because of hardware issues unrelated to Ubuntu. The long answer covers what I thought of Ubuntu over that month and how well it worked out for me.

Let’s start with the computer I was using. If you’ve seen some of my previous posts, you’ll probably know that my current Ubuntu machine is a Sony Vaio VGN-150e with a 2 GHz Core 2 Duo and 2 GB of RAM. (Ed: not anymore.) It’s not new, but it is perfectly adequate for Ubuntu. The problem lies with the fact that the laptop has a dead battery. Not a worn-out battery, a completely dead battery. I have a suspicion that the problem may even lie with the charging circuit and not the battery itself. This means that when the laptop is unplugged, it instantly dies just like a desktop would. If I happened to be working somewhere without a power outlet, I wasn’t going to be working. This complicated matters to the point where I finally gave up on using the laptop full-time.

But what about Ubuntu? During my experience, I decided that Ubuntu is perfectly usable on a day-to-day basis. In fact, I only discovered one issue: compatibility with traditional Windows programs. Everyone I worked with had Microsoft Office, which made the formatting of many shared documents I had to work with look very odd when opened with LibreOffice. I know LibreOffice isn’t part of Ubuntu and it isn’t Ubuntu’s fault that document formats aren’t universal, but I can’t see any way of getting around this issue except for buying Microsoft Office or forcing everyone else to use Ubuntu. When I finally switched from the Sony to my newer Windows 7 Acer laptop, I chose to use Windows because I already had purchased Microsoft Office and felt that I might as well make use of it.

On everything else except for compatibility, however, Ubuntu did well. I didn’t have a single crash, and I enjoyed the security of using Linux. The bottom line is that my main issues turned out to be hardware-related and that if I had a new laptop that was built for Ubuntu and didn’t have to constantly work with Microsoft Office documents, I would still be using Ubuntu full-time right now. As it is, I use Windows during the day and Ubuntu occasionally.

Posted on by Arnon Erba in How-To Guides

The login sound is disabled by default in Ubuntu 12.04+. The only sound that plays by default when an Ubuntu machine boots up is the drum beat sound that plays when the login screen loads. The sound that plays when the user logs in is disabled by default, but it is possible to enable and change it to anything you want. You can also disable the drum beat sound, if you would like to. See this Ask Ubuntu post for instructions on how to disable it.

To enable and change the Ubuntu login sound, you won’t need any extra tools aside from a terminal window and your own custom sound in .ogg format.

First, you will need to unhide and enable Ubuntu’s login sound startup process. Open up a terminal window, copy/paste this line of code, and run it.

sudo gedit /usr/share/gnome/autostart/libcanberra-login-sound.desktop

This will open up the file libcanberra-login-sound.desktop in Gedit with root privileges so that you can edit it. You should see this script open in Gedit:


Edit the last line in the file and change NoDisplay=true to NoDisplay=false.

Once you’ve done that, save and close the file and open the Startup Applications dialogue. The option GNOME Login Sound should now be visible.


Check the box next to it. Now, the default Ubuntu login sound should play when you log in to your account.

Now you can change the default sound. To do this, you will need to replace the default sound file with one of your choosing. However, to edit the login sound file, you must access it with root privileges. You can do this through a terminal session or by opening Nautilus, the file manager, with root privileges.

Method One: Terminal Session

Convert your custom sound file to .ogg format, name it desktop-login.ogg, and move it to the Downloads folder.

Then, open a terminal window and run this command:

cd /usr/share/sounds/ubuntu/stereo

This will open the directory containing the login file in the terminal window.


You will need to replace the file desktop-login.ogg with one of your own. You can back up the original file and then replace it with your own all inside the terminal window by running these commands:

sudo cp desktop-login.ogg desktop-login.ogg.old
sudo cp ~/Downloads/desktop-login.ogg

The first command renames the original desktop-login.ogg file to desktop-login.ogg.old. The file will remain untouched in its original directory, but Ubuntu will ignore it and use any custom desktop-login.ogg that you add to the directory.

The second command imports your custom desktop-login.ogg file to the system sounds directory. Done!

Log out and log back in to test your new login sound.

Method Two: Open Nautilus as Root

This method uses the file manager’s GUI to perform the sound file switch.

Open a terminal window and run this command:

gksu nautilus

This will open Nautilus with root privileges so that you can edit the login sound file. Browse to /usr/share/sounds/ubuntu/stereo and click on desktop-login.ogg. Press F2 and rename the file to desktop-login.ogg.old. This is the backup of the original file. The file will remain untouched, but Ubuntu will ignore it and use any .ogg file with the name desktop-login that you add to the directory.


Now, open a new window and browse to the location that your custom sound file is stored. It must also be named desktop-login.ogg, but it can be any file you want. Copy/paste your custom file into the root window we were just using. Done!

Log out and log back in to test out your new login sound.