Excessive data exposure is when an API responds to a request with more data than required. Superficially, it looks like a design flaw. In reality, OWASP Lists this problem as one of the top three API Security threats. If an attacker discovers a method that returns the right fields, they can use an unprivileged account to retrieve the data. As a result, these vulnerabilities can lead to serious data compromises, including leaking user data and creating legal liabilities.
This post will look at the nature of excessive data exposure and how you can protect yourself in Laravel applications.
What Is Excessive Data Exposure?
Many mobile and web apps request API data, receive a superset of the fields they need and filter the results based on what they want to display. So, the application design implicitly relies on the server returning all of its data for a given entity.
For example, consider a typical e-commerce website. It stores client information, including names, emails, and addresses. Here's an example API request for user Id #5 and the response.
GET http://localhost:8000/api/user/5
{
"id": 5,
"name": "Joe Customer",
"email": "joe@example.com",
"email_verified_at": null,
"address": "23 Not Taken Rd.",
"state": "Confusion",
"zip": 22222,
"phone": "(123)444-183",
"created_at": "2022-03-13T20:19:11.000000Z",
"updated_at": "2022-03-13T20:19:11.000000Z",
"api_token": null,
"password": "$2y$10$9kA9PSPPQhZ2jXpZgImZe.Ul6vCKuu8EB5xLsD6WCVKJiI9BQv6aO",
"remember_token": null
}
This request for user Id #5 returns all of the available fields for the user, including the hashed password and an API token, two fields that no display application should even need.
The architecture behind this application expects the display to use the fields it needs and discard the rest. But filtering data in display apps doesn't mean it's not visible. Any application with access to the API can retrieve this excess data, too. So, an attacker with a compromised password or even a spoofed account that has access to user records can retrieve all their information.
Protecting Laravel Applications From Excessive Data Exposure
In the example above, we retrieved information for a user by Id. This idiom is typical in "CRUD" APIs, where applications request the data they need and display the relevant fields based on context.
The response came from a Laravel server. Unfortunately, getting the application to return the excess data didn't require any extra effort. Quite the opposite: the code was from a Laravel RESTful API tutorial.
A Simple RESTful API
Laravel makes it easy to connect a web application to a database.
Most web frameworks advertise this as a feature, and they're right. Instead of writing boilerplate database access code, the framework generates it for you, and you can get to work on your business logic. Laravel does this with its Eloquent ORM. But, from a security standpoint, you might say that its design is optimistic.
The sample code used above has a user object with basic fields for user name, email, address, and basic authentication.
Here's the SQL table that stores the users:
create table users
(
id bigint unsigned auto_increment
primary key,
name varchar(255) not null,
email varchar(255) not null,
email_verified_at timestamp null,
address varchar(255) null,
state varchar(64) null,
zip int null,
phone varchar(255) null,
created_at timestamp null,
updated_at timestamp null,
api_token varchar(60) null,
password varchar(255) not null,
remember_token varchar(100) null,
constraint users_api_token_unique
unique (api_token),
constraint users_email_unique
unique (email)
)
collate=utf8mb4_unicode_ci;
When we give Laravel an empty model class, it figures out how to add, update, retrieve, and delete users from this table for us.
So, here's the model:
class User extends Model
{
}
And here's the controller (with the authentication code removed:)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
class UserController extends Controller
{
public function index()
{
return User::all();
}
public function show(Request $request, $id)
{
return User::find($id);
}
public function store(Request $request)
{
$user = User::create($request->all());
return response()->json($user, 201);
}
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$user->update($request->all());
return response()->json($user, 200);
}
public function delete(Request $request, $id)
{
$user = User::findOrFail($id);
$user->delete();
return response()->json(null, 204);
}
}
This code needs a few routes (that we don't need to see here), and we have a working API.
But as we saw above, the default behavior is to return all of the fields in the model. This is true for any model that uses the default code.
The good news is that there are a few ways to make this code more secure.
Move Your Fields to a Different Table
If your application uses a SQL database, you might consider moving the fields related to authentication to a different table and giving them their own model. Separating concerns this way makes sense from both an architectural and maintenance standpoint. It'll be easier to update your authentication model in the future and often makes your code easier to read.
But if you're using a NoSQL database or have inherited a "mature" codebase, this might not be possible.
Filter Your Fields From JSON
The default model code is generating a SQL query that looks something like this:
select * from users where id=5
If we were writing the application by hand, we would probably consider rewriting the query to only select the fields we need. We could take that approach now and modify the model to use a similar query.
But, that would create some new problems. While the display applications should never see hashed passwords, the authentication system needs them. As soon as we started crippling the model, we created the need for multiple interfaces or even a second model for the back end. As a result, we'd only add a new set of maintenance issues.
Laravel addresses this problem by making it possible to retrieve all the data from the data store while hiding the excess data from JSON. As a result, it's easy to prevent API clients from seeing certain fields while still making them accessible to backend model users. You do this by adding a $hidden field to the model class.
Let's hide the password field:
class User extends Model
{
protected $hidden = ['password'];
}
Now, we rerun the query for user #5:
GET http://localhost:8000/api/user/5
{
"id": 5,
"name": "Joe Customer",
"email": "joe@example.com",
"email_verified_at": null,
"address": "23 Not Taken Rd.",
"state": "Confusion",
"zip": 22222,
"phone": "(123)444-183",
"created_at": "2022-03-13T20:19:11.000000Z",
"updated_at": "2022-03-13T20:19:11.000000Z",
"api_token": null,
"remember_token": null
}
Laravel omitted the password field from the response.
The $hidden field is an array, so you can use it to filter out all of the unwanted data.
class User extends Model
{
protected $hidden = ['password', 'created_at', 'updated_at', 'api_token', 'remember_token', 'email_verified_at' ];
}
Now we have a simple mechanism for ensuring the no display application sees critical backend data:
GET http://localhost:8000/api/user/5
{
"id": 5,
"name": "Joe Customer",
"email": "joe@example.com",
"address": "23 Not Taken Rd.",
"state": "Confusion",
"zip": 22222,
"phone": "(123)444-183"
}
That Which Is Not Explicitly Permitted Is Denied
The $hidden field hides fields by name. It's a powerful tool, but it might not be enough. Web code moves fast, and you may inadvertently add a new field to your model and forget to hide it.
A safer security model is to define a set of fields that are allowed and only pass them to API users.
The $visible field defines the fields that are available to JSON. Laravel will not provide fields missing from this array to API requests.
Let's list name and email as the only visible fields:
class User extends Model
{
protected $visible = ['name', 'email'];
}
Then run a query.
GET http://localhost:8000/api/user/5
{
"name": "Joe Customer",
"email": "joe@example.com"
}
That's nice and clean!
So, we have two robust mechanisms for filtering backend information from display clients and potential hackers. Depending on how you want to address the problem, you can either list the fields that are visible or hidden.
Filter Laravel Fields to Avoid Excess Data Exposure
We've looked at the problem of excess data exposure and how it can compromise your application's security. Laravel's robust model-viewer-controller features make it easy to put a RESTful API together, with a small amount of code. But, its behavior isn't always what you want or need. We saw how the default model provides all available data to requestors. Then we looked at the tools Eloquent provides to make filtering fields from API request easy.
So, you have the tools you need to address Excess Data Exposure in your Laravel code. It's time to take the next step: StackHawk has the tools you need to keep your code and your clients safe. Sign up for a free account and see how!
This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).