The internal API
had one shared token.
Each request carried it in a header,
and the server compared it
with the one in its config.
If the strings were equal,
come in.
It looked like the safest line
in the whole service.
It was the line
that leaked the token.
An ordinary string comparison
is written to be fast.
It looks at the first character.
If they differ, it stops.
If they match, it moves to the second.
So a guess that is wrong
at the first character
is rejected a tiny bit sooner
than a guess
that is wrong at the fifth.
Tiny is a few nanoseconds.
Too small to see once.
Not too small to see
across a hundred thousand requests,
averaged,
from a machine in the same data centre,
which is where an attacker
who already has a foothold
tends to be standing.
Try every first character.
Keep the one that is slowest.
Move to the second.
The search stops being
every possible token
and becomes
every possible character,
one position at a time.
The server is not giving away
the secret.
It is giving away the score.
Compare secrets in constant time.
Every serious language ships a function for it.
MessageDigest isEqual in Java.
compare_digest in Python.
timingSafeEqual in Node,
which also wants both values
the same length,
so hash them first.
That one change closes this.
Then make the comparison
matter less.
Do not keep the token itself
on the server.
Keep a hash of it,
hash what arrives,
and look up by the hash,
so the thing you compare
is never the thing an attacker wants.
Give each client its own token,
so one leak is one revocation,
not a weekend of rotating everything.
And rate limit failed attempts,
so nobody gets to ask
a hundred thousand times.
The same habit applies
to webhook signatures,
password reset codes,
session ids,
anything where equal means trusted.
A check that is fast to say no
is telling the attacker
how close they got.
– Serguey Asael Shinder
Top comments (0)