Lazy Load stored XSS

Overview

Lazy Load is a WordPress plugin with over 90,000 active installs. It was developed by Automattic, TechCrunch, and 10up LLC. The plugin improves page load times by deferring image data loading until they become visible in the viewport.

A flaw allowed a malicious user with editing rights to inject JavaScript in WordPress posts and pages if the plugin was in use.

The bug was fixed in July 2016.

Details

The plugin is relatively simple. It processes <IMG> tags in post or page output with a regular expression to postpone loading the images. Some JavaScript is then used to load them as neccessary.

The regular expression:

// This is a pretty simple regex, but it works
$content = preg_replace( '#<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>#', sprintf( '<img${1}src="%s" data-lazy-src="${2}"${3}><noscript><img${1}src="${2}"${3}></noscript>', $placeholder_image ), $content );

A specially formatted image tag will cause unexpected HTML to be generated. Example:

<img src="/foo onerror=alert(/xss/) // " />

The replacement operation will cause the onerror part to be treated as a valid HTML attribute, allowing injection of JavaScript. This is the HTML generated:

<img src="placeholder.png" data-lazy-src="/foo" onerror=alert(/xss/) //"><noscript><img src="/foo" onerror=alert(/xss) //"></noscript>

Impact

The bug isn’t usually exploitable without authentication. A malicious user with the ability to edit posts (usually from Contributor level upwards) could inject JavaScript in posts. The script would be evaluated when the post is viewed, either by an administrator’s web browser before publication, or in other users’ browsers after publication.

If an administrator is targeted, the vulnerability can be used to execute functions on the victim administrator’s behalf. Under default configuration this can be used for server-side code execution e.g. via the plugin or theme editors.

Vendor response

The bug was reported via the HackerOne platform on July 20, 2016. An update correcting it was released on the same day.

Bug bounty

Automattic confirmed the vulnerability and awarded a $275 bounty two days later.

The plugin was also used on newsroom.uber.com, one of the web sites in scope of Uber’s bug bounty program at the time.

About a month after my report Uber decided that the bug was not eligible for bounty “since you need to have admin privileges to exploit this”.

I explained my idea, that the bug could be exploited by lower-level user accounts to gain administrator privileges. Privilege escalation wouldn’t really be an exploit if you already had admin privileges.

Uber answered that the bug wasn’t exploitable because “the only accounts that exist are already administrator accounts”.

I happened to have the list of accounts on the newsroom system as a PoC for my previous bug reports (some of which are already public on HackerOne). There were over 3,000 user accounts of various levels. Less than 100 of them actually were the highest “super admin” level. In other words it seemed a very viable privilege escalation target for a potential attacker.

At this point the Uber representative thanked me for the new information and told me to wait. Despite inquiries, I didn’t get another update for seven months.

In March next year Uber stated their final position on the matter. According to the new rationale the server is protected with 2FA and any exploit by an employee “would result in employee termination”. Therefore the bug was not exploitable and no bounty was awarded.

My foremost idea had been an external attacker who could first break into a lower level account (despite 2FA) and use this bug to gain access to a “super admin” account. Based on my previous findings it wasn’t an impossible scenario. I had demonstrated many ways of compromising user accounts on this system. It was affected e.g. by an unauthenticated stored XSS. I also reported two methods of bypassing the OneLogin 2FA authentication. They allowed an attacker to authenticate using the standard WordPress password system instead. Some of the users had default passwords. Some had the same trivially crackable passwords they used on another Uber system, hashes of which were retrievable with a SQL injection exploit.

At that stage the Lazy Load vulnerability would have given the attacker full control of the hosted websites and access to all public and private information stored on the system, including details of thousands of employee accounts. Prior to fixing other bugs I had reported, a privilege escalation like this would also have enabled the attacker to achieve server-side code execution on the newsroom server and an Atlassian Confluence server in Uber’s internal network.

Based on this information, the vulnerability was not only exploitable, but it could have been used as a crucial link in an attack chain eventually compromising the uberinternal.com network.

Credits

The vulnerability was found by Jouko Pynnönen of Klikki Oy, Finland.