A configurable account lockout mechanism (maximum retry count) is implemented using Lua persistence. The per-user configurable parameters are: (1) the number of retries allowable before the account is locked out for further use; (2) the interval (in minutes) that time has elapsed after being locked out that the account is reactivated.

A configurable account lockout mechanism (maximum retry count) is implemented using Lua persistence. The per-user configurable parameters are: (1) the number of retries allowable before the account is locked out for further use; (2) the interval (in minutes) that time has elapsed after being locked out that the account is reactivated.

−

Appending Javascript to the end of the response body - a very unsung useful feature of ModSecurity - is also used to alter the response back to the user.

+

Appending JavaScript to the end of the response body - a very unsung useful feature of ModSecurity - is also used to alter the response back to the user.

We will show the implementation, then after discuss how this solution can be extended and used as a temporary authentication mechanism.

We will show the implementation, then after discuss how this solution can be extended and used as a temporary authentication mechanism.

Line 343:

Line 343:

−

The Javascript appended to the response body, instead of redirecting to an error page, looks like this:

+

The JavaScript appended to the response body, instead of redirecting to an error page, looks like this:

Compare this with Figure 3. The original error message, in bright red color, has been replaced with <font face="Courier New">'* TOO MANY RETRY ATTEMPTS; YOUR ACCOUNT IS NOW LOCKED!'</font> and the Submit button has been disabled. The advantages of this approach are: (1) the user stays in "the same place" in the web site; (2) the look and feel of the web site is retained; the look and feel would have to be incorporated in a new error HTML page.

Compare this with Figure 3. The original error message, in bright red color, has been replaced with <font face="Courier New">'* TOO MANY RETRY ATTEMPTS; YOUR ACCOUNT IS NOW LOCKED!'</font> and the Submit button has been disabled. The advantages of this approach are: (1) the user stays in "the same place" in the web site; (2) the look and feel of the web site is retained; the look and feel would have to be incorporated in a new error HTML page.

−

=== Conclusion and Comments ===

+

=== Comments ===

In the real world, a file locking mechanism would need to be placed on the data file which could be implemented easily by creating or checking for an empty file like 'file.lock'. There doesn't seem to be an elegant 'sleep' or 'wait' capability in Lua script, so the rare occurrence that a request file lock is already used, an error message would have to be returned to the user saying "please try again".

In the real world, a file locking mechanism would need to be placed on the data file which could be implemented easily by creating or checking for an empty file like 'file.lock'. There doesn't seem to be an elegant 'sleep' or 'wait' capability in Lua script, so the rare occurrence that a request file lock is already used, an error message would have to be returned to the user saying "please try again".

Line 357:

Line 357:

# The use of Lua persistence

# The use of Lua persistence

# A configurable account lock out (maximum retry count) mechanism

# A configurable account lock out (maximum retry count) mechanism

−

# Appending Javascript to a response body to alter the HTML page's content and behavior.

+

# Appending JavaScript to a response body to alter the HTML page's content and behavior.

# A business logic flaw has been mitigated by a Web Application Firewall

# A business logic flaw has been mitigated by a Web Application Firewall

Revision as of 04:51, 13 November 2008

Lesson overview

The WebGoat lesson overview is included with the WebGoat lesson solution.

Lesson solution

Refer to the zip file with the WebGoat lesson solutions. See Appendix A for more information.

Strategy

This lesson illustrates using a weak secret password, favorite color, which can be guessed eventually. The project member chose to take a broader view in mitigating this vulnerability and using a couple of features of ModSecurity in ways not done in other lessons.

A configurable account lockout mechanism (maximum retry count) is implemented using Lua persistence. The per-user configurable parameters are: (1) the number of retries allowable before the account is locked out for further use; (2) the interval (in minutes) that time has elapsed after being locked out that the account is reactivated.

Appending JavaScript to the end of the response body - a very unsung useful feature of ModSecurity - is also used to alter the response back to the user.

We will show the implementation, then after discuss how this solution can be extended and used as a temporary authentication mechanism.

Implementation

First, let's go through the steps.

At the beginning of the lesson, the user is asked for their user name:

The user is asked to provide an answer to the secret question:

If the answer is wrong, the user receives an error message and is prompted again:

There is no limit on the number of retries, a scenario which exists in many web applications. A hint is given that the administrator may have an account name of 'admin' and therefore eventually the admin's secret question can be guessed.

