Laravel Stored XSS

I have been using the Laravel framework for quite a while now and discovered something odd.

When following the installation instructions for the latest Laravel version (5.6.8 at the time of writing) you will be up and running in a matter of minutes.

Even better: With a single command such as php artisan make:auth from the CLI, Laravel will scaffold basic login and registration views and routes.

This is a very convenient function and saves developers a lot of time. But what if there is some resource included that might be vulnerable?

POC

Vulnerability: Persistent Cross-site Scripting.

Installation

Following the instruction from the Laravel Docs we can quickly create a new project with basic authentication.

You can use the Laravel installer: laravel new project (where project is the directory that is going to be used for your new project.)

This requires you to global require the laravel installer first: composer global require "laravel/installer"

An other option is the “old” way: composer create-project --prefer-dist laravel/laravel project

After running one of the above commands your project will be created and you can start developing a new application. Pretty neat.

I am using the Vagrant/Homestead for local development. If you haven’t tried it and you are developing PHP applications I recommend checking this out!

Laravel installation

User registration and authentication

We now have a fresh Laravel project. Let’s start with building the user registration and authentication functionality: php artisan make:auth

This will create a view, some controllers and database migrations.

When this is done all you have to do is php artisan migrate and you are ready to go.

php artisan migrate

The vulnerability

So, the setup is ready. Time to show the vulnerability!

Since we used the php artisan make:auth command we now have a login and registration function.

Registration form

Let’s create a user:

Payload in registration form

The vulnerability lies within the username field: using {{ alert(document.domain) }} , Laravel transforms this to <script>alert(document.domain)</script> due to Vue.js frontend. This is the default frontend framework that Laravel uses.

After checking the app.js file in /project/resources/assets/js/ we can see that Laravel indeed uses Vue.js as default frontend framework:


/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

window.Vue = require('vue');

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

Vue.component('example-component', require('./components/ExampleComponent.vue'));

const app = new Vue({
    el: '#app'
});

This is where the vulnerability hides, as Vue sees curly brackets and turns it into <script> tags. Also, the v-pre tags were not present in the default front end code, combined with the curly brackets this led to the persistent cross-site scripting vulnerability

After login with a registered user with a username that contains a payload:

XSS POC

Issue has been reported to Taylor, the creator of Laravel, through e-mail and Twitter.


*** Update: Patch 5.6.9 is out now, where Taylor fixed the XSS vulnerability by adding v-pre to the username field. This was about 15 minutes after I tweeted about my finding/blogpost.

If you like my work you can buy me a coffee :)