Chapter 7

Defending Authentication

Knowing the place and the time of the coming battle, we may concentrate from the greatest distances in order to fight.

Sun Tzu in The Art of War

A key stratagem to use in defending your web applications can be divided into two parts: pre-authentication and post-authentication. Pre-authentication parts of your web site are locations where anonymous users may interact with it without specifying who they are. Post-authentication parts of your web site are available to users only after they have successfully logged in to their accounts. Different levels of functionality are available only after a user logs in to the web application. Once the application knows “who you are” then it may allow you access to specific data based on your role. Needless to say, the post-authentication portion of a web application provides access to more sensitive user data. Therefore, this part of an application is an attractive target for criminals.

Authentication is simply the process of proving to the web application that you are who you say you are. This is accomplished by submitting data from one or more of these categories:

  • Something you know
  • Something you have
  • Something you are

Most web applications use only a single factor for authentication—a password (something you know). If a user supplies the correct password associated with an account, she is given access, and a SessionID is created for an active session. From that point on, the client supplies the SessionID within a request cookie value to let the web application know who she is and that she has successfully authenticated. It goes without saying that the authentication process is one of the most critical transactions that occurs within the web application. It is therefore paramount that defenders keep a close watch on this process to ensure that attackers do not circumvent it. The recipes in this chapter help you defend the authentication process.


Recipe 7-1: Detecting the Submission of Common/Default Usernames
This recipe shows you how to detect when attackers attempt to submit common or default usernames when authenticating to the application.
Ingredients
  • ModSecurity
    • SecRule directive
    • ARGS variable
    • @pm operator
When applications are initially installed, they often come preconfigured with default accounts. These accounts normally have some legitimate purpose, such as a test or administrator account. These accounts also often are preset with a default password. This fact would not pose a problem if all application owners properly removed, deactivated, or set a new password on these accounts. Sadly, this is not the case. Attackers know this and usually attempt to access these default accounts. Here are some common default account names:
  • admin
  • administrator
  • root
  • system
  • guest
  • operator
  • super
  • test
  • qa
  • backup
These default accounts also often have default passwords, or the site owners set easily guessed ones, such as these:
  • blank (no password set)
  • admin
  • pass
  • pass123
  • password
  • password123
  • changeme
  • qwerty
Identifying the Username Parameter Field
To analyze the username submitted in an authentication attempt, we must review the full HTTP transaction. Let’s use the standard WordPress login, shown in Figure 7-1, as our example.

Figure 7-1: WordPress Login page

c07f001.tif
When the user clicks the Login button, the credentials are sent to the application for processing. This is how the request looks when it is received:
POST /wordpress/wp-login.php HTTP/1.1
Host: 192.168.1.113
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0) 
Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
DNT: 1
Referer: http://192.168.1.113/wordpress/wp-login.php
Content-Type: application/x-www-form-urlencoded
Via: 1.1 owaspbwa.localdomain
Connection: Keep-Alive
Content-Length: 73
 
log=administrator&pwd=pass123&submit=Login+%C2%BB&
redirect_to=wp-admin%2F
We can see that the username parameter is in the REQUEST_BODY of the request and is called log.
Inspect the Submitted Username
Now that we know which parameter data to inspect, we can use the following rule to check for common or default username values submitted to the /wordpress/wp-login.php page during an authentication attempt:
SecRule REQUEST_FILENAME "@streq /wordpress/wp-login.php" "chain,
phase:2,id:999320,t:none,block,msg:'Default/Common Username 
Submitted for Authentication.',logdata:%{args.log}'"
        SecRule REQUEST_METHOD "@streq POST" "chain"
                SecRule ARGS:log "@pm admin administrator root 