The first step of the solution is a little tricky: we have to keep track of the user name from the request/response cycle of the first page to the 2nd page that guesses the answer to the secret question. In the real world, to take into account concurrency, we would set a cookie for this scenario, but for our purpose we we to keep it simple and show the main points of the solution.

The user name is displayed when an incorrect response; e.g. '* Incorrect response for admin. Please try again!'. So at this point we have the user name and know that they have guessed the secret question; for our purpose, the retry count is technically still 0.

The 'global' entry contains the configurable values for the number of retries that are going to be allowed and the interval (in minutes) that needs to elapse before the user's account is reactivated. Here, 2 retries are allowed, and if the user's account is locked and the time interval between retry attempts is 15 minutes, the account will be reactivated.

Where the Lua scripts have to make changes to the data file, a read/write operation is performed. In one case, only a read operation is necessary. Keep in mind that the data file can be edited at any time (if using *nix, be sure to restore the original owner and group name) to observe and experiment with the values and results.

The Lua script responsible for handling the HTTP responses for this lesson is 'secret-question-result_04-2.lua'.

After obtaining the user name, we loop through all of the users in the data file:
'dofile(datafile)'

This calls the function 'Entry'; first that particular user's data is saved to local variables:

The user is set to current or not. If it is the current user, we increase the retry count and if the account is locked out, we set the script's return screen to non-nil, which will trigger the SecRuleAction in phase 4 of this lesson's ModSecurity configuration file; we will go over this later in this lesson - view either 'rulefile_04-2a_forgot-password.conf' or 'rulefile_04-2b_forgot-password.conf'.

The last step of the loop (the function 'Entry') is to add the user's updated information to a string buffer:

The 2nd request is when the user is making a guess for the secret question. If no other sublessons of the lesson are being used - only 'rulefile_00_initialize.conf' and either 'rulefile_04-2a_forgot-password.conf' or 'rulefile_04-2b_forgot-password.conf' - then we don't need to filter to call the 2nd function. If it is desirable to filter, add the conditional statement as done previously with the POST parameters as Color=green&SUBMIT=Submit'.

Entry = Entry1
dofile(datafile)

Let's examine what each function does.

'Entry0' loops and processes each user in the data file. First, each entry's values are copied to local variables as previously shown.

Next, we get the configurable value from the user 'global' that we need, 'interval'; note that 'global' always has to be the first entry in the data file.

It is better to only read the data file if possible, which is done here. If the user is locked out but the interval between lock out time and now has been exceeded, we will let the user through and in the 2nd function deal with this condition so nil is returned in the function and the main body (to return nil to the SecRuleAction). If the user is locked out, we return non-nil (the string message is recorded in the audit log and the debug file, irrespective of the explicit log entry 'm.log(9, str1)' to the debug file), the SecRuleAction will trigger, and the user will receive a message that they have been locked out (which will be shown later).

For the 2nd function, 'Entry1', remember that we are on the page that is guessing the color. In this function, though, we will make changes to the data file according to the results.

First, retrieve the 'global' user's configuration parameters but this time we need the retry maximum count also:

if current == "yes" then
if lockedout == "yes" then
-- check if time interval exceeded; if so, unlock account
num1 = os.time()
if num1 >= (interval + g_interval*60) then
lockedout = "no"
retry = 0
interval = 0
else
-- question: do we want to set a new interval to the current time? IMO, yes
interval = os.time()
retstr = "Luascript: Entry1 - account already locked!"
end
end
if lockedout == "no" then
-- have to be real careful here that both are integers;
-- if not, then function will return suddenly with nil
if retry >= g_retry then
lockedout = "yes"
interval = os.time() -- record the second that user was locked
retstr = "Luascript: account is now locked!"
end
end
end

If the user is not the current user, nothing changes.

If the user is current, check if their account is currently locked.

If yes, check if the max interval time has been exceeded; if yes, then set the user lockout to "no", and reset the retry and interval values to 0; if no, reset the interval time to the current time (optional) and set 'retstr' so that it is denoted that the user is locked out and the SecRuleAction will be triggered.

If the account is not locked out, check if the retry count has been reached; if so, mark the account as locked out and reset the interval.

Note, that a conditional 'if lockedout == "no", then, else' was not used. If the account was originally locked out but changed to not locked out, that user is processed in the 'lockedout == "no" section as a safety check (and if one wants to add other logic here).

