1.Summary
ILIAS can integrate Shibboleth as a login method. So that the identity provider can tell the application that a session has ended, there is a back channel: an endpoint that is called not by the browser of a logged-in person but by a third-party server. This endpoint is therefore deliberately exempt from the login requirement – which in itself is correct, because the caller is not someone who could be logged in.
To work out which session to end, the logout handling went through every
active session of the instance and passed the stored session data of each
to a custom parsing function. That function called
unserialize() without using the allowed-classes parameter to
restrict what may be created. Stored strings thus became
real objects – created on behalf of a caller who had never logged in.
The second part of the finding is the question of how an attacker gets anything into a session in the first place. That, too, worked without an account: another entry point of the application, the LTI integration, stores submitted request parameters in the session and is likewise exempted from the login requirement by the same initialisation. This makes it possible to create an anonymous session with arbitrary content, which then sits in the database.
Objects created during deserialisation are discarded again at the end of the request, and their clean-up methods run as they are. A library class shipped with ILIAS writes a file in its clean-up method, with a path taken from one of its own properties. Via this route, an unauthenticated caller could place a file with content of their choosing below the served directory and then have it executed – code execution as the web server user, without an account, without a token, without any action by a human on the other side.
2.Affected systems
The vulnerable code was checked individually in every version line maintained at the time of the report – not inferred from one line to the others. It is present in all three lines and identical in substance.
| Version line | Affected | Fixed in |
|---|---|---|
| ILIAS 9 | all versions before 9.22 | 9.22 |
| ILIAS 10 | all versions before 10.10 | 10.10 |
| ILIAS 11 | all versions before 11.3 | 11.3 |
Present code and actual triggerability are two different things
The version information above follows the vulnerable code, and for the question “do I need to update?” that is the right basis. When checking the individual lines, however, it turned out that the endpoint does not behave the same everywhere. I record this here because it helps operators with prioritisation – not because it reduces the urgency of the update:
| Line | Vulnerable code | Triggerable via the endpoint |
|---|---|---|
| ILIAS 9 | present | No. The relevant branch depends on a superglobal that no longer exists since PHP 7; it is therefore never entered. |
| ILIAS 10 | present | Yes – demonstrated as part of the report. |
| ILIAS 11 | present | No. The endpoint accesses the service container before it has been built and therefore aborts early on every request. |
No reason to postpone the update
What blocks the endpoint in lines 9 and 11 is incidental: a language change in PHP and a programming error elsewhere. Both are properties of the shipped state, not protective measures. A patch, a plugin or a customised installation can undo them without anyone noticing. The vulnerable code itself is present in all three lines – and that is why the vendor removed it from all three.
When an instance was reachable in practice
Beyond the version, actual exploitability depends on properties of the operating environment. The main factors for assessing your own installation are: whether PHP's SOAP extension is loaded, whether the endpoint is reachable for the server itself over HTTPS with a certificate it trusts, and whether there is a writable area below the served directory in which stored files are also executed. In a standard installation these conditions can be met; anyone running their instance differently may not have been reachable. This, too, is a snapshot of the configuration and no substitute for the update.
3.Technical analysis
Prerequisites. None. No account, no role, no CSRF token, no interaction on the other side. Two ordinary HTTP requests were enough, to endpoints that must both be publicly reachable for the associated procedures to work.
The finding does not arise at a single point. It arises where two decisions meet, each of which is understandable on its own: an endpoint that must be reachable without login, and a store that an unauthenticated user can fill. Between them sits a function that treats this store as trustworthy.
The four links
- The endpoint is exempt from the login requirement The central initialisation of ILIAS knows contexts in which no authentication is required. The Shibboleth back channel is one of them, and rightly so: the calling identity provider is not a logged-in user.
- The logout handling reads all sessions To determine the session to end, all still valid session records were loaded and their stored contents sent one by one through a hand-written parsing function.
-
Deserialisation without class restriction
This function called
unserialize()without specifying allowed classes. As a result, the stored text can become any object whose class the application knows – including classes from bundled third-party libraries. - The content got into the session without login Another entry point, likewise exempt from login, stores submitted request parameters in the session. The anonymous session created this way is saved – and thus read along by the previous step.
Why is this dangerous if it only reads?
unserialize() does not read data, it builds objects. These objects
belong to real classes of the application, and their lifecycle methods run – even
if the program never uses them, because the clean-up method is called at the latest
when they are discarded. A grown project almost always brings along, through its
dependencies, a class that does something consequential in the process: writes a file,
issues a command, opens a connection. The attacker therefore writes no
code, but assembles existing building blocks so that their normal behaviour achieves
the desired result. That is why the rule applies without exception:
never apply unserialize() to data that can be influenced
from outside – and where it is unavoidable, explicitly restrict the allowed
classes.
Withheld
Unlike in SIT-2026-001, I show no source code here, do not name the library class suitable as a tool, and do not present the flow as a sequence of concrete requests. For code execution without login whose patch is a few weeks old, that would be a blueprint against every instance not yet updated. Anyone who wants to read the code will find it via the vendor's commit linked in section 7.
The vendor's fix
The vendor removed the endpoint's logout handling in all three lines instead of hardening it. I consider that the right decision: the function was tied to an operating mode that only some instances use at all, and its task – picking the matching local session out of an external notification – can hardly be solved sensibly without looking through all session data. Removed code is the only code guaranteed to contain no more vulnerabilities.
Why the chain held
Here, too, none of the points is a flaw on its own. Only their combination produces the finding:
- An exemption from the login requirement that is correct for its original purpose applies to everything that runs in that context – including processing added later.
- The same initialisation exempts a second entry point from the login requirement, one that writes data. Harmless on its own, in combination half the battle.
- In many applications session data is implicitly considered trustworthy because it comes “from ourselves”. As soon as a login-free path can write into it, that assumption no longer holds.
- The parsing function was hand-written and deserialised without class restriction – the parameter for this has existed since PHP 7.0.
- The processing covered all sessions of the instance, not just the caller's. So it was enough to create a session anywhere at all.
- The bundled third-party libraries provided a class whose normal behaviour on clean-up is to write a file.
4.Impact
At the end of the chain is code execution with the privileges of the web server user. This affects not a single function but the entire instance: all content and courses, all user accounts including password hashes, the complete data set, stored credentials for connected systems and all active sessions. From this point on the installation is no longer trustworthy, and applying the patch alone does not make it so again.
Making matters worse, there is no hurdle to stop an attacker: without an account and without user interaction, the flow can be automated and run broadly against reachable instances once the location is known. That is exactly the difference from SIT-2026-001, where at least a logged-in account with write permission was required.
| Metric | Value | Rationale |
|---|---|---|
| Attack vector | Network | Ordinary HTTP requests to publicly reachable endpoints. |
| Complexity | low | No guessing, no time window, no bypassing of a protective measure. |
| Privileges required | none | Both endpoints involved are exempt from the login requirement. |
| User interaction | none | Nobody has to click on anything or be logged in. |
| Confidentiality / integrity / availability | high in each case | Code execution as the web server user covers reading, modifying and destroying. |
For context, the counter-check belongs here too: the rating assumes that the environmental conditions named in section 2 are met. They describe an ordinary installation, but not every one. I list them explicitly so that operators can assess their own situation instead of having to trust a number – and because a condition that is not met today may be met tomorrow.
Impact in one sentence: complete takeover of the instance by any caller from the network, without an account and without any action by a human on the other side.
5.Mitigations / workarounds
Update
The decisive and only complete measure is updating to ILIAS 9.22, 10.10 or 11.3. These versions were released on 12 August 2026 and contain further security fixes in addition to this correction.
Interim measures if you cannot update immediately
-
Block the endpoint at the upstream web server. The affected file is
shib_logout.phpfrom the Shibboleth component (the path differs between version lines; the public CVE entry gives it in full). If you do not use Shibboleth as a login method, you never need this back channel – requests to it can be rejected without side effects, and that is the most effective immediate measure. If you use Shibboleth, you can restrict access to the addresses of your own identity provider. - Block unused entry points as well. This applies in particular to the entry point of the LTI integration if no external tools are connected. It is the route by which content gets into a session without login, and in a typical installation it is as unused as it is reachable.
- Prevent execution in the data directory. Preventing the web server from executing files stored below the served directory is worthwhile hardening regardless of this finding – and here it is the link that turns writing a file into code execution.
For this vulnerability, patching is not enough
If the flaw was exploited before the update was applied, the attacker's access remains – stored files, modified accounts or planted keys do not disappear with an update. Before giving the all-clear, therefore, carry out the check in the next section.
Checking for past exploitation
Both endpoints involved are addressed via ordinary HTTP requests and leave corresponding traces. Useful starting points:
-
Requests to
shib_logout.php. In instances without Shibboleth there should be none at all; every single hit needs explaining. In instances with Shibboleth, calls from addresses other than those of your own identity provider are suspicious. - New or modified executable files below the served data directory – compared against a known clean backup, not by gut feeling.
- Unusually large session records in the session table, especially those without an associated user account.
- PHP errors related to SOAP processing or to deserialisation that coincide in time with such calls.
If the suspicion hardens, treat the instance as compromised: take it off the network, secure evidence, rebuild from a demonstrably clean backup, renew all credentials and keys – including those for connected systems – and invalidate all sessions. Resetting passwords alone is not enough if the attacker was able to execute code on the server.
Note for operators
If you have questions about whether your own installation is affected or about analysing log data, you can contact security@schweigertit.de.
6.Disclosure timeline
- Identification and report to the vendor Mantis 0048152
- Patch by the vendor ILIAS 9.22, 10.10 and 11.3
- Publication of the CVE entry CVE-2026-80428
- Publication of this Advisory
7.References
- CVE CVE-2026-80428 – published 2026-08-26, CWE-502, CVSS 4.0 9.3 (Critical).
- Vendor ILIAS open source e-Learning e.V. – report of 2026-08-03, tracked as Mantis 0048152; fix included in releases 9.22, 10.10 and 11.3 of 2026-08-12.
-
Fix
Commit
f36934a6f937d0fe837ca6e642986458b4069a95in the vendor's repository. - CWE CWE-502 – Deserialization of Untrusted Data.
- Related SIT-2026-001 – second finding from the same investigation, fixed in the same releases.
- Policy schweigertIT's responsible disclosure policy.
- Research Research overview with the current CVE list.
Questions about this Advisory
Please send questions to security@schweigertit.de, encrypted if you like. I do not release proof-of-concept code, even on request.
Writeup on the PHP object injection in the “Poking on ILIAS” series
Responsible disclosure
Why this says less than usual
For SIT-2026-001 I was able to show the source code and trace the path of the value link by link, because a logged-in account was required there and the fix is immediately obvious. This case is different: code execution without login can be used in automated fashion against every reachable instance, and after a patch it takes weeks to months until the majority of installations are updated.
That is why this page contains exactly as much as operators need to assess whether they are affected, prioritise correctly and analyse log data – and nothing that would do the work for someone looking for an unpatched instance. I make this trade-off afresh with every publication; it does not always come out the same way.