In deze blogpost gaan we dieper in op een aantal veelvoorkomende Laravel beveiligingsfouten die ik ben tegengekomen in projecten, persoonlijk heb gemaakt of zelfs ben tegengekomen op door Stack Overflow goedgekeurde antwoorden. We zullen het kort hebben over bestandsvalidatie, massatoewijzing en Laravel's query builder. Het doel van deze blog is om te laten zien hoe kleine, gemakkelijk te vermijden fouten een grote impact kunnen hebben op de beveiliging van je applicatie. En hopelijk om te voorkomen dat je ze in de toekomst maakt. Dus laten we beginnen!
Beveiliging van Laravel 101
Bestand valideren
Laravel biedt een robuust en veilig bestandssysteem waarmee ontwikkelaars bestanden kunnen opslaan en ophalen uit lokale of cloud-opslag. Hieronder staat wat code, rechtstreeks van Stackoverflow, om het uploaden van een avatar naar een profiel af te handelen:
Kun je zien hoe een aanvaller misbruik zou kunnen maken van het volgende fragment?
public function storeImage(Request $request)
{
$request->validate([
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
$imageName = Str::random(16) . '.' . $request->image->getClientOriginalExtension();
Storage::disk('public')->putFileAs("avatars", $request->image, $imageName);
return back()->with('success','You have successfully uploaded your avatar!');
}
Stel, een aanvaller wil een .html bestand met een XSS (Cross-site scripting) payload uploaden naar je server. Je zou verwachten dat Laravel's server-side validatie image en mimes:jpeg,png,jpg,gif,svg dit zou oplossen.
Sidenote: Dit is geen bug in Laravel, maar gewoon hoe mime type controle werkt.
De cruciale fout die wordt gemaakt is het gebruik van Laravel's getClientOriginalExtension methode om de extensie van het bestand direct uit het verzoek te halen. In plaats daarvan willen we in dit scenario gebruik maken van:
$request->image->extension();
Dit zal het mime type van het bestand raden op basis van de inhoud van het daadwerkelijke bestand in plaats van wat wordt ontvangen van de client.
Een andere veilige manier om hiermee om te gaan is door Laravel's put methode te gebruiken om bestanden op te slaan:
Storage::disk('public')->put(Str::random(16), $request->image);
Dit zal automatisch je bestandsnaam vervangen door een willekeurige hash en de juiste extensie toevoegen.
De belangrijkste afleiding hier is om nooit te vertrouwen op gebruikersinvoer, vooral als het gaat om het uploaden van bestanden.
Massa toewijzing
Kun jij het beveiligingslek in deze code vinden?
Gebruikersmodel
class User extends Model {
protected $fillable = ['username', 'email', 'password', 'role'];
}
Registercontroller
class RegisterController
{
public function create(Request $request)
{
$request->validate([
'username' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:12|confirmed',
]);
$user = new User();
$user->role = 'guest'
$user->fill($request->all());
$user->save();
return response()->json(['success' => true],201);
}
}
Als een kwaadwillende gebruiker het registratieverzoek vervalst en "rol": "admin" toevoegt aan de payload van het formulier. Laravel zou deze gebruiker graag toegang geven tot je hele systeem.
Dit komt doordat $request→all() niet alleen gevalideerde gegevens uit het verzoek haalt, maar ALLE gegevens. In combinatie met de rol die is toegevoegd aan de $fillable property van het model, zou de gebruiker worden aangemaakt met de rol "admin".
Voorkom dit door $request→validated() te gebruiken om alleen gevalideerde invoer op te halen uit je request. Of zorg ervoor dat je invulbare eigenschappen correct zijn ingesteld.
Bonuspunten als je hebt gezien dat het wachtwoord in platte tekst wordt opgeslagen ;)
Query builder parameterbinding
SQL-injectie is een veel voorkomende aanvalsvector voor webtoepassingen, en Laravel is daarop geen uitzondering. Het treedt op wanneer een aanvaller kwaadaardige SQL-code kan invoeren in de invoervelden of query-strings van een webtoepassing.
Hier is een voorbeeld van SQL-injectie met behulp van de query builder van Laravel:
//User input
$search = "1; DROP TABLE users;";
DB::table('users')->whereRaw("name = " . $search)->get();
Deze code is kwetsbaar voor SQL-injectie omdat de aanvaller willekeurige SQL-code kan uitvoeren door de variabele $search in te stellen op een tekenreeks die SQL-opdrachten bevat. In dit geval kan de aanvaller de hele tabel users verwijderen door $search in te stellen op "1; DROP TABLE users;".
Om SQL injectie aanvallen te voorkomen, moet je altijd gebruik maken van Laravel's query builder of parameter binding bij het construeren van SQL queries.
Voorbeeld van parameter binding:
$search = "1; DROP TABLE users;";
DB::table('users')->whereRaw("name = ?", $search)->get();
Conclusie
Hoewel de bovenstaande voorbeelden voor sommigen voor de hand liggend kunnen zijn, hoop ik dat dit artikel het belang illustreert van een goed begrip van de innerlijke werking van een framework, zelfs als het naadloos lijkt om te gaan met beveiliging. Ondanks Laravel's beginnersvriendelijke aard en geminimaliseerde overhead voor ontwikkelaars, vergeet niet om voorzichtig te blijven en zelfgenoegzaamheid te vermijden.