This is arguably the most important chapter in the book. Try as we may to prevent attackers compromising our systems, there will always be a chance that one will succeed. Damage, actual and reputational, can be minimized by taking action early. Damage can even be prevented by acting as soon as unauthorized access is attempted, before an attacker succeeds in gaining entry.
In order to respond rapidly to unauthorized access, you must generate logs, and you or an operations team must monitor them. This may also be required by compliance departments. If you have several applications and servers, manually looking through log files becomes unsustainable.
In this chapter, we will look at how to automatically consolidate log files, from one or several servers, so they can be viewed and searched in one place. We will set up the Elastic Stack, or ELK, which is a popular open source toolset for logging and monitoring.
We will also look at how to create custom logging for our application, beyond what is provided by Apache by default, and create alerts so that we don’t miss important security events.
12.1 Logging, Aggregating, and Analytics
Even if you have only one server, the system log file, Apache error file, Apache access log, and database log file can all contain entries indicating that attackers have compromised your system, or attempted to do so. If you have your application running on several load-balanced servers, or have multiple applications, it is infeasible to regularly read each log file.
Timestamp
Client IP address
URL
Response code
Reading a single, aggregated log is better than reading several logs and several hosts because you only have to look in once place. However, it is difficult to read and analyze because it is large and entries appear in the order sent, not necessarily in an order that shows a sequence of related events. It is difficult to find important entries and to follow an attack vector. Analytic engines help with this task by providing a query language and dashboards. They can also generate alerts, sending emails or notifications to tools such as Slack.
One popular toolset is Elastic Stack, also called ELK. Later in this chapter, we will use this stack in an exercise to implement logging and monitoring for Coffeeshop.
12.2 The ELK Stack
ELK stands for Elasticsearch, Logstash, and Kibana. They are three tools by the company Elastic that, used together, are a popular open source log file aggregation and monitoring platform.
Elasticsearch is a REST-based search engine based on the Lucene library. Logstash is a log aggregator. Kibana is a web-based visualization tool.
A fourth component, Beats, is often used with ELK. Beat abstracts the log collection from Logstash. Beats components for specific purposes, such as extracting log files or system metrics, send data to Logstash, which aggregates and stores it. Elasticsearch provides the API to query the logs and metrics. Kibana adds a GUI. As the stack has now grown beyond the original three components, ELK is also referred to as Elastic Stack.
where n is an index number (000001, 000002, etc.) created by log file rotation. When using Kibana to view logs, we define an index pattern for it to search. By default, this is *, which matches all indexes.
Loading Log Files with Logstash
In the next exercise, we will add ELK to Coffeeshop and use it to view access logs. We will not use Beats as the built-in log file parsing functionality in Logstash is sufficient for our purposes.
The first section, input { ... }, defines the file sources. We are loading all files from the /var/log/apache2 directory, starting at the beginning of each file. Logstash understands file rotation. When a log file reaches a certain size, Apache appends a number to it and creates a new log file. Once log files reach a certain age, they are compressed. Logstash understands this and ensures log entries are not duplicated when it parses them.
Logstash assigns a type to each log file. It puts this in the type field. The default is _doc.
message: The whole line from the log file
timestamp: Parsed from a string matching Day Month Date Hour:Minute:Seconds.Milliseconds Year
hostname: Hostname the log was extracted from
path: The full path of the file
We do this so that we can differentiate between error and access log entries when searching.
The grok filter extracts additional fields using regular expressions. We are using it to extract fields from the message field. The regular expressions are defined using the syntax %{PATTERN}. There are a number of built-in patterns. The one we are using is COMBINEDAPACHELOG, which parses Apache access log file entries into their constituent fields. A full list is available in Logstash’s logstash-patterns-core GitHub repository.1
This replaces the default parsing for the timestamp field. Apache error log timestamps are formatted according to the Logstash default. Apache access log timestamps are not, so we need to declare the correct syntax.
The last section in the file, output { ... }, defines where to send the parsed log entries. We are sending them to two locations: elasticsearch and stdout. For elasticsearch, we define the host and port Elasticsearch is running on. For stdout, we use the rubydebug plug-in to format it. This is the default value. Another possibility is json.
We will install Elasticsearch, Logstash, and Kibana and configure Logstash to log Apache logs. Then we will use Elasticsearch to view login attempts to the Coffeeshop admin console.
Installing ELK
- 1.
Add Elastic to the Apt sources list so that we can fetch the latest versions with apt-get.
- 2.
Install Elasticsearch, Logstash, and Kibana using apt-get.
- 3.
Configure Logsearch to use fewer workers than the default (on a development server, we only need one worker).
- 4.
Add the configuration file to read Apache log files.
- 5.
Bind the Kibana web server to address 0.0.0.0 so that we can view it from a browser on the host computer.
- 6.
Enable and start Kibana (Elasticsearch and Logstash are auto-enabled and started during installation).
For details, read the install-elk.sh script.
Configure Kibana to View Apache Access Logs
Visit the URL http://10.50.0.2:5601 in a browser to open Kibana. It may take a minute or two to start. If you get a welcome screen, click Explore on my own. Click the three horizontal bars menu icon at the top left and select Discover under Analytics. The first time you do this on a Kibana installation, you will be taken to a screen to create an index pattern. Click the Create index pattern button, and in the next screen, type * in the Name field and select @timestamp from the Timestamp drop-down (see Figure 12-1). Click Create index pattern to save. Now select Discover under Analytics from the menu again.
You should now see a screen like Figure 12-2 (if you have a wizard popover, close it first). The log entries you see in the section on the right will differ.
Click + Add Filter (highlighted in the figure). In the popup, select type from the Field drop-down (not _type with the leading underscore character), select is from the Operator drop-down, and enter apache_access under Value. This is shown in Figure 12-3. Click Save.
In another tab, visit a page in Coffeeshop (e.g., http://10.50.0.2). Now click the Refresh button in Kibana (top right). You should see your page access logged in the search results.
You can save the filter for later use by clicking on the disk icon to the left of the search bar and clicking the Save Current Query button. The search will then be available in the drop-down list when you click the disk icon again.
Viewing Admin Login Attempts
Let’s create a more complex query to view accesses on the admin console login page. This time we need to use Elasticsearch’s query language, DSL. Leave the type: apache_access filter we previously created as it is. Click + Add Filter again.
Click Save. This is a simple wildcard search on the request field, which contains the URL from the HTTP request.
Go back to Kibana and click Refresh. You should see your login attempt.
12.3 Creating Custom Log Files
One of the drawbacks of form-based authentication is that unsuccessful login results in a 200 OK response code. Django simply redisplays the login page but with an error as part of the HTML body. A successful login results in a 302 Found redirect, but using the response code to differentiate between a successful and a failed login is error-prone.
We would like to see successful and unsuccessful admin console logins in our Kibana control panel. We can create our own log file to report them to. We can also send email alerts whenever there is a successful login.
We could create our own custom login and logout messages and also send them to the console, but in order for ELK to parse them into fields, we need to have a common format for each line in this file. A better solution is to create a separate file to log them to. We can do this in Django by setting the LOGGING variable in settings.py.
Formatters define how log messages are formatted, for example, by prepending a timestamp and log level.
Handlers define how log entries are written, for example, streaming to the console or writing to a file. Handlers can use a custom formatter.
Loggers define a name that can be associated with a handler. In Python, retrieving that handler by name selects the logging destination. A logger also has a log level, for example, ERROR or DEBUG.
To report login and logout messages to a separate file, we define a formatter to prepend a timestamp when logging the message, a handler that writes to a file using that formatter, and a logger that lets us write to that handler in Python code. We will do this in the next exercise.
Django handles login and logout requests within its auth module. Rather than having to edit this code to add logging messages, it sends signals when certain actions occur, such as a user logging in, out, or failing a login attempt. We can bind handlers to these signals in Python and write to our log file from those. We will do this in the next exercise too.
- 1.
Create a new Django logger.
- 2.
Receive login and logout signals and write log entries.
- 3.
Configure Logstash to receive and parse the log messages.
- 4.
Configure Kibana to view the log messages.
The code for this exercise is in vagrant/snippets/loginalert.
Create a Django Logger
The 'formatters': { ... } section prepends a timestamp to the log messages. The 'handlers': { ... } section defines two handlers: console, which appends to the console, and another, login, that writes to a new file. The 'root' { ... } section defines the default logger that is used when no others match. The 'loggers': { ... } section defines two loggers: one called django, which writes to the console, and one called login, which writes to our new login log file.
Handle Login and Logout Events
We have one function for each of four signals sent by the auth module. The @receiver decorator binds each function to a signal. In each case, we want to send a message to the login logger. We arbitrarily choose level INFO. We will log the username, IP address, and URI as well as success or failure.
Configure Logstash
Copy this file to /etc/logstash/conf.d/apache.conf. Make sure you either call it apache.conf or you delete the old apache.conf. If both files are in there, the log entries will be processed twice.
The match line in grok parses the message field into some new fields. TIMESTAMP_ISO8601, WORD, etc., are built-in Logstash templates. We are reusing some fields that have already been created when parsing the Apache access.log file, for example, for the username. The [user][identity] syntax means the value will be placed in a variable called user.identity.
The match line within date parses the ISO 8601 timestamps that were extracted by grok. Without this, Logstash would use the time it processed the log entry rather than the timestamp contained within it.
from inside the Coffeeshop VM.
Configure Kibana
in a web browser.
As in the last exercise, we are going to create a filter, this time on the new django_login type we created previously. From the two-horizonal-bar menu at the top right of Kibana, select Discover under Analytics. Click + Add filter, as in Figure 12-2. For Field, select type. For Operator, select is. Under Value, enter django_login and click Save.
Let’s make a few login and logout records. In another tab or browser window, visit
http://10.50.0.2/account/login/
and make a failed login attempt (e.g., with xxx as the username and password). Now log in correctly as either bob or alice and then logout. Go back to Kibana and click the Refresh button at the top right. You should see a screen similar to Figure 12-6.
source.address
user.identity
action
status
12.4 Creating Alerts for Security Events
Kibana also supports sending alerts to channels such as Email or Slack. This feature is part of the paid version of Kibana, but there is an open source tool that has similar functionality. It is called ElastAlert, and we will use it in the next exercise to send an Email whenever there is a successful login as the admin user.
ElastAlert is a Python program that queries Elasticsearch. It runs rules, each of which is defined in a YAML file. The rules query Elasticsearch and apply a filter, just like Kibana. When an entry matches a filter, the rule performs an action such as sending an Email.
Any: Performs the action on everything that matches the filter
Blacklist: Performs the action if a field matches an entry in a blacklist
Whitelist: Performs the action if a field does not match an entry in a whitelist
Change: Performs the action if a field changes
Frequency: Performs the action if there are a certain number of events in a given timeframe
Spike: Performs the action if the event occurs a defined multiplication factor more than in the previous time period
Flatline: Performs the action if the number of events is under a given threshold
New term: Performs the action when a value occurs in a field for the first time
Cardinality: Performs the action when the total number of unique values in a field is above or below a threshold
Metric aggregation: Performs the action when the valuer of a metric is higher or lower than a threshold within a calculation window
Spike aggregation: Similar to spike but on a metric within a calculation window
Percentage match: Performs the action when the percentage of document in the match bucket is higher or lower than a threshold within a calculation window
In the exercise, we will use the Any type. For more details on the others, see the ElastAlert documentation.2
ElastAlert is designed to be run as a daemon, for example, with the Python zdaemon process controller or as a systemd service.
This exercise builds on the previous two, so make sure you have completed them first. We will use ElastAlert to send an email whenever someone successfully logs in as admin. We have MailCatcher set up in the Coffeeshop VM, so we will use it as our SMTP server.
Configuring ElastAlert
tell ElastAlert where to find the Elasticsearch service.
We also tell Elasticsearch to run every minute and buffer for 15 minutes in case some alerts are not received in real time.
For the Elasticsearch host and port, enter localhost and 9200, respectively. Enter f for Use SSL. You can choose the defaults for all other options.
Creating the Rule
The file also contains connection details for the SMTP server.
Running ElastAlert
Now logout from Coffeeshop if you are logged in already and then visit
http://10.50.0.2/admin
in your web browser. Log in as user admin.
We have set the --verbose flag on ElastAlert, so you should see it detect the login. It may take a minute or two as it only runs every 60 seconds. Open MailCatcher by visiting
http://10.50.0.2:1080
in your browser. You should see a screen like Figure 12-8.
12.5 Summary
Logging and monitoring are essential for spotting and responding to intrusions or intrusion attempts. Web servers, database servers, operating systems, and web applications all produce logs, but monitoring them regularly is difficult without an aggregation and search toolset such as the ELK stack.
Logging applications such as Kibana don’t always get looked at regularly enough to act on critical security events promptly. Alert tools such as ElastAlert can filter events and send them to other channels that are monitored more frequently, such as Email or Stack.
In this chapter, we looked at how ELK can be used to monitor Apache error and access logs. We also created a log file that records Admin user login and logout events and configured ElastAlert to send emails whenever there is a successful Admin user login.
In the next chapter, we look at how third-party tools, as well as people themselves, can introduce vulnerabilities and what we can do to defend against them.