- Home
- About us
- Products & Services
- Blog
- News
- Contact
- Client Portal
Fairly often applications include a password recovery feature that uses extremely weak questions. Password recovery mechanisms are problematic as they can be used as a secondary means of authentication which are not subject to the same strict criteria as the primary means of authentication. A perfect example of this is when U.S. Vice Presidential candidate Sarah Palin's private Yahoo account became compromised when the son of a Tennessee state representative reset Palin's password using the password recovery mechanism which required her birth date, ZIP code and information about where she met her spouse. All this information was publicly available and gathered within 45 minutes.
An out-of-band method should be used to allow users to recover their passwords. SMS can be a cheap method to notify users of their new passwords. A free alternative is to send an email containing a unique, time-limited, un-guessable single-use recovery URL. The email should be sent to the address that the user provided during registration. This post describes how you can use ModSecurity and Lua to accomplish this. The process is as follows:
SecRule REQUEST_COOKIES:PHPSESSID ^(.+)$ \
"phase:2,capture,log,pass,chain,\
msg:'Initializing session: %{TX.0}',setsid:%{TX.0}"
SecRule &REQUEST_COOKIES:PHPSESSID "@eq 1"
SecRule SESSION:IS_NEW "@eq 1" \
"phase:1,nolog,pass,\
setvar:SESSION.TIMEOUT=1200"
SecRule REQUEST_URI "/PasswordRecovery.php" "t:none,log, \ msg:'Password Recovery Page Detected',chain,redirect:/passwd.html" SecRule &SESSION:PASS "!@eq 1"
The above rule detects whether the password recovery page was requested, redirects the user to our psuedo password recovery script, and finally the script only matches if the SESSION.PASS variable is not set. We set the SESSION.PASS variable next if the user sends a request with the proper one time token.
The /passwd.html simply contains a form where users submit there username via the RecoverUserId parameter.
# Only run once
SecRule ARGS:RecoverUserId ^(.+)$ \
"phase:2,capture,log,chain, \
pass,setvar:SESSION.USERNAME=%{TX.0}, \
setvar:SESSION.TOKEN=SuperSecretString%{TX.0}-%{TIME_EPOCH}, \
msg:'Initializing User Collection with %{ARGS.RecoverUserId}'"
SecRule &ARGS:RecoverUserId "@eq 1" chain
SecRule &SESSION:RANDOMTOKEN "@eq 0" chain
SecRule SESSION:TOKEN ^(.+)$ "capture,t:none,t:sha1,t:hexEncode, \
setvar:SESSION.RANDOMTOKEN=%{TX.1}"
The rule above detects whether a single RecoverUserId parameter was submitted. If so, parse out the submitted username and set the SESSION.USERNAME variable.
Next, we create a SESSION.TOKEN variable that is made up of a random secret, the submitted username and the unix epoch. This is obviously a poor man's hack to create a one time token in ModSecurity rules. Ideally, we could create the one time token in a Lua script and set the token in a collection variable, but unfortunetely ModSecurity does not currently support this.
If the SESSION.RANDOM variable has not been set yet we create a sha1 hex encoded hash of the SESSION.TOKEN variable. We only want to run this rule once to prevent an attacker from continuously changing the valid one time hash effectively creating a denial of service condition preventing the user from regaining control of their account.
SecRule &ARGS:RecoverUserId "@eq 1" "phase:2,redirect:/passwd1.html, \ exec:/opt/modsecurity/etc/rules/PasswordRecovery.lua"
This rule verifies again that only one RecoverUserId parameter was submitted, executes our password recovery script and redirects the user to /passwd1.html. The main advantage to using a Lua script (besides for efficiency) is that Lua can access all of the ModSecurity variables including our collection data.
I imagine the redirection rules seems a bit peculiar. As mentioned before, the /passwd.html web page was a simple HTML form the submitted the client's username to allow ModSecurity to parse the value and set it in a session collection. Instead of actually creating a proper script we use ModSecurity to detect that RecoverUserId was submitted and then redirect the user to a generic web page simulating an actual script. The generic web page simply states if the username submitted was valid, an email will be sent shortly containing further instructions.
-- Requires the following Debian packages
-- liblua5.1-md5-0 liblua5.1-socket2 liblua5.1-rex-pcre0
-- lua-apr
function main()
local smtp = require "socket.smtp"
local http = require "socket.http"
local rex = require "rex_pcre"
-- Retrieve global user value
local username = m.getvar("SESSION.USERNAME");
local token = m.getvar("SESSION.RANDOMTOKEN");
-- Create URL variable to query
local url = "http://private.example.com/?user_id=" .. username
-- Query the user's email
-- html = http.request(url);
-- Parse the email address from the response
local email = rex.match(html, "(.*?) ")
-- Create one time short lived random email
local randomurl = "http://example.com/?PasswordRecoveryToken="
.. token
r, e = smtp.send {
from = 'admin@example.com',
rcpt = email,
server = 'localhost',
source = smtp.message{
headers = {
to = email,
From = "admin@example.com",
subject = "Password Recovery Instructions"
},
body = "To reset your account please click " ..
"on the following link: " .. randomurl
}
}
return nil;
end
Every Lua rule needs to have an entry point that ModSecurity can find, which is the main() function. Next we include various packages and create the username and token parameters. These use the m.getvar() function to retrieve ModSecurity variables. In this case, we were able to query a private web app created for this purpose to lookup user's email addresses. Another option would be to store the mapping in a flat file and have Lua parse that file instead. After querying the email address, we parse out the value from the XML formatted response and create the randomurl variable that contains the URL with the one time token. Finally we send an email to the user that includes further instructions.
SecRule &ARGS:PasswordRecoveryToken "@eq 1" \
"phase:2,chain,t:none,redirect:/PasswordRecovery.php,log, \
msg:'Enabling SESSION.PASS %{SESSION:RANDOMTOKEN} eq
%{ARGS.PasswordRecoveryToken}'"
SecRule ARGS:PasswordRecoveryToken "@eq %{SESSION.RANDOMTOKEN}" \
setvar:SESSION.PASS=1
In the rule above, we check that there is only one PasswordRecoveryToken parameter submitted, to prevent brute force attacks. If there is then we compare the value of the PasswordRecoveryToken parameter to the value previously saved in the SESSION collection. If they match then we set the SESSION.PASS parameter to 1 and redirect the user to /PasswordRecovery.php. This time around, however, our previous rule looking for requests for this URL wont match due to the SESSION.PASS parameter set to 1.
Post new comment