system guest operator super test qa backup"
This ruleset would generate the following ModSecurity debug log data if it received the preceding request:
Recipe: Invoking rule b7e1dfc0; [file "/etc/apache2/modsecurity-crs/
base_rules/modsecurity_crs_15_custom.conf"] [line "1"] 
[id "999320"].
Rule b7e1dfc0: SecRule "REQUEST_FILENAME" "@streq /wordpress/
wp-login.php" "phase:2,log,chain,id:999320,t:none,block,msg:'Default
/Common Username Submitted for Authentication.',logdata:%{args.log}
'"
Transformation completed in 2 usec.
Executing operator "streq" with param "/wordpress/wp-login.php" 
against REQUEST_FILENAME.
Target value: "/wordpress/wp-login.php"
Operator completed in 5 usec.
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule b7e24e38; [file "/etc/apache2/modsecurity-crs/
base_rules/modsecurity_crs_15_custom.conf"] [line "2"].
Rule b7e24e38: SecRule "REQUEST_METHOD" "@streq POST" "chain"
Transformation completed in 0 usec.
Executing operator "streq" with param "POST" against REQUEST_METHOD.
Target value: "POST"
Operator completed in 3 usec.
Rule returned 1.
Match -> mode NEXT_RULE.
Recipe: Invoking rule b7e252c8; [file "/etc/apache2/modsecurity-crs/
base_rules/modsecurity_crs_15_custom.conf"] [line "3"].
Rule b7e252c8: SecRule "ARGS:log" "@pm admin administrator root 
system guest operator super test qa backup"
Transformation completed in 0 usec.
Executing operator "pm" with param "admin administrator root system 
guest operator super test qa backup" against ARGS:log.
Target value: "administrator"
Operator completed in 5 usec.
Resolved macro %{args.log} to: administrator
Warning. Matched phrase "admin" at ARGS:log. [file "/etc/apache2/
modsecurity-crs/base_rules/modsecurity_crs_15_custom.conf"] 
[line "1"] [id "999320"] [msg "Default/Common Username Submitted for
 Authentication."] [data "administrator'"]


Recipe 7-2: Detecting the Submission of Multiple Usernames
This recipe shows you how to identify when attackers cycle through many different usernames during authentication attempts.
Ingredients
  • ModSecurity
    • SecRule directive
    • @within operator
    • initcol action
    • setvar action
    • expirevar action
Because most average users choose weak passwords, attackers can easily pick a simple password and then cycle through various known usernames in hopes of hitting on a valid combination. Let’s look at some real-world attack examples.
Horizontal Brute-Force Scanning
One of the projects I lead for the Web Application Security Consortium (WASC) is the Distributed Web Honeypot Project. As part of this project, participants can deploy open proxy Apache web servers that attackers will eventually use to loop their attack traffic through in hopes of hiding their source IP address. While monitoring the honeypot traffic, we identified a widespread authentication attack against Yahoo! accounts. Here is a sample of the attack traffic:
GET http://217.12.8.76/config/isp_verify_user?l=bandit_unreal&
p=123456 HTTP/1.0
GET http://202.86.7.110/config/isp_verify_user?l=federico_lara&
p=123456 HTTP/1.0
GET http://202.86.7.110/config/isp_verify_user?l=felipe_rogelio&
p=123456 HTTP/1.0
GET http://66.163.169.179/config/isp_verify_user?l=bambi_shelton&
p=123456 HTTP/1.0
GET http://119.160.244.232/config/isp_verify_user?l=dinamis18&
p=123456 HTTP/1.0
GET https://login.yahoo.com/config/isp_verify_user?l=felix_pay&
l%20=lol_039&p=123456 HTTP/1.0
In these authentication requests, the l parameter is the username, and the p parameter is the password. Notice that the attackers are using the same password (123456) and are simply trying various usernames against it. This technique is often called horizontal brute-force scanning. An additional advantage of this approach is that the attackers do not lock out individual accounts, as they would if they attempted a vertical brute-force scanning attack against a single user account.
Identifying Multiple Username Submissions
To catch this type of activity, we must use ModSecurity’s persistent storage so that we can save and track data across multiple requests. The OWASP ModSecurity Core Rule Set (CRS) activates the IP-based persistent collection by default at the end of the modsecurity_crs_10_config.conf file:
#
# -=[ Global and IP Collections ]=-
#
# Create both Global and IP collections for rules to use.
# There are some CRS rules that assume that these two collections
# have already been initiated.
#
SecRule REQUEST_HEADERS:User-Agent "^(.*)$" "phase:1,id:'981217',
t:none,pass,nolog,t:sha1,t:hexEncode,
setvar:tx.ua_hash=%{matched_var}"
SecRule REQUEST_HEADERS:x-forwarded-for "^(d{1,3}.d{1,3}.d
{1,3}.d{1,3})" "phase:1,id:'981225',t:none,pass,nolog,capture,
setvar:tx.real_ip=%{tx.1}"
SecRule &TX:REAL_IP "!@eq 0" "phase:1,id:'981226',t:none,pass,nolog,
initcol:global=global,initcol:ip=%{tx.real_ip}_%{tx.ua_hash}"
SecRule &TX:REAL_IP "@eq 0"  "phase:1,id:'981218',t:none,pass,nolog,
initcol:global=global,initcol:ip=%{remote_addr}_%{tx.ua_hash}"
The bold initcol actions create and access persistent storage that is unique for each IP address and User-Agent string combination. With this data available to use, we can then use the following custom rules to track usernames submitted to our WordPress login page:
#
# If this is the first authentication attempt, then
# just save the username data for subsequent checks.
#
SecRule REQUEST_FILENAME "@streq /wordpress/wp-login.php" "chain,
phase:2,id:999321,t:none,pass,nolog"
        SecRule REQUEST_METHOD "@streq POST" "chain"
                SecRule ARGS:log ".*" "chain"
                        SecRule &IP:PREVIOUS_USERNAME "@eq 0"
 "setvar:ip.previous_username=%{args.log}"
 
#
# If the client has previously submitted a username, then
# compare the current value with the previous one.
# If they don't match, then increase the username counter.
# If the counter exceeds 5 within 60 seconds, then trigger
# an alert.
#
SecRule REQUEST_FILENAME "@streq /wordpress/wp-login.php" "chain,
phase:2,id:999322,t:none,block,msg:'Multiple Username Violation: Too 
Many Usernames Submitted for Authentication.',logdata:'Current 
Username: %{args.log}, Previous Usernames: %{ip.previous_username}.
 Total # of usernames submitted: %{ip.multiple_username_count}'"
  SecRule REQUEST_METHOD "@streq POST" "chain"
    SecRule ARGS:log ".*" "chain"
      SecRule &IP:PREVIOUS_USERNAME "@eq 1" "chain"
        SecRule ARGS:log "!@within %{ip.previous_username}" "chain,
        setvar:ip.multiple_username_count=+1,
        expirevar:ip.multiple_username_count=60,
        setvar:'ip.previous_username=%{ip.previous_username}, 
        %{args.log}'"
          SecRule IP:MULTIPLE_USERNAME_COUNT "@gt 5"
If a client submits five or more different usernames for authentication within a one-minute timeframe, we generate an event similar to the following:
[Thu May 10 19:22:02 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Operator GT matched 5 at IP:multiple_
username_count. [file "/etc/apache2/modsecurity-crs/base_rules/
modsecurity_crs_15_custom.conf"] [line "6"] [id "999322"] [msg 
"Multiple Username Violation: Too Many Usernames Submitted for 
Authentication."] [data "Current Username: wstanton, Previous 
Usernames: janesmith, jdoe, kjames, backup, admin, guest, 
wstanton. Total # of usernames submitted: 6"] 
[hostname "192.168.1.113"] [uri "http://192.168.1.113/wordpress
/wp-login.php"] [unique_id "T6xNmn8AAQEAAFTiF4oAAAAG"]
As you can see, the alert also lists the previous usernames submitted by this user during the violation. This allows you to quickly see which user accounts the user was targeting.


Recipe 7-3: Detecting Failed Authentication Attempts
This recipe shows you how to identify when a client generates multiple failed authentication attempts in a short period of time.
Ingredients
  • ModSecurity
    • SecRule directive
    • @within operator
    • initcol action
    • setvar action
    • expirevar action
Authentication Failure Monitoring
When a client submits incorrect credentials during authentication, what happens? Does he get redirected to another web page? Does HTML text within the body of the response page identify the authentication failure? Conversely, what does it look like when a client successfully authenticates to the application? We must learn what these two scenarios look like so that we can create applicable ModSecurity rules to generate alerts when successive failures occur.
When a client submits an incorrect password on the WordPress login page, this is the raw response:
HTTP/1.1 200 OK
Date: Fri, 11 May 2012 03:24:53 GMT
Server: Microsoft-IIS/7.0
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Last-Modified: Fri, 11 May 2012 03:24:54 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 1697
Connection: close
Content-Type: text/html; charset=UTF-8
 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
     <title>WordPress &rsaquo; Login</title>
     <meta http-equiv="Content-Type" content="text/html; c
harset=UTF-8" />
     <link rel="stylesheet" href="http://192.168.1.113/wordpress/
wp-admin/wp-admin.css" type="text/css" />
     <script type="text/javascript">
     function focusit() {
          document.getElementById('log').focus();
     }
     window.onload = focusit;
     </script>
</head>
<body>
 
<div id="login">
<h1><a href="http://wordpress.org/">WordPress</a></h1>
<div id='login_error'><strong>Error</strong>: Incorrect password.
</div>
...
</body>
</html>
The bold text in the response is the key information:
  • The application responds with an HTTP status code of 200 OK.
  • The HTML response text includes a message stating that the wrong password was used.
With this data, we can craft the following ModSecurity ruleset:
SecRule REQUEST_FILENAME "@streq /wordpress/wp-login.php" "chain,
phase:4,id:999323,t:none,block,msg:'Authentication Failure Violation
.',logdata:'Number of Authentication Failures: %{ip.failed_auth_
attempt}'"
  SecRule REQUEST_METHOD "@streq POST" "chain"
    SecRule ARGS:pwd ".*" "chain"
      SecRule RESPONSE_STATUS "200" "chain"
        SecRule RESPONSE_BODY "@contains <strong>Error</strong>: 
Incorrect password." "chain,setvar:ip.failed_auth_attempt=+1,
expirevar:ip.failed_auth_attempt=60"
          SecRule IP:FAILED_AUTH_ATTEMPT "@gt 5"
This ruleset monitors for authentication failures and tracks how many occur within one minute. If this limit is exceeded, an event is triggered. Here is a sample alert that is generated:
[Fri May 11 00:23:42 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Operator GT matched 5 at IP:failed_auth_
attempt. [file "/etc/apache2/modsecurity-crs/base_rules/
modsecurity_crs_15_custom.conf"] [line "2"] [id "999323"] 
[msg "Authentication Failure Violation."] [data "Number of 
Authentication Failures: 6"] [hostname "192.168.1.113"] 
[uri "/wordpress/wp-login.php"] 
[unique_id "T6yUTX8AAQEAAF0-Az8AAAAB"]


Recipe 7-4: Detecting a High Rate of Authentication Attempts
This recipe shows you how to identify when a client attempts multiple authentications in a short period of time.
Ingredients
  • OWASP ModSecurity Core Rule Set (CRS)
    • modsecurity_crs_10_config.conf
    • modsecurity_crs_11_brute_force.conf
Vertical Brute-Force Authentication Attacks
If a client submits incorrect credentials to successfully authenticate to the web application, it is a good idea to track this occurrence to ensure that it doesn’t happen repeatedly. If it does, odds are that an attacker is conducting a brute-force scanning session to try to enumerate valid credentials for user accounts. Automation is the key for attackers to try to guess valid credentials. Numerous public and commercial tools can be used to conduct these automated authentication scans. One such tool is Burp Suite1 from PortSwigger. Burp Suite is a full-featured web application penetration testing toolset that comes with many useful modules. The Intruder module allows the user to specify which parts of a request to manipulate, as well as send various data payloads. Figure 7-2 shows the main Intruder interface that is prepopulated with the login request data for WordPress.

Figure 7-2: Burp Suite’s Intruder screen with WordPress login data

c07f002.tif
Figure 7-2 shows that we have targeted the password parameter field (pwd) as our position of attack. After we have selected the desired payload data, we can launch our attack. Figure 7-3 shows the Intruder Results screen as it cycles through various password combinations.

Figure 7-3: Burp Suite Intruder Results screen

c07f003.tif
In Figure 7-3, you can see that Intruder attempted to send a password value of aaaa. Figure 7-4 shows the application’s HTML response data that indicates that the wrong password was used.
This attempt was unsuccessful, but it is only a matter of time before an attacker would eventually find the correct password.

Figure 7-4: Burp Suite Intruder’s HTML Results screen

c07f004.tif
Detecting Brute-Force Attacks
To detect these types of automated authentication attempts, we can use the brute-force detection capabilities of the OWASP ModSecurity Core Rule Set. The first file to look at is modsecurity_crs_10_config.conf, which has these configuration settings:
#
# -=[ Brute Force Protection ]=-
#
# If you are using the Brute Force Protection rule set, then 
# uncomment the following lines and set the following variables:
# - Protected URLs: resources to protect (e.g. login pages) 
# - set to your login page
# - Burst Time Slice Interval: time interval window to monitor for 
#   bursts
# - Request Threshold: request # threshold to trigger a burst
# - Block Period: temporary block timeout
#
#SecAction "phase:1,id:'981214',t:none,nolog,pass, 
#setvar:'tx.brute_force_protected_urls=/login.jsp 
#setvar:'tx.brute_force_burst_time_slice=60', 
#setvar:'tx.brute_force_counter_threshold=10', 
#setvar:'tx.brute_force_block_timeout=300'"
You need to uncomment these rules and customize them for your site. If we wanted to protect the WordPress login page, we would use this setting:
SecAction "phase:1,id:'981214',t:none,nolog,pass, 
setvar:'tx.brute_force_protected_urls=/wordpress/wp-login.php', 
setvar:'tx.brute_force_burst_time_slice=60', 
setvar:'tx.brute_force_counter_threshold=10', 
setvar:'tx.brute_force_block_timeout=300'"
The next step is to activate the modsecurity_crs_11_brute_force.conf file, which has the following rules:
#
# Anti-Automation Rule for specific Pages (Brute Force Protection)
# This is a rate-limiting rule set and does not directly correlate
# whether the authentication attempt was successful or not.
#
 
#
# Enforce an existing IP address block and log only 1-time/minute.
# We don't want to get flooded by alerts during an attack or scan so
# we are only triggering an alert once/minute.  You can adjust how 
# often you want to receive status alerts by changing the expirevar 
# setting below.
#
SecRule IP:BRUTE_FORCE_BLOCK "@eq 1" "chain,phase:1,id:'981036',
block,msg:'Brute Force Attack Identified from %{remote_addr}
 (%{tx.brute_force_block_counter} hits since last alert)',
setvar:ip.brute_force_block_counter=+1"
     SecRule &IP:BRUTE_FORCE_BLOCK_FLAG "@eq 0"
 "setvar:ip.brute_force_block_flag=1,
expirevar:ip.brute_force_block_flag=60,
setvar:tx.brute_force_block_counter=%{ip.brute_force_block_counter},
setvar:ip.brute_force_block_counter=0"
 
#
# Block and track # of requests but don't log
SecRule IP:BRUTE_FORCE_BLOCK "@eq 1" "phase:1,id:'981037',block,
nolog,setvar:ip.brute_force_block_counter=+1"
 
#
# skipAfter Checks
# There are different scenarios where we don't want to do checks -
# 1. If the user has not defined any URLs for Brute Force 
# Protection in the 10 config file
# 2. If the current URL is not listed as a protected URL
# 3. If the current IP address has already been blocked due to high
# requests
# In these cases, we skip doing the request counts.
#
SecRule &TX:BRUTE_FORCE_PROTECTED_URLS "@eq 0" "phase:5,id:'981038',
t:none,nolog,pass,skipAfter:END_BRUTE_FORCE_PROTECTION_CHECKS"
SecRule REQUEST_FILENAME "!@within %{tx.brute_force_protected_urls}"
 "phase:5,id:'981039',t:none,nolog,pass,
skipAfter:END_BRUTE_FORCE_PROTECTION_CHECKS"
SecRule IP:BRUTE_FORCE_BLOCK "@eq 1" "phase:5,id:'981040',t:none,
nolog,pass,skipAfter:END_BRUTE_FORCE_PROTECTION_CHECKS"
 
#
# Brute Force Counter
# Count the number of requests to these resources
#
SecAction "phase:5,id:'981041',t:none,nolog,pass,
setvar:ip.brute_force_counter=+1"
 
#
# Check Brute Force Counter
# If the request count is greater than or equal to 50 within 5 mins,
# we then set the burst counter
#
SecRule IP:BRUTE_FORCE_COUNTER "@gt %{tx.brute_force_counter_
threshold}"
 "phase:5,id:'981042',t:none,nolog,pass,
t:none,setvar:ip.brute_force_burst_counter=+1,
expirevar:ip.brute_force_burst_counter=%{tx.brute_force_burst_time
_slice},setvar:!ip.brute_force_counter"
 
#
# Check Brute Force Burst Counter and set Block
# Check the burst counter - if greater than or equal to 2, then we 
# set the IP block variable for 5 mins and issue an alert.
#
SecRule IP:BRUTE_FORCE_BURST_COUNTER "@ge 2" "phase:5,id:'981043',
t:none,log,pass,msg:'Potential Brute Force Attack from %{remote_addr}
 - # of Request Bursts: %{ip.brute_force_burst_counter}',
setvar:ip.brute_force_block=1,
expirevar:ip.brute_force_block=%{tx.brute_force_block_timeout}"
 
SecMarker END_BRUTE_FORCE_PROTECTION_CHECKS
These rules monitor for access attempts to the defined login page. If more than two bursts of traffic occur within one minute, the user is temporarily blocked for five minutes, and alerts are generated. If we run Burp Suite Intruder against our WordPress login script now, we would receive alerts similar to this:
[Fri May 11 00:49:23 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Operator GE matched 2 at IP:brute_force_burst
_counter. [file "/etc/apache2/modsecurity-crs/base_rules/
modsecurity_crs_11_brute_force.conf"] [line "60"] [id "981043"] 
[msg "Potential Brute Force Attack from 192.168.1.103 - # of Request
 Bursts: 2"] [hostname "192.168.1.113"] 
[uri "/wordpress/wp-login.php"] 
[unique_id "T6yaUn8AAQEAAF95A7cAAAAB"]
The client is then put into block mode. Alerts are suppressed and generate only periodic alerting. This is to avoid alert flooding during attacks. Here is an example of the period alert:
[Fri May 11 00:50:23 2012] [error] [client 192.168.1.103] 
ModSecurity: Access denied with code 403 (phase 1). Operator EQ 
matched 0 at IP. [file "/etc/apache2/modsecurity-crs/base_rules/
modsecurity_crs_11_brute_force.conf"] [line "23"]
 [id "981036"] [msg "Brute Force Attack Identified from 
192.168.1.103 (5107 hits since last alert)"] 
[hostname "192.168.1.113"] [uri "/wordpress/wp-login.php"] 
[unique_id "T6yaj38AAQEAAF@OKzYAAAAQ"]
This alert indicates that ModSecurity has responded with a 403 Forbidden message and also provides statistics on the total number of attacks since the last alert (5107). As soon as these brute-force authentication attack responses kick in, Figure 7-5 shows that Burp Suite Intruder is receiving 403 status code responses and thus cannot enumerate valid credentials.

Figure 7-5: Burp Suite Intruder receiving 403 responses

c07f005.tif


Recipe 7-5: Normalizing Authentication Failure Details
This recipe demonstrates how to modify the authentication response data so that it does not divulge too much information.
Ingredients
  • ModSecurity
    • SecContentInjection directive
    • SecStreamOutbodyInspection directive
    • STREAM_OUTPUT_BODY variable
    • @rsub operator
Providing Detailed Authentication Failure Information
We don’t need to make an attacker’s job any easier. Unfortunately, that is precisely what we are doing when the login processes on our web applications provide too much information on failed authentication attempts. Two pieces of data usually are required for standard logins: a username and a password.
The attacker must identify the correct combination of these two individual components. Figure 7-6 shows how WordPress responds when a user submits an authentication attempt with an invalid username.

Figure 7-6: WordPress Error page for an invalid username

c07f006.tif
As you can see, WordPress tells the user that the username is invalid. Knowing this information, an attacker can simply cycle through common usernames or execute a brute-force attack to enumerate valid ones. When a client sends an authentication request with a valid username but an incorrect password, WordPress presents the user with the error page shown in Figure 7-7.

Figure 7-7: WordPress Error page for an invalid password

c07f007.tif
The error message on this page clearly states that the user submitted an incorrect password. With this information, the attacker could then conduct a targeted brute-force attack on the password field.
Normalizing Authentication Failure Messages
With ModSecurity, not only can we inspect the outbound response data sent to clients, but we also can manipulate it. This means that we can actually change the HTML text presented to users when they have an authentication failure. We want to standardize the following two different HTML texts when a failure occurs:
  • Invalid username:
    <div id='login_error'><strong>Error</strong>: Wrong username.</div>
  • Invalid password:
    <div id='login_error'><strong>Error</strong>: Incorrect password.
    </div>
The following ruleset silently normalizes the HTML text returned by these two situations to simply read Error: Authentication Failure.
SecContentInjection On
SecStreamOutBodyInspection On
SecRule REQUEST_FILENAME "@streq /wordpress/wp-login.php" "chain,
phase:4,t:none,nolog,pass"
  SecRule ARGS:log|ARGS:pwd "!^$" "chain"
    SecRule STREAM_OUTPUT_BODY "@rsub s/<div id='login_error'>
<strong>Error</strong>: .*?.</div>/<div id='login_error'>
<strong>Error</strong>: Authentication Failure.</div>"
With these rules in place, now when someone fails to authenticate, she receives a consistent error message, as shown in Figure 7-8.

Figure 7-8: WordPress Error page with a normalized error message

c07f008.tif


Recipe 7-6: Enforcing Password Complexity
This recipe demonstrates how to apply a password complexity check when a user initially creates an account.
Ingredients
  • ModSecurity
    • SecStreamInBodyInspection directive
    • ARGS variable
    • STREAM_INPUT_BODY variable
    • @rx operator
    • prepend action
Poor Passwords
The truth is that, left to their own devices, users pick terribly weak passwords. This fact has been confirmed multiple times recently with the data breaches at Gawker and Sony, where security researchers analyzed millions of user account passwords. Table 7-1 lists the top 25 most-used passwords from the Sony breach.

Table 7-1: Sony’s Top 25 Most-Used Passwords

Table 7-1
You can see that most of these passwords are not complex, and many are simply dictionary words. We can conclude that, unless the web application enforces minimum password complexity restrictions concerning the length and character sets in use, users will pick passwords that are easy to remember. Although this seems like an easy issue to address, many web applications do not include granular controls over password complexity management.
Enforcing Password Complexity
Using ModSecurity, we can easily analyze password data that is submitted as part of initial account creation or a password change process. Consider the WordPress Add New User interface, shown in Figure 7-9.

Figure 7-9: WordPress Add New User screen

c07f009.tif
As you can see, the user must specify a new password twice. When the request is sent to the web application, this is how it looks:
POST /wordpress/wp-admin/users.php HTTP/1.1
Host: 192.168.1.113
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0)
 Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://192.168.1.113/wordpress/wp-admin/users.php
Cookie: wordpressuser_2312f1fa644db0240f09344031a04c85=admin;
 wordpresspass_2312f1fa644db0240f09344031a04c85=
c3284d0f94606de1fd2af172aba15bf3
Content-Type: application/x-www-form-urlencoded
Content-Length: 147
 
action=adduser&user_login=bsmith&first_name=Bob&last_name=Smith&
email=bsmith%40email.net&url=&pass1=password&pass2=password&
adduser=Add+User+%C2%BB
The new password data is held within the bold pass1 and pass2 parameter fields. With this information, we can create the following ruleset that enforces better password complexity:
SecStreamInBodyInspection On
 
SecRule REQUEST_FILENAME "@streq /wordpress/wp-admin/users.php" 
"chain,phase:2,t:none,log,pass,msg:'Password Complexity Violation: 
Rejecting Weak Password Choice.',logdata:'User: %{args.user_login},
 and password: %{args.pass1}'"
        SecRule ARGS:pass1|ARGS:pass2 "!^(?=[a-zA-Z0-9]*?[A-Z])
(?=[a-zA-Z0-9]*?[a-z])(?=[a-zA-Z0-9]*?[0-9])[a-zA-Z0-9]{8,}$" 
"chain"
                SecRule STREAM_INPUT_BODY "@rsub s/pass1=.*?&/
pass1=&/" "setvar:tx.passwd_complexity_violation=1"
 
SecRule TX:PASSWD_COMPLEXITY_VIOLATION "@eq 1" "phase:4,t:none,nolog
,pass,prepend:'<script>confirm('Your password(s) do not meet minimum
 requirements of: at least 8 characters in length and including both
 upper/lowercase letters and numbers. Please try again.')
</script>'"
If the passwords do not meet the password complexity rules, we do two things:
  • We actually delete the first password from the request with the @rsub operator so that the application rejects the request.
  • We inject some JavaScript into the response body to notify the end user of the problem.
Figure 7-10 shows the JavaScript alert notification.

Figure 7-10: JavaScript notification of the password complexity failure

c07f010.tif
The following alert message also is generated:
[Fri May 11 03:14:57 2012] [error] [client 192.168.1.103] 
ModSecurity: Warning. Operator rsub succeeded. [file "/etc/apache2/
modsecurity-crs/base_rules/modsecurity_crs_15_custom.conf"] 
[line "3"] [msg "Password Complexity Violation: Rejecting Weak 
Password Choice."] [data "User: bsmith, and password: pass"] 
[hostname "192.168.1.113"] [uri "/wordpress/wp-admin/users.php"] 
[unique_id "T6y8cX8AAQEAAGg7AhwAAAAD"]


Recipe 7-7: Correlating Usernames with SessionIDs
This recipe shows you how to correlate an application username with the active SessionID.
Ingredients
  • OWASP ModSecurity Core Rule Set (CRS)
    • modsecurity_crs_16_username_tracking.conf
  • ModSecurity
    • RESPONSE_HEADERS:Set-Cookie variable
    • ARGS:username variable
    • setsid action
    • setuid action
    • setvar action
When security-related events are generated, can you identify the actual application user within the alert data? You will have an IP address, but that does not directly correlate to a specific application user. What about a SessionID? SessionIDs are too transient, because they are valid for only a short time. What you need to be able to do is correlate the username that was submitted during successful authentication with the active SessionID. This allows tracking, because the SessionID is resubmitted to the application on subsequent requests within the Cookie request headers.
Creating Session Collections
To track data from request to request related to a particular SessionID, you must use the ModSecurity setsid action when the application either issues or receives a SessionID. In the former case, when an application issues a Set-Cookie response header, that is the time to create a persistent session collection. For instance, here is how it looks when Joomla hands out a SessionID when a client first arrives at the site:
HTTP/1.1 200 OK
P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"
Expires: Mon, 01 Jan 2001 00:00:00 GMT
Last-Modified: Fri, 11 May 2012 10:05:32 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0,
 pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 5485
Content-Type: text/html; charset=utf-8
Set-Cookie: d5a4bd280a324d2ac98eb2c0fe58b9e0=
bdvkm96nk9nk35k6mggecbk596; path=/
We can therefore use the following ModSecurity rule to create the local persistent collection:
SecRule RESPONSE_HEADERS:/Set-Cookie2?/ "(?i:([a-z0-9]{32})=
([^s]+);s?)" "phase:3,id:'999033',t:none,pass,nolog,capture,
setsid:%{TX.2},setvar:session.valid=1"
Here is the debug log processing:
Rule 100d119d8: SecRule "RESPONSE_HEADERS:/Set-Cookie2?/"
 "@rx (?i:([a-z0-9]{32})=([^\s]+)\;\s?)"
 "phase:3,id:999033,t:none,pass,nolog,capture,setsid:%{TX.2},
setvar:session.valid=1"
Transformation completed in 2 usec.
Executing operator "rx" with param "(?i:([a-z0-9]{32})=([^\s]+)\;
\s?)" against RESPONSE_HEADERS:Set-Cookie.
Target value: "d5a4bd280a324d2ac98eb2c0fe58b9e0=bdvkm96nk9nk35k6mgge
cbk596; path=/"
Added regex subexpression to TX.0: d5a4bd280a324d2ac98eb2c0fe58b9e0=
bdvkm96nk9nk35k6mggecbk596;
Added regex subexpression to TX.1: d5a4bd280a324d2ac98eb2c0fe58b9e0
Added regex subexpression to TX.2: bdvkm96nk9nk35k6mggecbk596
Operator completed in 33 usec.
Resolved macro %{TX.2} to: bdvkm96nk9nk35k6mggecbk596
collection_retrieve_ex: Retrieving collection (name "default_
SESSION", filename "/tmp/default_SESSION")
Creating collection (name "default_SESSION", key
 "bdvkm96nk9nk35k6mggecbk596").
Setting default timeout collection value 3600.
Recorded original collection variable: SESSION.UPDATE_COUNTER = "0"
Added collection "default_SESSION" to the list as "SESSION".
Setting variable: session.valid=1
Set variable "session.valid" to "1"
...
collection_store: Retrieving collection (name "default_SESSION", 
filename "/tmp/default_SESSION")
Wrote variable: name "__expire_KEY", value "1336774845".
Wrote variable: name "KEY", value "bdvkm96nk9nk35k6mggecbk596".
Wrote variable: name "TIMEOUT", value "3600".
Wrote variable: name "__key", value "bdvkm96nk9nk35k6mggecbk596".
Wrote variable: name "__name", value "default_SESSION".
Wrote variable: name "CREATE_TIME", value "1336771245".
Wrote variable: name "UPDATE_COUNTER", value "1".
Wrote variable: name "valid", value "1".
Wrote variable: name "LAST_UPDATE_TIME", value "1336771245".
Persisted collection (name "default_SESSION", key
 "bdvkm96nk9nk35k6mggecbk596").
Now, when the user returns, she submits her SessionID Cookie data in her request header, like this:
GET /joomla/index.php?option=com_virtuemart&Itemid=5 HTTP/1.1
Host: 192.168.1.113
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0)
 Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://192.168.1.113/joomla/index.php
Cookie: d5a4bd280a324d2ac98eb2c0fe58b9e0=bdvkm96nk9nk35k6mggecbk596
We can then use this SessionID data and reopen our local persistent collection data for this user.
Saving Username Data
When the user sends a username during authentication to Joomla, it looks something like this:
POST /joomla/index.php?option=com_user&view=login&Itemid=2 HTTP/1.1
Host: 192.168.1.113
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0)
 Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://192.168.1.113/joomla/index.php?option=com_user&view=
login&Itemid=2
Cookie: d5a4bd280a324d2ac98eb2c0fe58b9e0=bdvkm96nk9nk35k6mggecbk596
Content-Type: application/x-www-form-urlencoded
Content-Length: 122
 
username=admin&passwd=admin&Submit=Login&option=com_user&task=
login&return=aW5kZXgucGhw&229b2f19d899b6c2d367a728717f27be=1
Note the bold SessionID cookie data and username parameter information. We can now use the following rules, which access the saved session data and save the username that the client sent:
SecRule REQUEST_COOKIES:'/(?i:([a-z0-9]{32}))/' ".*" "chain,phase:1,
id:'981054',t:none,pass,nolog,capture,setsid:%{TX.0}"
        SecRule SESSION:USERNAME ".*" "capture,setuid:%{TX.0}"
 
SecRule ARGS:username ".*" "phase:3,id:'981075',t:none,pass,nolog,
noauditlog,capture,setvar:session.username=%{TX.0},setuid:%{TX.0}"
With these rules in place, we can tie username data to SessionIDs. Here is an example of an audit log file after our rules are in place:
--fd2b4607-A--
[11/May/2012:17:21:02 --0400] T62CvcCoAWcAAGAKCrMAAAAC 192.168.1.103
 60986 192.168.1.103 80
--fd2b4607-B--
GET /joomla/index.php?option=com_virtuemart&Itemid=5 HTTP/1.1
Host: 192.168.1.113
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:12.0)
 Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;
q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Proxy-Connection: keep-alive
Referer: http://192.168.1.113/joomla/index.php
Cookie: d5a4bd280a324d2ac98eb2c0fe58b9e0=bdvkm96nk9nk35k6mggecbk596
 
--fd2b4607-F--
HTTP/1.1 301 Moved Permanently
P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"
Location: http://192.168.1.113/joomla/index.php?option=
com_virtuemart&Itemid=5&vmcchk=1&Itemid=5
Vary: Accept-Encoding
Content-Length: 0
Content-Type: text/html
Set-Cookie: virtuemart=bdvkm96nk9nk35k6mggecbk596
 
--fd2b4607-H--
Apache-Handler: proxy-server
Stopwatch: 1336771261972831 246822 (- - -)
Stopwatch2: 1336771261972831 246822; combined=108496, p1=3688, 
p2=98627, p3=149, p4=3969, p5=1362, sr=959, sw=701, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.7.0-rc1 
(http://www.modsecurity.org/); core ruleset/2.2.4.
Server: Apache/2.2.17 (Unix) mod_ssl/2.2.12 OpenSSL/0.9.8r DAV/2
WebApp-Info: "default" "bdvkm96nk9nk35k6mggecbk596" "admin"
Engine-Mode: "ENABLED"
 
--fd2b4607-Z--
The bold WebApp-Info token data in section H shows that the username associated with this SessionID is “admin.” This allows you to track which user account is associated with any subsequent security events.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.25.32