Learn how 3DS OUTSCALE runs its Bug Bounty program and go behind the scenes of a recent CVE (Common Vulnerabilities and Exposures) publication.
Bug Bounty at 3DS OUTSCALE
3DS OUTSCALE, as an IaaS Cloud provider, is looking for any security-oriented feedback. For this purpose, we opened our Bug Bounty program on the YesWeHack platform in 2016. Security researchers are encouraged to hunt for bugs on our public IaaS platform, especially on the API and the Cockpit web interface.
We want them to find any ways of bypassing segregation measures, or anything impacting their customer experience or the infrastructure itself! To make it possible, we provide security researchers access to a Cloud account that will allow them to test our infrastructure as if they were regular customers. With limited but sufficient quotas, they may spawn private LANs, virtual machines and manage CPUs, RAM, security groups, etc.
Practical Case: Vulnerability in a WordPress Plugin
In 2021, a security researcher (0xspade) submitted a report on our YesWeHack program. This report pointed out an endpoint (a web address at which users can send requests to perform specific actions) on our official website that was not properly escaping input data. The researcher proved that sending some altered data to this endpoint could lead to local code execution. In short, we were facing an XSS vulnerability.
At 3DS OUTSCALE, we use WordPress to power some of our official websites. We had to conduct an investigation to find out which part of the source code was vulnerable, because WordPress works with plugins, which all have their own logic. This investigation led us to apply a hotfix on our servers while waiting for an official patch from the editor. The technical details can be found in the last part of this article.
After a first unsuccessful contact with the plugin editor, we decided to contact ANSSI (the National Cybersecurity Agency of France) as well as WPScan, which is a French CNA (CVE Numbering Authority) specialized in vulnerabilities that can be found within the WordPress ecosystem.
WPScan notably offers a vulnerability scanner as a plugin/CLI, and maintains a database of all vulnerabilities affecting WordPress and its plugins. They were therefore the right contact to help us move forward, acting as our intermediary between the editor and the marketplace during the whole process.
They helped us make the editor realize the severity of the vulnerability and gave them some time to release patches that fixed the problem. We methodically followed the steps and carried out the process successfully.
Official advisories are available here:
In order for these calls to be made by every visitor, logged in or not, the developers have been using a non-authenticated AJAX call.
The problem was that the function associated with this AJAX call (check_privacy_settings) returned JSON data, but with a wrong Content-Type (text/html) and including non-sanitized input passed during the request.
First, how does this plugin work?
When a visitor arrives on a website using this plugin, an AJAX call is made to retrieve some information:
Figure 1 – AJAX call by the vulnerable plugin
Two parameters are sent along with the request, which is supposed to trigger a check_privacy_settings function. This function is actually fired up by a wp_ajax_nopriv_ hook. For those who are not familiar with the WordPress universe, it is a custom AJAX endpoint created by the plugin’s developers and reachable without authentication.
If we look at the function’s code, without going into details, we basically have something like this:
Figure 2 – simplified check_privacy_settings function’s code
We can see that a part of the input (the
$setting variable corresponding to the values of the
$settings associative array) is reinjected into the function’s output, without sanitization.
Let’s play with it.
The scenario on which we will base our thinking is the following:
- An attacker creates a malicious webpage (which has a safe-looking URL, or at least not suspicious) and sends the link to the victim;
- The malicious page redirects the victim’s browser to the vulnerable endpoint by passing HTML input as POST parameters;
- Once redirected to the vulnerable endpoint, the victim’s browser executes the payload.
Figure 3 – Try #1: The malicious page
Figure 4 – Try #1: The vulnerable endpoint
As you can see, the
<h1> tag is interpreted by the browser because the Content-Type of the response is set to text/html (this is the default Content-Type returned by admin-ajax.php).
But we also have some escaped characters. That is because of the
json_encode function which escapes the data to make it JSON valid. This is why
\ are being escaped.
What we have to understand is that it won’t be possible to execute code with
We know that the use of
The onload event works well with
<body> tags. Moreover,
<img> is a void element and HTML does not require to close it, and the
<body>‘s closing tag is also not mandatory for the browser to interpret it.
Figure 5 – Try #2: The malicious page
And the resulted behavior:
Figure 6 – Try #2: The vulnerable endpoint
However, we are somewhat limited in the code we can run due to escaped characters, as seen earlier. Thus we need to find workarounds.
Furthermore, if we want to use variables inside the payload, we have to declare them without the
var keyword. This technique is valid and won’t trigger a ReferenceError as long as we don’t use strict mode (and we won’t).
For return statements, we may use
return(value) instead of
And remember, we can’t use single and double quotes! Meaning we won’t be able to use standard strings in the script we write for the onload event. We have two options to work around this problem:
- Using backticks (
`) instead of quotes given that template literals can be used in the same way as strings;
- Using an array of charcodes without spaces (
[104,101,108,108,111]) and converting it into a string with a function such as the following:
// Function.prototype.apply() : String.fromCharCode.apply(null, charcodes) // OR Spread syntax : String.fromCharCode(...charcodes)
Thanks to these little tweaks, we are able to make the victim’s browser execute whatever we want with, for instance, something like
Now, imagine the victim is an administrator of the targeted WordPress, with a valid session cookie (i.e., they are already logged in as admin on the site). Because the admin panel is on the same domain as this vulnerable endpoint, AJAX calls are possible and not restricted (i.e., we don’t need explicit CORS permissions), and iframe manipulation is not restricted by the same-origin policy. We may then perform any action we want on the admin panel on behalf of the administrator (user creation, article edition, plugin installation, etc.), and even upload a webshell to keep full access to the WordPress in the future and try lateral movements or privilege escalation.
The easiest, and fastest, way to protect against this vulnerability is to adapt the Content-Type each time a function returns JSON data.
The plugin’s editor first decided to add a nonce check for this request in their 1.9.26 patch. Unfortunately, this wasn’t enough since it didn’t fix the XSS vulnerability but only slightly reduced the severity. In fact, a nonce has a default validity of 24 hours (twice its default 12 hours lifetime). However, if it’s compromised, the admin may still be exposed to an attack during this time. But above all, the problem is that the nonce is the same for all unauthenticated visitors. All four variables used to create the nonce (
Eventually the editor added a casting to integer on to the input data (the famous
$setting variable, since the client is supposed to send a number) in the version 1.9.27. Now a string payload is converted to int(0) and there is no more XSS. As there are one partial patch and one complete patch, two distinct CVEs have been opened.
If you are a cybersecurity researcher yourself and would like to discover the Cloud, you are welcome to join our bug bounty program! You just have to create a YesWeHack account and contact us so that we can provide you with free access to the platform. Hope to see you soon 😉