Improving In-Place Editing Security

Improving In-Place Editing Security

Running any web-based CMS always comes with the very real security risk of getting malicious code injected that is then served to a victim.

Prior to launching your Scrivito application, you should secure it to avoid exploitations that could affect your website visitors and/or the content on your website.

The problem: Cross-site scripting (XSS)

One of the basic security rules for web applications is this one:

No user input data is secure, until proven otherwise, and all user input is potentially manipulated.

You may know the few people editing content on your website to be loyal, but you still want to be immune to accidents and hackers.

Every page displays user input (your website content is input coming from your editors), and this makes your application vulnerable to a whole range of attacks: Cross-Site Scripting, or XSS.

To illustrate the problem, start a working Scrivito application locally, navigate your web browser to any page that includes a widget attribute, and add the following line into an existing or newly created widget, using the HTML editor:

Whenever the site is loaded, your web browser will try to load an image from the URL /missing_image. Because your application responds with an HTTP error, the JavaScript code in the onerror handler is invoked.

Note that there is no user interaction required to invoke the error handler.

Granted, this code merely annoys the user by showing alert boxes over and over again but the error handler may contain any malicious JavaScript code. An attacker may, for example, load an external script and collect cookies from logged-in users to impersonate them. Depending on the functionality of your website, the consequences of this may be very serious.

And because this XSS attack is stored on the server, once this change is published, it affects your editors and website visitors alike.

It gets even worse

Scrivito's in-place editing functionality requires a powerful JavaScript API. This API provides write-access to CMS objects and the ability to publish changes.

The in-place editing API features permissions, and you are strongly advised to make use of them to restrict potentially dangerous operations such as publishing changes to specific users. For example, see how to prevent users from publishing their own working copies in the Ruby API documentation.

However, using the technique described above, a malicious user (Mallory) may inject JavaScript code that makes use of Scrivito's JavaScript API to fetch all CMS objects, delete them and publish the changes. Even though permissions keep her from executing the code herself successfully, she can circumvent the permissions because the code is stored on the server and can be used against other users with higher privileges: Mallory tricks a supereditor (or anyone else known to have the permission to publish changes) into looking at the changes simply by inviting them to the working copy. When the collaborator with the required permission loads the page, CMS objects are deleted and the changes are published immediately.

This is a type of attack known as privilege elevation because a lower-privileged attacker can access functions that should not be available to them.
The JavaScript may even include error handling to not raise suspicion. While happily destroying your entire website content, the privileged user may not even notice what happened until it is too late.

Apart from the organizational work to restore your website, this can quickly become a financial disaster if your organization is required by law to keep particular pieces of information available online.

The solution

At this point, you may wonder whether it is feasible to disallow markup in your content. Doing so would restrict your editors to plain text and escape all content. But this defeats the purpose of having a CMS in the first place.

Think about the root cause: All these attacks work because the browser executes the JavaScript code from an untrusted location (the website content). The browser must be instructed which code sources to trust. The Content Security Policy (CSP) is the perfect way to do this:

Instead of blindly trusting everything that a server delivers, CSP defines the Content-Security-Policy HTTP header that allows you to create a whitelist of sources of trusted content, and instructs the browser to only execute or render resources from those sources. Even if an attacker can find a hole through which to inject script, the script won’t match the whitelist, and therefore won’t be executed.

For more information about CSP, please refer to this Introduction to Content Security Policy.

Scrivito supports the use of CSP by not rendering any script tags in your application. This makes it easier to get started with a working Content-Security-Policy header.

How to secure your application

We recommend using the secure_headers gem. It provides useful defaults and is easy to integrate.

Add to your Gemfile:

And then execute:

Provide an initializer, config/initializers/csp.rb:

Restart your server and then check for the HTTP header:

You will notice that the HTTP header is Content-Security-Policy-Report-Only. This means the policy is only monitored but not enforced. For a start, resources, code and CSS are allowed only from the application itself, fonts are additionally allowed from data-URIs. This is the minimum whitelist required for Scrivito at the moment.

When reloading a page of the website in your browser, you should see warnings in the JavaScript console about violations of a Content Security Policy directive. Please refer to the secure_headers documentation for more detailed header examples.

If you insert the following into the HTML markup of a page again:

You should see the warning onerror attribute on IMG element. Ideally, this is the only warning referring to a CSP directive you should see. If it's not, the warnings should point you to the source of the problem. If you fixed all other warnings, good for you!

At this point, the JavaScript alert still appears because the violations are just reported but not blocked. To actually block them, change the initializer to this:

Restart your server and then check for the HTTP header:

Reload the page that previously showed a JavaScript alert and the alert does not appear anymore. The JavaScript console logs that a violation was blocked.

Congratulations, you have successfully prevented a whole range of potential XSS attacks by malicious editors!

There is also a tool from mozilla that helps you to check your csp and gives informations what else can be done for security: