
Secure Account Recovery Made Simple - CiPHPerCoder
https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-account-recovery-made-simple
======
tptacek
I like Scott a lot but I do not love this post.

He's right: don't ask for security questions from your users. Security
questions are ridiculous.

He's wrong about making password reset optional. In 10+ years of consulting at
Matasano I can't think of a single application for which users could sign up
directly that didn't have password reset. You're asking for a support
nightmare by making it optional --- worse, you're putting your engineering
team in a position where they can be socially engineered into changing
passwords, and this is an attack vector that actually does get used in the
wild.

The password reset scheme here is, I think, overcomplicated. If you generate a
long random token _and associate it with the user 's account_, there's no
significant risk of timing attacks (I'd go a step farther and suggest there
probably isn't a realistic risk of timing attacks against tokens either way,
but I'd want to confirm that suspicion with some research first). Just look up
the reset by the token value, invalidate the reset record (you want to save
them anyways), and then check whether the email matches.

If you really, really wanted to be paranoid about it, you could just HMAC the
token against a secret you pass in through the environment, so that the
attacker can't even map inputs to database entries (in the same manner as they
can't meaningfully launch a timing attack against a password hash).

But nerdery aside, unless I'm missing it, this piece seems to be lacking one
of the most important controls for password resets:

Invalidate reset records after use, after any password change, and after a
fixed (short) amount of time.

It is amazing how many applications have password reset schemes that leave
login-equivalent data in people's email boxes for all time.

~~~
CiPHPerCoder
> But nerdery aside, unless I'm missing it, this piece seems to be lacking one
> of the most important controls for password resets:

> Invalidate reset records after use, after any password change, and after a
> fixed (short) amount of time.

That was (somewhat implicitly) covered in the "what incumbent systems" do:
[https://paragonie.com/blog/2016/09/untangling-forget-me-
knot...](https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-
account-recovery-made-simple#secure-password-reset-tokens)

Since you mention it, I'll make that a separate list item. It's important
enough to risk being repetitive about.

~~~
tptacek
I don't see it anywhere. You should make it very explicit, since it's a
mistake _lots_ of systems make. There are popular applications where you can
use password reset emails over and over again.

I would say the first most important attribute of a password reset scheme is
that it not be based on weak secondary passwords like "Mother's maiden name".

The second most important attribute of a password reset scheme is that it be
based on a simple lookup of a random token, and not elaborate encryption
schemes, since those schemes tend to cough up game-over auth bypass bugs.

The third post important attribute of a password reset scheme is that they be
single-use and rigorously invalidated, so that your application isn't spewing
hazmat over people's email spools.

~~~
CiPHPerCoder
Okay, I've made it explicit.

[https://paragonie.com/blog/2016/09/untangling-forget-me-
knot...](https://paragonie.com/blog/2016/09/untangling-forget-me-knot-secure-
account-recovery-made-simple#expire-tokens)

------
kevinpet
The suggestion to make password reset opt-in during signup will never fly.
Useful advise starts from the actual behavior of today's users, not the
hypothetical behavior of a perfect user.

~~~
tptacek
It is really difficult for me to think of an application used by consumers
that could sustainably manage password reset as an option, _even if the option
defaulted to "on"_. Just having the option would generate an untenable amount
of expensive support calls.

~~~
CiPHPerCoder
> Just having the option would generate an untenable amount of expensive
> support calls.

I can see the potential for that to be true, but I don't think there's any
evidence that this is necessarily true.

Specifically: If you bury the option where only power users (i.e. the kind of
users who are likely to use GnuPG and KeePassX) will trivially find it, the
sort of folks who never change defaults would leave it enabled.

------
stringlytyped
I am probably missing something obvious, but I am not sure how a timing attack
would work during the verification stage (when the emailed token is compared
against the database to ensure that it is valid).

If an attacker provides an invalid token, the record wouldn't be found in the
database, and the web app would return an error indicating an invalid token.
When a valid token is provided, the user is authenticated. You would
immediately know whether a token is valid or not—no timing requiring.

However, perhaps there is potential for a timing attack during the initial
stage of the reset process, when the user is asking to enter their email
address? If the email address provided exists in the database, the server has
to 1) generate a token, 2) save the token in the database and 3) send out an
email with the generated token. If the email address doesn't exist in the
database, the server doesn't have to perform any of these functions.

Potentially, couldn't this allow an attacker to enumerate the email addresses
in the web app's database? Of course, in and of itself, this wouldn't allow an
attacker to access any of those accounts. And the split-token method suggested
by the article wouldn't prevent this enumeration issue.

Is there further potential for a timing attack that I am missing?

EDIT: fixed a typo

~~~
stringlytyped
I been doing some reading about this in an attempt to answer my own question.
It turns out, there is potential for a timing attack during the verification
stage. Provided you store the plain text token in the database, an attacker
can deduce a valid token by submitting various guesses to the server.

Hashing the token protects against this.

For more detail: [http://blog.ircmaxell.com/2014/11/its-all-about-
time.html](http://blog.ircmaxell.com/2014/11/its-all-about-time.html)

------
aftbit
What's wrong with sending the user a JWT[0] with a short expiration time as
their password reset token? For example,

{ "account_id": 12345678, "password_reset_token":
"bohghai6fui1Ma7ozaF3nu2PheV2eeroh8daonoh6ceiCub3joengei2Lohhu8ti", "exp":
1474737955 }

Set the expiration to be 5 minutes from the time the token is generated. This
leaves open the possibility that the same token could be used to reset the
user's password multiple times, but you could avoid that by including the hash
of the user's previous password in the token, or by keeping a "password
changes" counter in the database and including that value in the token.

0:
[https://tools.ietf.org/html/rfc7519#page-9](https://tools.ietf.org/html/rfc7519#page-9)
or [https://jwt.io](https://jwt.io)

------
munin
Is there an example of the (specific) timing attack mentioned in the article
being exploited?

------
yid
Whenever I see "base64" mentioned in a security article, I get cautious.

The "split token" password reset is snake oil. Just store the hash of the
token (ideally stretched like any password) in the database and mail the
original token out. No need for "split tokens". A password reset token is a
temporary password and should be treated like one.

~~~
UnoriginalGuy
base64 is just a data storage format and something that is required if you
want to push data via GET. They're talking about pseudorandom numbers using a
CSPRNG, with appropriate length, that's normally adequate for this scenario.

Can you explain in more detail why base64 makes you "cautious?"

> Just store the hash of the token (ideally stretched like any password) in
> the database

That's actually LESS secure. Their scheme has an ID (or "selector") and a hash
("verifier"). This means you can limit attempts against a single ID/account
and also aren't going to compare a hash entered to every record in that table.

What you're proposing is massively weaker than what they propose. It also has
timing attack problems.

~~~
tinus_hn
Base64 is an implementation detail so it is kind of strange to see it
mentioned here. It also obfuscates things so you often see it used in insecure
solutions.

That is why the use of base64 makes this look suspect even though on close
inspection the ideas are probably valid.

~~~
CiPHPerCoder
The other post that was linked in the snippet that mentions base64 is far more
likely to make people nervous: [https://paragonie.com/blog/2016/06/constant-
time-encoding-bo...](https://paragonie.com/blog/2016/06/constant-time-
encoding-boring-cryptography-rfc-4648-and-you)

It's about an implementation of RFC 4648 encoding (including base64, base32,
etc.) that doesn't index based on secret data.

Consequently, if a practical cache-timing exploit is ever demonstrated in
existing implementations of encoding functions, the open source library we
wrote will be immune.

