Updated 10 July 20
One of the largest problems that occurs with modern software development is when applications are too trusting of data sent to them by the client. The issue is usually down to the fact that the goal of most developers is just to get the code working (consistently) on hundreds of different platforms and browsers, which isn’t an easy task. Hackers know this and so can easily assume that there will be a point somewhere in the application where the reliability of the data being sent to the backend is never called into question.
The easiest way to spot these kinds of flaws is to assume that all clients on the other side of your network are extremely untrustworthy. Once your code is running on someone else's machine you can no longer trust the data sent to your backend.
As an example, if you had a “Delete User” button and a corresponding endpoint “/deleteUser?id=...”. Understandably, these should only be accessible by an administrator. To achieve this, you hide the button behind an admin panel which requires you to log in and to be on a specific IP to ensure no unauthorised person will have access to it.
Yet, if a regular user was to manually access the “/deleteUser” endpoint, the security guarding the button no longer matters and bad things can start to happen...
A basic demonstration of something similar I've encountered can be seen in this example below. 👇
The above function is an example of an endpoint called “getData”. The purpose of this endpoint would be to return data from the users account.
This endpoint is taking in the user's ID in a query parameter, looking up the user and returning some data from that account. I’m sure you can see the issue with this.
The backend is blindly trusting whatever information the client provides to it and will happily return the data for any account that you happen to know the ID for. This becomes even more of an issue if you’re using automatically incrementing IDs or IDs that can be guessed, e.g MongoDB ObjectIDs.
The way I have prevented this being an issue in the past was to never accept data directly from the client and to instead, only trust what we know is the truth (our backend). An example of the fixed “getData” endpoint can be seen below.👇
This was made possible using Koa’s “state” functionality. This means that we can fetch and save the users data into the backend state when they log in, then subsequently fetch any data from the backend state when any request is made to our server knowing that this data cannot be modified and will only return the data of the currently logged in user.
This means that we will never have to accept any data directly from the user, making our application a little bit more secure in the process.
It’s always worth going through every endpoint in your website's backend and thinking about how you could break it because once your app gets big enough, other people will be trying hard to break it too. It’s useful to play around with different types of requests, different data structures to see what happens;
- Can you modify requests?
- Does the endpoint process any data you send to it?
- What happens if you use another user's token?
- If the endpoint is expecting a Json body, what happens if a field is left blank?
- What if the field is changed?
These are just a few simple examples but still worth considering because it’s always better if you can break your own backend before someone with bad intentions gets the chance to.