Bug #298
security issue in email password reset
| Status: | Closed | Start: | 18/01/2011 | |
|---|---|---|---|---|
| Priority: | High | Due date: | ||
| Assigned to: | % Done: | 100% |
||
| Category: | Self Service Password | |||
| Target version: | self-service-password-0.5 |
Description
Anybody, who knows the username and email address of another user can reset that persons password without having access to the corresponding email account.
The reason for this is the us of the php session id as the email token. The session id is regularly sent to the requester's web browser as a cookie. However, the requestor does not necessarily have to be the owner of the account to be reset. The requester can easily read the session id from his browsers cookie store and compose a valid reset url without having access to the email sent to the account owner.
To cope with this problem, I propose the following:
- Do no longer use the php session id as the token
- instead, use an ancrypted token. The encryption key must only be stored on the server. The token will not be sent to the webbrowser.
- The token might contain the login name and a timestamp. This has the additional benefit of a token lifetime that is independent of the php session life time
I did a proof of concept implementation of my suggestion:
In file config.inc.php I added two parameters:
# secret to encrypt email token $keyphrase = "secret"; #lifetime (in seconds) of token for email password reset $token_lifetime = 60*60
In file functions.inc.php I added two functions for encrypting (AES256) / decrypting and base64 encoding / deconding the token:
function enctoken($token) function dectoken($token)
In File sendtoken.php the token is generated in section "Send token by mail":
# create the token
$token = time() .";" . $login;
$enctoken = enctoken($token);
# Build reset by token URL
$method = "http";
if ( $_SERVER['HTTPS'] ) { $method .= "s"; }
$server_name = $_SERVER['SERVER_NAME'];
$script_name = $_SERVER['SCRIPT_NAME'];
$reset_url = $method."://".$server_name.$script_name."?action=resetbytoken&token=".$enctoken;
Finally, in file resetbytoken.php the token is validated in section "Get token":
$dectoken = dectoken($_REQUEST["token"]);
$tokentime = strstr($dectoken, ';', true);
$login = substr($dectoken, strpos($dectoken, ';')+1);
and the token lifetime is checked:
if ( time() - $tokentime > $token_lifetime ) {
$result = "tokennotvalid";
error_log("Token lifetime expired");
}
Thank you for your nice library! I hope my suggestions are helpful.
Kind regards,
Matthias
Associated revisions
Disable output buffering (references #298)
Add encrypt/decrypt functions (references #298)
Use encrypt/decrypt functions for tokens (references #298)
History
Updated by Clément OUDOT over 1 year ago
- Status changed from New to Assigned
- Assigned to set to Clément OUDOT
- Target version set to self-service-password-0.5
Hello Matthias,
thanks for using SSP!
For me there is no security issue, because we use the PHP session mechanism after HTTP headers are sent, so the session id is never sent to the client. You can check this by reading the cookies in your browser, or if you set the debug mode, you will see those messages:
Warning: session_start() [function.session-start]: Cannot send session cookie - headers already sent by (output started at /usr/local/self-service-password/index.php:64) in /usr/local/self-service-password/pages/sendtoken.php on line 112
But your solution seems ok too, we can maybe propose each of them in the software.
I like the idea to use PHP sessions mechanism, because you can then use different session backend, just by configuring PHP (it will be independent from SSP).
The token lifetime issue is already opened here: #290.
And I would like to have a one-time token (see #289), which is not possible with your code.
Updated by Matthias Ganzinger over 1 year ago
Hello Clément,
thanks for your reply.
I investigated the problem a little further. Obviously, the reason why in my case cookies were set after the headers were sent is the activation of output buffering. I am using OpenSUSE 11.3 and on this system output_buffering is set to 4096 by default. If I change this setting to 0 I also get the log entry you cited and no cookies are sent.
Perhaps it is not the cleanest way to use start_session() without checking if any output buffering is active. Another option would be to encrypt the SID sent by email e.g. using my enctoken function.
At least, I suggest to include a warning note into the documentation.
Regards,
Matthias
Updated by Clément OUDOT over 1 year ago
Matthias Ganzinger wrote:
Hello Clément,
thanks for your reply.
I investigated the problem a little further. Obviously, the reason why in my case cookies were set after the headers were sent is the activation of output buffering. I am using OpenSUSE 11.3 and on this system output_buffering is set to 4096 by default. If I change this setting to 0 I also get the log entry you cited and no cookies are sent.
Perhaps it is not the cleanest way to use start_session() without checking if any output buffering is active. Another option would be to encrypt the SID sent by email e.g. using my enctoken function.
This information is very interesting. I will use your enctoken function for the token, in order to prevent any security problems.
At least, I suggest to include a warning note into the documentation.
I will try to release 0.5 soon with the enctoken function.
Updated by Clément OUDOT about 1 year ago
- Status changed from Assigned to Closed
- % Done changed from 70 to 100