Privilege Escalation via XSS
Since we are now capable of storing JavaScript code
inside the target WordPress application
and having it executed by the admin user when loading the page,
we’re ready to get more creative
and explore different avenues for obtaining administrative privileges.
We could leverage our XSS to steal cookies and session information
if the application uses an insecure session management configuration.
If we can steal an authenticated user’s cookie,
we could masquerade as that user within the target web site.
Websites use cookies to track state and information about users.
Cookies can be set with several optional flags,
including two that are particularly interesting to us as penetration testers:
Secure and HttpOnly.
The Secure flag instructs the browser to only send the cookie
over encrypted connections, such as HTTPS.
This protects the cookie from being sent in clear text
and captured over the network.
The HttpOnly flag instructs the browser to deny JavaScript access to the cookie.
If this flag is not set, we can use an XSS payload to steal the cookie.
Let’s verify the nature of WordPress’ session cookies
by first logging in as the admin user.
Next, we can open the Web Developer Tools,
navigate to the Storage tab,
then click on http://offsecwp under the Cookies menu on the left.
We notice that our browser has stored six different cookies,
but only four are session cookies.
Of these four cookies, if we exclude the negligible wordpress_test_cookie,
all support the HttpOnly feature.
Since all the session cookies can be sent only via HTTP, unfortunately,
they also cannot be retrieved via JavaScript through our attack vector.
We’ll need to find a new angle.
When the admin loads the Visitors plugin dashboard
that contains the injected JavaScript,
it executes whatever we provided as a payload,
be it an alert pop-up banner or a more complex JavaScript function.
For instance, we could craft a JavaScript function
that adds another WordPress administrative account,
so that once the real administrator executes our injected code,
the function will execute behind the scenes.
In order to succeed with our attack angle,
we need to cover another web application attack class.
To develop this attack, we’ll build a similar scenario as depicted by Shift8.
First, we’ll create a JS function that fetches the WordPress admin nonce.
The nonce is a server-generated token that is included in each HTTP request
to add randomness and prevent Cross-Site-Request-Forgery attacks.
A CSRF attack occurs via social engineering
in which the victim clicks on a malicious link
that performs a preconfigured action on behalf of the user.
The malicious link could be disguised by an apparently-harmless description,
often luring the victim to click on it.
In our case, by including and checking the pseudo-random nonce,
WordPress prevents this kind of attack,
since an attacker could not have prior knowledge of the token.
However, as we’ll soon explain, the nonce won’t be an obstacle
for the stored XSS vulnerability we discovered in the plugin.
As mentioned, in order to perform any administrative action,
we need to first gather the nonce.
We can accomplish this using the following JavaScript function:
This function performs a new HTTP request to the new user URL,
and saves the nonce value found in the
HTTP response based on a regular expression.
Now that we’ve dynamically retrieved the nonce,
we can craft the main function responsible for creating the new admin user.
This function includes the new backdoored admin account,
just after the nonce we obtained previously.
If our attack succeeds, we’ll be able to gain administrative access
to the entire WordPress installation.
To ensure that our JavaScript payload
will be handled correctly by Burp and the target application,
we need to first minify it, then encode it.
To minify our attack code into a one-liner, we can navigate to JS Compress.
Once we have clicked on Compress JavaScript,
we’ll copy the output and save it locally.
As a final attack step, we are going to encode the minified JavaScript code,
so any bad characters won’t interfere with sending the payload.
The encode_to_javascript function will parse the minified JS string parameter
and convert each character into the corresponding UTF-16 integer code.
Let’s run the function from the browser’s console.
We are going to decode and execute the encoded string
by first decoding the string with the fromCharCode method,
then running it via the eval method.
Once we have copied the encoded string,
we can insert it with the following curl command and launch the attack:
Before running the curl attack command,
let’s switch to Burp and turn Intercept on.
We instructed curl to send a specially-crafted HTTP request
with a User-Agent header containing our malicious payload,
then forward it to our Burp instance so we can inspect it further.
After running the curl command,
we can inspect the request in Burp.
Everything seems correct, so let’s forward the request by clicking Forward,
then disabling Intercept.
At this point, our XSS exploit should have been stored in the WordPress database.
We only need to simulate execution
by logging in to the OffSec WP instance as admin,
then clicking on the Visitors plugin dashboard on the bottom left.
We notice that only one entry is present,
and apparently no User-Agent has been recorded.
This is because the User-Agent field
contained our attack embedded into “script” tags,
so the browser cannot render any string from it.
By loading the plugin statistics,
we should have executed the malicious script,
so let’s verify if our attack succeeded by clicking on the Users menu.
Excellent! Due to this XSS flaw,
we managed to elevate our application privileges
from a standard user to administrator via a specially-crafted HTTP request.
We could now advance our attack and gain access to the underlying host
by crafting a custom WordPress plugin with an embedded web shell.
We’ll cover web shells more in-depth in an another Topic.