Also, note that the return value to SecRuleAction as currently implemented only returns boolean: either nil (nothing bad happened) or non-nil (changing ModSecurity to accept nil or a string as a return value should be possible because the return string is written to the log files). In our case, the bottom line is the same: the user is locked out, but the causes are different.

Finally, at end of the function each user's values appended to a buffer, then after exiting the function the buffer is written back for a new data file (the code for this has been previously shown).

We jump up now to the ModSecurity rules. Two alternatives have been given, 'rulefile_04-2a_forgot-password.conf' and 'rulefile_04-2a_forgot-password.conf'; they differ as to how to notify the user that they have been locked out.

First, 'rulefile_04-2a_forgot-password.conf':

Recall that the HTTP request triggers all of the processing when a user has made an incorrect guess; the error message '* Incorrect response for admin. Please try again!' (see Figure 3 at the beginning of this page) is our cue that a certain user has failed at making a correct guess.

(Note: previously, it was explained why the lessons are filtered by menu number and the reasons for using 'ARGS:menu' in Phase 2 and 'TX:MENU' in Phase 4)

Very simply, if the user's account is locked out, they get redirected to an error page.

The 2nd ruleset, rulefile_04-2b_forgot-password.conf, offers a more elegant solution. Inside the Lua script, a non-nil value is returned when the user's account is locked out. Instead of passing through the response body and processing the result in the request body in 'rulefile_04-2a_forgot-password.conf', we do the opposite:

The JavaScript appended to the response body, instead of redirecting to an error page, looks like this:

Compare this with Figure 3. The original error message, in bright red color, has been replaced with '* TOO MANY RETRY ATTEMPTS; YOUR ACCOUNT IS NOW LOCKED!' and the Submit button has been disabled. The advantages of this approach are: (1) the user stays in "the same place" in the web site; (2) the look and feel of the web site is retained; the look and feel would have to be incorporated in a new error HTML page.

Comments

In the real world, a file locking mechanism would need to be placed on the data file which could be implemented easily by creating or checking for an empty file like 'file.lock'. There doesn't seem to be an elegant 'sleep' or 'wait' capability in Lua script, so the rare occurrence that a request file lock is already used, an error message would have to be returned to the user saying "please try again".

What has been shown in this sublesson?

The use of Lua persistence

A configurable account lock out (maximum retry count) mechanism

Appending JavaScript to a response body to alter the HTML page's content and behavior.

A business logic flaw has been mitigated by a Web Application Firewall

The basic concepts demonstrated here can be extended and be used in real world examples, such as:

A web site's authentication has no maximum retry count or account lock out mechanism.

A web site (or areas of a web site), previous open to the public, require protection via an authentication mechanism.

The Lua scripts can be modified to tie into other authentication credential storage mechanisms (if the capability is supported in the language).

Reviewer comments

In looking at this lesson, there are a few security issues we need to address:

Issue 1 - Attackers who are attempting to enumerate valid usernames. If you submit an invalid one the html response text includes info stating as such. We can track this.

Issue 2 – Once a specific valid username is identified, the attacker then starts a targeted attack to guess answers to the password hint (favorite color).

Issue 3 – Attackers can initiate reverse brute force attacks – this is when an attacker cycles through different valid user accounts and submits the same common answer to the question (submitting Blue as the answer to different usernames).

Your approach seems to address issue #2 above as you are monitoring/correlating the answers submitted with the username. The main issue that I see with your use of temporary lockouts is that you have to be careful not to lock out legitimate clients. In this case, you are using the Lua script to lock out anyone from accessing the targeted account which would be a denial of service attack against legit users. It is for this reason that I would recommend a more targeted blocking scheme aimed at the JSESSIONID value instead of the account name. This approach is not foolproof, however, as attackers could potentially cycle through different JSESSIONIDs for evasion. WebGoat will redirect the user back to the start page if the JSESSIONID is missing or invalid and therefore it is deemed as an acceptable countermeasure as it would require the attacker log authenticate again.

What we need to do is to also address issues 1 and 2. The key to doing this is to leverage the JSESSIONID handed out by WebGoat to store/track data. We can then add additional logic checks. Here is an example rule set which looks to achieve the same basic protections as your Lua scripts.

I do like your use of Content Injection of adding JS to alter the html text and to disable the Submit button display, however this doesn’t really do anything to prevent an attacker from re-submitting more data. They don’t need to have the submit button enabled in order to re-submit their data.