Skip to Main Content
November 23, 2021

Persistence Through Service Workers-Part 3: Easy JavaScript Payload Deployment

Written by Drew Kirkpatrick
Application Security Assessment Penetration Testing Red Team Adversarial Attack Simulation Security Testing & Analysis

In "Persistence Through Service Workers—PART 2: C2 Setup and Use," we demonstrated setting up the Shadow Workers C2 server and how to add both the service worker JavaScript and what Shadow Workers calls the “XSS Payload” JavaScript to the target application. In the example, we didn’t load the “XSS Payload” through a cross-site scripting vulnerability. Instead, we copied that JavaScript directly into a JavaScript file in the application itself, as we already had write access to the target application.

We carefully selected which JavaScript file to copy our code into so that it would execute when an Administrator was using administrative features of the site, which would provide us access to those administrative features through Shadow Workers C2 capabilities. Selecting the appropriate JavaScript file to copy the “XSS Payload” into can be a difficult task without insight into the application’s general usage. Ideally, you want the payload to be running as often as possible. This payload is critical because it not only registers our malicious service worker, it's also required to be running for our victim’s browser to get hooked into our Shadow Workers C2 dashboard. It is essential for this code to run in our victim’s browser.

So how do we go about picking which JavaScript file to add our payload into if we’re in a hurry? Something elegant and incredibly creative? I think not.

Figure 1 - Subtle Clue on Upcoming Technique

Instead of carefully selecting which JavaScript file will run most of the time, we’ll simply add our payload to all JavaScript files. We'll also include a little bit of extra code to ensure that the payload is only run once. Here’s an example of JavaScript that would accomplish running a payload of, "alert(1)," only once:

if (typeof javaScriptPayload === 'undefined')
    {
        var javaScriptPayload = function()
        {
            alert(1);
            console.log('++Running this code!');
        } 
        javaScriptPayload();
    } 
else
    {
        console.log('--Not running this code'); 
    }

No matter how many JavaScript files you copy this code into in a web application, only the first JavaScript file included with this code will execute. The rest will harmlessly print, "—Not running this code," in the console.

This works because the first line checks to see whether "javaScriptPayload" is defined. If it is not defined, then it defines a function with that name. In that function, we put in our malicious code, such as an alert box or Shadow Workers payload. Once that function is defined, we call it with "javaScriptPayload();" The else statement is unnecessary in practice, but for development, we simply put a print statement saying that the code isn’t running. It’s rather interesting to see how many extra copies of the payload end up in the rendered webpage when you have the console open.

Figure 2 - Example Console Showing Multiple Copies Not Running

Now that we have a technique for only running one copy of our malicious JavaScript, no matter how many JavaScript files receive that code, how do we actually copy our malicious JavaScript into those files?

Here is a simple bash script that will find all .js files in directories beneath it and append our desired JavaScript into each one.

#!/bin/bash

declare -a fileArray
declare -a directoryArray

while read line
do
    fileArray+=("$line")

done < <(find . -type f -name '*.js' 2>/dev/null |  awk -F: '{print $1}')

# Make sure we're looking at unique files
while read uniqFile
do
    echo "Unique file: $uniqFile"
    echo 
    echo >> $uniqFile
    echo "if (typeof javaScriptPayload === 'undefined')
    {
        var javaScriptPayload = function()
        {
            console.log('++Running this code!');
            alert(1);
        } 
        javaScriptPayload();
    } 
else
    {
        console.log('--Not running this code'); 
    }" >> $uniqFile

done < <(printf "%s\n" "${fileArray[@]}" | sort -u)

This script will create a list of all .js files below the script on the filesystem. Each unique .js file in that list will append our JavaScript onto the bottom of that file.

Let’s run our example on a WordPress server. Here I’ll use my favorite InfoSec Fashionistas demo site.

Figure 3 - Payloads Not Yet Added

We’ve copied our installer bash script onto the application server and will run it in the WordPress directories.

Figure 4 - Copying Installer Bash Script

After running the bash script, we'll see that our payload code has been copied into many JavaScript files identified by our installer.

Figure 5 - Copying Payload Into JavaScript Files

WordPress deployments can have multiple file locations, and in our example site, the core files are located in /usr/share/wordpress. We will run our script there as well to ensure we also get our payload added to JavaScript files used by admins.

Figure 6 - WordPress Deployed in Multiple Directories

Running our installer script in that directory completes our modifications to the application’s JavaScript files.

Figure 7 - Copying Payload Into Remaining JavaScript Files

If we open our web console and browse the application, we’ll see that we get one alert pop up, but we’ll often notice multiple console print statements indicating that additional files executed our payload code.

Figure 8 - Single Payload Executing
Figure 9 - Successfully Running Single Copy of Payload

While not an elegant solution, this approach would maximize the amount of time your Shadow Workers “XSS Payload” was running, while also minimizing the effort for deployment. Before using this technique, ensure you have a plan for cleaning up your artifacts upon completion of your engagement.

References:

https://shadow-workers.github.io/

https://github.com/shadow-workers/shadow-workers

https://github.com/hoodoer/javaScriptDeployer