This blog post should help potential contributors understand how to make backend changes while exploring some topics in security.
For this excercise, let’s think about the limitations of secure web apps and why you shouldn’t trust proprietary code! Hopefully you learn a little about our security model along the way. Let’s say you work for generic evil company and want to steal everyone’s password. What can you do?
Attack the backend
If you’re looking to do something malicious, changing the backend has a big advantage: users won’t ever know if they don’t self-host! The major disadvantage is that Passit’s backend can’t ever truly determine what users are storing on it.
- Clone the repo:
git clone [email protected]:passit/passit-backend.git
- Follow the backend’s readme to run the project locally.
While we can’t just scoop up passwords, we can monitor incoming requests and see what the client sends for authentication. In a normal Django app this would be the user’s typed-in password! If we had that, we could decrypt their secrets. However, Passit’s frontend hashes the password with argon2 (from libsodium) before sending it to the server. In effect, the hashed password becomes the password when authenticating to the backend. Django then hashes the password again and compares it to the local database to check if it’s correct. It does not store the client-hashed password. That’s really nice because if someone stole the client-hashed version they could authenticate to the backend and download ciphertext.
To steal passwords we need two things:
- The ciphertext to decrypt
- A way to decrypt it
If we can authenticate using the user’s hashed password, that’s half the battle already. Of course, if we had database access we can just yank the ciphertext anyway. We could also just brute force (guess) the hash, but that would take a very long time and would probably get stopped by rate limiting. So, for this exercise let’s assume we don’t have unrestricted database access, but can try to add some evil code into the app.
Open apps/auth/views.py
which is our Django settings file. This lets us define authentication backends. We can inject a malicious backend here.
apps/auth/views.py
...
class LoginView(KnoxLoginView):
authentication_classes = [BasicAuthentication]
Inject a new one:
...
from steal_hashes import StealHashesAuthentication
class LoginView(KnoxLoginView):
authentication_classes = [StealHashesAuthentication, BasicAuthentication]
Make a new file in the root folder called steal_hashes.py.
from rest_framework.authentication import (
BaseAuthentication,
)
class StealHashesAuthentication(BaseAuthentication):
def authenticate(self, request):
print(request.META.get('HTTP_AUTHORIZATION'))
This header is just a base64 of the client-hashed password. So now we have it. We could make this better by sending it to a remote server and obfuscating the code so it’s not obvious what it’s doing.
This is easy, right? What backends might already be doing this? Do you reuse passwords? What would stop a company from logging your plaintext password and trying to authenticate to other sites it may already know you use? Hashing passwords in the database is nice, but doesn’t prevent this.
Now you’ve seen how we can change backend code. To finish we should ensure tests still run. We can just run them in docker with
docker-compose run --rm web ./manage.py test
Then just open a merge request in GitLab with the changes. Of course this change would be rejected.
In a future blog post we’ll explore the frontend and Passit SDK to see how they work and what other trouble could be caused.