Blocking Common Attacks using ModSecurity 2.5: Part 3
Read Part One of Blocking Common Attacks using ModSecurity 2.5 here.Read Part Two of Blocking Common Attacks using ModSecurity 2.5 here.Source code revelation
Normally, requesting a file with a .php extension will cause mod_php to execute the PHP code contained within the file and then return the resulting web page to the user. If the web server is misconfigured (for example if mod_php is not loaded) then the .php file will be sent by the server without interpretation, and this can be a security problem. If the source code contains credentials used to connect to an SQL database then that opens up an avenue for attack, and of course the source code being available will allow a potential attacker to scrutinize the code for vulnerabilities.
Preventing source code revelation is easy. With response body access on in ModSecurity, simply add a rule to detect the opening PHP tag:
Prevent PHP source code from being disclosed
SecRule RESPONSE_BODY "
Preventing Perl and JSP source code from being disclosed works in a similar manner:
# Prevent Perl source code from being disclosed
SecRule RESPONSE_BODY "#!/usr/bin/perl" "deny,msg:'Perl
source code disclosure blocked'"
# Prevent JSP source code from being disclosed
SecRule RESPONSE_BODY "
Directory traversal attacks
Normally, all web servers should be configured to reject attempts to access any document that is not under the web server's root directory. For example, if your web server root is /home/www, then attempting to retrieve /home/joan/.bashrc should not be possible since this file is not located under the /home/www web server root. The obvious attempt to access the /home/joan directory is, of course, easy for the web server to block, however there is a more subtle way to access this directory which still allows the path to start with /home/www, and that is to make use of the .. symbolic directory link which links to the parent directory in any given directory.
Even though most web servers are hardened against this sort of attack, web applications that accept input from users may still not be checking it properly, potentially allowing users to get access to files they shouldn't be able to view via simple directory traversal attacks. This alone is reason to implement protection against this sort of attack using ModSecurity rules. Furthermore, keeping with the principle of Defense in Depth, having multiple protections against this vulnerability can be beneficial in case the web server should contain a flaw that allows this kind of attack in certain circumstances.
There is more than one way to validly represent the .. link to the parent directory. URL encoding of .. yields % 2e% 2e, and adding the final slash at the end we end up with % 2e% 2e% 2f(please ignore the space).
Here, then is a list of what needs to be blocked:
../
..% 2f
.% 2e/
% 2e% 2e% 2f
% 2e% 2e/
% 2e./
Fortunately, we can use the ModSecurity transformation t:urlDecode. This function does all the URL decoding for us, and will allow us to ignore the percent-encoded values, and thus only one rule is needed to block these attacks:
SecRule REQUEST_URI "../" "t:urlDecode,deny"
Blog spam
The rise of weblogs, or blogs, as a new way to present information, share thoughts, and keep an online journal has made way for a new phenomenon: blog comments designed to advertise a product or drive traffic to a website.
Blog spam isn't a security problem per se, but it can be annoying and cost a lot of time when you have to manually remove spam comments (or delete them from the approval queue, if comments have to be approved before being posted on the blog).
Blog spam can be mitigated by collecting a list of the most common spam phrases, and using the ability of ModSecurity to scan POST data. Any attempted blog comment that contains one of the offending phrases can then be blocked.
From both a performance and maintainability perspective, using the @pmFromFile operator is the best choice when dealing with large word lists such as spam phrases. To create the list of phrases to be blocked, simply insert them into a text file, for example, /usr/local/spamlist.txt:
viagra
v1agra
auto insurance
rx medications
cheap medications
...
Then create ModSecurity rules to block those phrases when they are used in locations such as the page that creates new blog comments:
#
# Prevent blog spam by checking comment against known spam
# phrases in file /usr/local/spamlist.txt
#
SecRule ARGS "@pmFromFile /usr/local/spamlist.txt" "t:
lowercase,deny,msg:'Blog spam blocked'"
Keep in mind that the spam list file can contain whole sentences—not just single words—so be sure to take advantage of that fact when creating the list of known spam phrases.
SQL injection
SQL injection attacks can occur if an attacker is able to supply data to a web application that is then used in unsanitized form in an SQL query. This can cause the SQL query to do completely different things than intended by the developers of the web application. Consider an SQL query like this:
SELECT * FROM user WHERE username = '%s' AND password = '%s';
The flaw here is that if someone can provide a password that looks like ' OR '1'='1, then the query, with username and password inserted, will become:
SELECT * FROM user WHERE username = 'anyuser' AND password = ''
OR '1'='1';
This query will return all users in the results table, since the OR '1'='1' part at the end of the statement will make the entire statement true no matter what username and password is provided.
Standard injection attempts
Let's take a look at some of the most common ways SQL injection attacks are performed.
Retrieving data from multiple tables with UNION
An SQL UNION statement can be used to retrieve data from two separate tables. If there is one table named cooking_recipes and another table named user_credentials, then the following SQL statement will retrieve data from both tables:
SELECT dish_name FROM recipe UNION SELECT username, password
FROM user_credentials;
It's easy to see how the UNION statement can allow an attacker to retrieve data from other tables in the database if he manages to sneak it into a query. A similar SQL statement is UNION ALL, which works almost the same way as UNION—the only difference is that UNION ALL will not eliminate any duplicate rows returned in the result.
Multiple queries in one call
If the SQL engine allows multiple statements in a single SQL query then seemingly harmless statements such as the following can present a problem:
SELECT * FROM products WHERE id = %d;
If an attacker is able to provide an ID parameter of 1; DROP TABLE products;, then the statement suddenly becomes:
SELECT * FROM products WHERE id = 1; DROP TABLE products;
When the SQL engine executes this, it will first perform the expected SELECT query, and then the DROP TABLE products statement, which will cause the products table to be deleted.
Reading arbitrary files
MySQL can be used to read data from arbitrary files on the system. This is done by using the LOAD_FILE() function:
SELECT LOAD_FILE("/etc/passwd");
This command returns the contents of the file /etc/passwd. This works for any file to which the MySQL process has read access.
Writing data to files
MySQL also supports the command INTO OUTFILE which can be used to write data into files. This attack illustrates how dangerous it can be to include user-supplied data in SQL commands, since with the proper syntax, an SQL command can not only affect the database, but also the underlying file system.
This simple example shows how to use MySQL to write the string some data into the file test.txt:
mysql> SELECT "some data" INTO OUTFILE "test.txt";
Preventing SQL injection attacks
There are three important steps you need to take to prevent SQL injection attacks:
Use SQL prepared statements.
Sanitize user data.
Use ModSecurity to block SQL injection code supplied to web applications.
These are in order of importance, so the most important consideration should always be to make sure that any code querying SQL databases that relies on user input should use prepared statements. A prepared statement looks as follows:
SELECT * FROM books WHERE isbn = ? AND num_copies
This allows the SQL engine to replace the question marks with the actual data. Since the SQL engine knows exactly what is data and what SQL syntax, this prevents SQL injection from taking place.
The advantages of using prepared statements are twofold:
They effectively prevent SQL injection.
They speed up execution time, since the SQL engine can compile the statement once, and use the pre-compiled statement on all subsequent query invocations.
So not only will using prepared statements make your code more secure—it will also make it quicker.
The second step is to make sure that any user data used in SQL queries is sanitized. Any unsafe characters such as single quotes should be escaped. If you are using PHP, the function mysql_real_escape_string() will do this for you.
Finally, let's take a look at strings that ModSecurity can help block to prevent SQL injection attacks.
What to block
The following table lists common SQL commands that you should consider blocking, together with a suggested regular expression for blocking. The regular expressions are in lowercase and therefore assume that the t:lowercase transformation function is used.
SQL code
Regular expression
UNION SELECT
unions+select
UNION ALL SELECT
unions+alls+select
INTO OUTFILE
intos+outfile
DROP TABLE
drops+table
ALTER TABLE
alters+table
LOAD_FILE
load_file
SELECT *
selects+*
For example, a rule to detect attempts to write data into files using INTO OUTFILE looks as follows:
SecRule ARGS "intos+outfile" "t:lowercase,deny,msg:
'SQL Injection'"
The s+ regular expression syntax allows for detection of an arbitrary number of whitespace characters. This will detect evasion attempts such as INTO OUTFILE where multiple spaces are used between the SQL command words.
Website defacement
We've all seen the news stories: "Large Company X was yesterday hacked and their homepage was replaced with an obscene message". This sort of thing is an everyday occurrence on the Internet.
After the company SCO initiated a lawsuit against Linux vendors citing copyright violations in the Linux source code, the SCO corporate website was hacked and an image was altered to read WE OWN ALL YOUR CODE—pay us all your money. The hack was subtle enough that the casual visitor to the SCO site would likely not be able to tell that this was not the official version of the homepage:
The above image shows what the SCO homepage looked like after being defaced—quite subtle, don't you think?
Preventing website defacement is important for a business for several reasons:
Potential customers will turn away when they see the hacked site
There will be an obvious loss of revenue if the site is used for any sort of e-commerce sales
Bad publicity will tarnish the company's reputation
Defacement of a site will of course depend on a vulnerability being successfully exploited. The measures we will look at here are aimed to detect that a defacement has taken place, so that the real site can be restored as quickly as possible.
Detection of website defacement is usually done by looking for a specific token in the outgoing web pages. This token has been placed within the pages in advance specifically so that it may be used to detect defacement—if the token isn't there then the site has likely been defaced. This can be sufficient, but it can also allow the attacker to insert the same token into his defaced page, defeating the detection mechanism. Therefore, we will go one better and create a defacement detection technology that will be difficult for the hacker to get around.
To create a dynamic token, we will be using the visitor's IP address. The reason we use the IP address instead of the hostname is that a reverse lookup may not always be possible, whereas the IP address will always be available.
The following example code in JSP illustrates how the token is calculated and inserted into the page.
%s",
tokenHashed));
%>
Assuming the background of the page is white, the markup will ensure it is not visible to website viewers.
Now for the ModSecurity rules to handle the defacement detection. We need to look at outgoing pages and make sure that they include the appropriate token. Since the token will be different for different users, we need to calculate the same MD5 sum token in our ModSecurity rule and make sure that this token is included in the output. If not, we block the page from being sent and sound the alert by sending an email message to the website administrator.
#
# Detect and block outgoing pages not containing our token
#
SecRule REMOTE_ADDR ".*" "phase:4,deny,chain,t:md5,t:hexEncode,
exec:/usr/bin/emailadmin.sh"
SecRule RESPONSE_BODY "!@contains %{MATCHED_VAR}"
We are placing the rule in phase 4 since this is required when we want to inspect the response body. The exec action is used to send an email to the website administrator to let him know of the website defacement.
ModSecurity 2.5
Prevent web application hacking with this easy to use guide
Secure your system by knowing exactly how a hacker would break into it
Covers writing rules in-depth and Modsecurity rule language elements such as variables, actions, and request phases
Covers the common attacks in use on the Web, and ways to find the geographical location of an attacker and send alert emails when attacks are discovered
Packed with many real-life examples for better understanding
http://www.packtpub.com/modsecurity-2-5/bookBrute force attacks
Brute force attacks involve an attacker repeatedly trying to gain access to a resource by guessing usernames, passwords, email addresses, and similar credentials. They can be incredibly effective if no protection is in place, since most users choose passwords that are short and easy to remember. Furthermore, most users will use nearly identical passwords on all websites for which a login is required, and so compromise of one password can lead to the user having his account compromised at a whole range of other sites.
A good way to defend against brute force attacks is to allow a certain number of login attempts, say three, and after that start delaying or blocking further attempts. Let's see how we can use ModSecurity to accomplish this.
If your login verification page is situated at
yoursite.com/login, then the following rules will keep track of the number of login attempts by users:
#
# Block further login attempts after 3 failed attempts
#
# Initalize IP collection with user's IP address
SecAction "initcol:ip=%{REMOTE_ADDR},pass,nolog"
# Detect failed login attempts
SecRule RESPONSE_BODY "Username does not exist" "phase:4,pass,setvar:
ip.failed_logins=+1,expirevar:ip.failed_logins=60"
# Block subsequent login attempts
SecRule IP:FAILED_LOGINS "@gt 3" deny
The rules initialize the ip collection and increase the field ip.failed_logins after each failed login attempt. Once more than three failed logins are detected, further attempts are blocked. The expirevar action is used to reset the number of failed login attempts to zero after 60 seconds, so the block will be in effect for a maximum of 60 seconds.
Another approach is to start delaying requests once the threshold number of login attempts has been reached. This has the advantage of not denying access in case a legitimate user has actually forgotten his password and needs more attempts to remember it. Here are the rules to do that:
#
# Throttle login attempts after 3 failed attempts
#
SecAction "initcol:ip=%{REMOTE_ADDR},pass,nolog"
SecRule RESPONSE_BODY "Username does not exist" "phase:4,pass,setvar:
ip.failed_logins=+1,expirevar:ip.failed_logins=10"
SecRule IP:FAILED_LOGINS "@gt 3" "phase:4,allow,pause:3000"
The pause action is what delays the request, and the time specified is in milliseconds, so the above will delay the response for three seconds once the limit of three failed login attempts has been exceeded.
Directory indexing
When a user requests an URL like
http://www.example.com/, with no filename specification, Apache will look for the file specified by the DirectoryIndex setting (for example index.html). If this file is found, it is served to the user. If it doesn't exist, what happens next is determined by whether the Apache option called Indexes is enabled or not.
The Indexes option can be enabled for a directory in the following way:
Options +Indexes
If the Indexes option is active then Apache will generate a directory listing and display it to the user if the default DirectoryIndex file is not found. This listing contains the names of all files and sub-directories in the requested directory, and this can be a problem for several reasons:
Files that were never meant to be publicly disclosed can be requested by the user, even if they are not linked from anywhere
Names of subdirectories are displayed, and again this may lead to the user wandering into directories that were never meant for public disclosure
In a perfect world, you would never have files under the web server root that users should not be able to download, and all directories or files requiring authorization should be protected by the appropriate HTTP authentication settings. However, in the real world, files and directories do sometimes end up under the web server root even when they are not meant to be accessible by all users. Therefore it makes sense to turn off directory indexing so that this listing is never generated:
Options -Indexes
Even with this in place, sometimes directory indexing can get turned back on—configuration files get edited or replaced with defaults. One option would be to comment out the line for the mod_autoindex module in the Apache configuration file:
#
# Disable directory indexing
#
# LoadModule autoindex_module modules/mod_autoindex.so
However, even this can fail should the configuration file be returned to its default at some point, or if a web server vulnerability causes the directory index to be returned even though Options -Indexes is set. Consider for example the vulnerability discovered in 2001 that affected Apache version 1.3.20 and earlier, described as follows in the changelog for Apache when the corrected version 1.3.22 was released:
A vulnerability was found when Multiviews are used to negotiate the directory index. In some configurations, requesting a URI with a QUERY_STRING of M=D could return a directory listing rather than the expected index page.
This shows that unexpected circumstances can cause directory indexes to be returned even when the web server administrator does everything correctly. Therefore, in keeping with the Defense in Depth principle, adding a precautionary set of rules to ModSecurity to block any directory index from escaping the web server can be a good idea.
These rules will block the Apache directory index from being returned:
#
# Prevent directory listings from accidentally being returned
#
SecRule REQUEST_URI "/$" "phase:4,deny,chain,log,
msg:'Directory index returned'"
SecRule RESPONSE_BODY "Index of /"
The above rule chain is placed in phase 4, since we need to examine the response body for the telltale signature Index of /, which is what Apache returns in directory index pages. This string could potentially be contained within regular HTML documents, so we do an additional check in the first rule—the request URI has to end with a forward slash, which it does when the user requests a directory. Even if the user were to request /example, without a trailing slash, the Apache module mod_dir will issue a 301—Moved permanently redirect to /example/ before the directory listing is returned (or not returned, as will be the case with the rule chain above active).
Detecting the real IP address of an attacker
If you're under attack by a sophisticated adversary, he will most likely be hiding behind an anonymizing proxy—sometimes he will even be using multiple chained proxies to avoid detection. The illustration below shows how this works when two proxy servers are involved. The web server will only see the IP address of the last proxy server, and even if the proxy server administrator co-operated to help find an attacker, the logs would only show the IP address of the proxy server before it in the chain.
Wouldn't it be great to be able to get the real IP address of an attacker and have it logged if a severe enough attack is taking place? The real IP address can be what makes or breaks an investigation if an attack ever has to be reported to the police.
The first step in implementing real IP address detection is to realize that ModSecurity's redirect action can be used to redirect to a different page when an attack is detected. We will just be redirecting to a standard 404—Not Found error page.
Now the remaining problem is: What do we put on this modified error page to detect the attacker's IP address? One possible avenue of approach would be to include some JavaScript code in the error page to try to find out his IP address. Unfortunately, it's not possible to detect the IP address of a computer using JavaScript—using the function java.net.InetAddress.getLocalHost() returns localhost on all systems.
However, what if the attacker has Java enabled in his browser? In that case, it is actually possible to use Java to detect the IP address of the attacking computer. We will basically be turning the attacker's own web browser against him by loading a Java applet that will detect his IP address and transmit it to our server. The following diagram illustrates how this works:
Lars Kindermann has a ready-made Java applet to detect IP addresses called "MyAddress" which is available at
http://www.reglos.de/myaddress/MyAddress.html. Simply download this to your web server by saving the MyAddress.class file that is linked to on the page.
To get the attacker's IP address we will be using a technique familiar from the section on cross-site scripting attacks—the use of an tag to transmit data back to our server. In this case, the data we will be transmitting is the attacker's IP address. Once we have the attacker's IP address—say 1.2.3.4—we will include the following tag on the error page to capture his IP address:
This will cause the attacker's web browser to perform a GET request to the page at www.ourserver.com/log_ip.php, handily providing the IP address in the query string. It is then a simple matter for the script log_ip.php to record the IP address in a database table or a text file.
This is the code that needs to be included on our modified error page in order to retrieve the IP address by invoking the Java applet and then printing out the tag:
function MyAddress(IP) {
document.write("");
}
This first line uses an tag to load the Java applet called MyAddress.class. The subsequent lines execute JavaScript code that does two things:
Retrieves the IP address of the attacker's computer.
Writes an tag to the web page that references our own server to send the IP address back to us.
You can see that the second step is what makes this similar to cross-site scripting.
This suffers from a small problem—the tag doesn't actually reference a valid image, which will cause the attacker to see a page with a "broken image" icon. Luckily, this is easily resolved by setting the width and height attributes of the image to zero:
function MyAddress(IP) {
document.write("");
}
Now, the final piece of the puzzle is just to redirect detected attacks to the log_ip.php page. The following ModSecurity rule illustrates how to do this:
SecRule ARGS "/etc/passwd" "pass,redirect:/log_ip.php"
Though real IP-detection may not be preferable for "everyday" attacks, it can be a handy tool in those cases where finding out the IP of the attacker is essential to prevent further crimes from taking place or assisting the police in an investigation.
Summary
In this article, we looked at different methods of attack currently used against web applications and servers. We learned the anatomy behind attacks such as cross-site scripting, cross-site request forgeries, and SQL injection. We saw how ModSecurity can be used to mitigate or block these attacks, and how ModSecurity can be a vital part of applying the Defense in Depth strategy. In the last sections of the article we learned how to defeat HTTP fingerprinting and how to detect the real IP address of an attacker if he is surfing via a proxy server but has Java enabled in his browser.
[
1 |
2 | 3 ]
Thanks, I had the same problem and solved it using your explanation ;)