Reputation: 150
I'm trying to understand how flask-security
token creation works. I have a secret key in my flask
app configuration, but our code is open source and in python, so it's not very secret, really.
I have the ability in the code to access the serializer that created the token:
serializer = current_app.extensions['security'].remember_token_serializer
And then I can do serializer.loads(token)
and receive the username and the hashed password.
So my question is this: what prevents a malicious user listening to my traffic, that has the secret key, from doing the same thing - getting a flask-security
token serializer, and deserializing my token? Is there some other salt that flask
uses when creating the tokens? But then, how would it decipher the token if the service was to be restarted?
Upvotes: 1
Views: 1468
Reputation: 9634
I'm not entirely sure about the implementation details and how the token is generated (but I'm pretty sure flask security uses itsdangerous
).
The main use of the token is not to store secrets that you don't want anyone to see, because the token can easily be decoded and viewed by anyone (That's the reason an extra step was taken to store the password hash that can't be decrypted).
The token is used to send data from one source (The client) to another (The server) and ensure that the data is not Modified or changed by anyone
Yes anyone can look inside and see what is contained in the token, the point is that without the secret key no one can tamper with the data, if an attacker generates his own token, he has to do that with the secret key
else the token would be rejected by flask security because it's not valid.
Here is a quick demo of what i mean using itsdangerous (Flask itself uses this library for sessions)
Let's assume that in your application you only grant admin access to users by checking if they have the parameter is_admin
set to 'true'
in the token.
A non-admin user John authenticates and gets his token sent to him.
from itsdangerous import URLSafeSerializer
>>> s = URLSafeSerializer('secret key')
>>> s.dumps({'username': 'john', 'is_admin': 'false'})
'eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0.k45WPrVOG1Nrags0bwpVUbS7Vcw'
Somehow along the line, an attacker intercepts the token and being smart enough he knows the tokens are base64 encoded strings, so they can easily be decoded back back to their original form. Python even has a module in the standard library for this, so he doesn't have to stress himself.
import base64
>>> base64.b64decode('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0===')
b'{"username":"john","is_admin":"false"}'
Just like that he's able to decode our token and see what's inside (This is why the password was hashed for extra security)
Since he knows the parameters we expecting it would be natural to guess that any user that has is_admin
set to true
has more access to things that a normal user wouldn't, he then proceeds to generate his own token.
Since he doesn't have our server secret, he just uses a random secret
>>> t = URLSafeSerializer('fake key')
>>> t.dumps({'username': 'john', 'is_admin': 'true'})
'eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.fPUeGmfSaQWCysy5WWTmmMeuo6c'
Good, then he sends the token to our server.
When we try to decode the token with our Serializer (that has been configured to use our server key) we'll get an error. and the attacker can't get in because he used the wrong secret to sign his token.
>>> s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.fPUeGmfSaQWCysy5WWTmmMeuo6c')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 582, in loads
return self.load_payload(self.make_signer(salt).unsign(s))
File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 374, in unsign
payload=value)
itsdangerous.BadSignature: Signature b'fPUeGmfSaQWCysy5WWTmmMeuo6c' does not match
>>>
The server can only validate tokens that were signed with the key it has. So as far as your server key hasn't been compromised you don't need to worry about anyone sending modified data to your application.
You only have to make sure you don't store "real secrets" in the token
# We can always load the correct token back
s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0.k45WPrVOG1Nrags0bwpVUbS7Vcw')
{'username': 'john', 'is_admin': 'false'}
Even if you try to attach the signature from the right token to the wrong data it won't be valid and you'll still get an error
s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.k45WPrVOG1Nrags0bwpVUbS7Vcw')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 582, in loads
return self.load_payload(self.make_signer(salt).unsign(s))
File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 374, in unsign
payload=value)
itsdangerous.BadSignature: Signature b'k45WPrVOG1Nrags0bwpVUbS7Vcw' does not match
Disclaimer: This might not be the exact implementation in flask security but my answer should give you an idea on what is going on behind the scenes. You might also want to read up JSON web tokens
and don't store your secrets in your configuration file, store it in a .env
(which wouldn't be part of source control) or make it an environment variable and EXPORT/SET the variable in the production environment.
Checkout dotenv
Upvotes: 4