1.Summary

Tables in ILIAS can be sorted by clicking a column header. Which column to sort by is carried as a value in the request URL. The table framework accepted this value and passed it on without checking it against the columns the table actually has.

In the Trash of a repository container – that is, in the Trash of every course, every group, every category and every folder – this unchecked value then ended up in the database query that loads the contents of the Trash. There it was appended to the ORDER BY clause without escaping. Anyone able to modify the request thereby controlled part of the SQL statement.

The prerequisite is a logged-in account with write permission on a single, arbitrary container. That is not a special privilege: anyone who administers a course or a group anywhere has it. Administrative access to the instance is explicitly not required.

2.Affected systems

The faulty code was checked individually in every release line maintained at the time of the report – rather than inferring from one line to the others. Between line 9 and the later lines, the paths differ only by the move from Services/… to components/ILIAS/….

Affected status per release line and the fixing version for each
Release 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

The identical code was also present in the then-current development state of the next major version and was verified there as well. Since that state was not available as a release at the time of the report, it is not listed here as a separate row.

When assessing your own installation, it also helps to know that the affected screen is reachable not only via the visible Trash tab. The corresponding command is accepted by the general command dispatch of the container interface and is guarded by the write permission. Whether an installation is affected therefore depends on the version, not on the configuration.

3.Technical analysis

Prerequisites. A logged-in account with write permission on a single, arbitrary repository container – course, group, category or folder. No administrative access, no special role, no user interaction on the other side. The affected screen is the Trash of that container.

The entry point was not the Trash. I had been looking for places where an SQL statement is assembled from strings, and worked backwards from there: where does the value that ends up in the statement come from? For this query the trail led all the way back to the address bar. Accordingly, the cause lies not in the Trash but one level deeper – in the shared table framework. The Trash is merely the place where it becomes visible.

The sort parameter is never checked

The framework reads the table’s navigation parameter, splits it at the colons into sort field, sort direction and offset, and takes over the sort field unchanged:

class.ilTable2GUI.php – taking the sort parameter from the request
$nav = explode(":", $this->nav_value);

// $nav[0] is order by
$req_order_field = $nav[0] ?? "";
$req_order_dir   = $nav[1] ?? "";
$req_offset      = (int) ($nav[2] ?? 0);

$this->setOrderField(($req_order_field != "") ? $req_order_field : $this->getDefaultOrderField());
$this->setOrderDirection(($req_order_dir != "") ? $req_order_dir : $this->getDefaultOrderDirection());

Notably, a suitable allowlist already exists: every table registers its sortable columns when it is built. The check against this list, however, sat exclusively in the branch that restores a previously saved sort order from the session – and even there it only decided the direction. When the value came from the request, the branch was skipped and the list was never consulted.

The sort direction, by contrast, is handled cleanly: it is normalised to asc or desc, and anything else falls back to asc. Nothing could be injected via the direction. Only the field was open.

The input filter does not apply here

On its way to the table, the value passes through the general input handling. That, however, only removes HTML markup. Quotation marks, parentheses, commas, equals signs, comment markers and hexadecimal literals survive it unchanged – for a sort parameter that is further processed as an SQL expression, it is therefore ineffective.

Why doesn’t a placeholder help here?

The usual answer to SQL injection is: do not write values into the statement, pass them as parameters. That does not work for column names. A placeholder cannot stand in for an identifier – anyone who wants to sort by a selectable column has to write its name into the statement. The sort clause is thus one of the few places where the standard solution fundamentally does not apply and every project has to come up with something of its own: as a rule, an allowlist. This is not an ILIAS peculiarity; it applies to every application that offers sortable tables.

The path to the database

  1. Request The navigation parameter of the Trash table carries the value field:direction:offset. It is part of the URL and not bound to any CSRF token.
  2. Table framework The field is taken over without being checked against the registered columns.
  3. Trash table This table explicitly sorts in the database rather than in PHP. It therefore passes the field on to the query layer.
  4. Query layer There the ORDER BY clause is built by string concatenation and the query is executed.
class.ilTreeTrashQueries.php – the vulnerable code
$order = ' ';
if ($order_field) {
    $order = 'ORDER BY ' . $order_field . ' ' . $order_direction;
}

$query = $select . $from . $this->appendTrashNodeForContainerQueryFilter($filter) . $order;
// ...
$res = $this->db->query($query);

The filters of the same query are handled correctly: they go through the escaping functions of the database layer, and the filter for the person who deleted the item is resolved to a user ID beforehand. The sort clause was the only open spot in this query – and exactly one is enough.

The vendor’s fix

The vendor addressed the sink: the sort field is escaped as an identifier, and the direction is additionally normalised.

Fix in class.ilTreeTrashQueries.php
$valid_direction = strtolower($order_direction) === 'desc' ? 'DESC' : 'ASC';
$order = 'ORDER BY ' . $this->db->quoteIdentifier($order_field) . ' ' . $valid_direction;

That reliably closes this spot. In addition, I suggested in the report that the sort parameter also be checked against the registered columns in the table framework itself. The allowlist is already available there; applying it to the path from the request as well would cover all views at once. That several ILIAS components already perform this mapping themselves – with an explicit mapping of column names and a normalised direction – shows that the solution already exists in the project and would merely need to be made available centrally. How such a refactoring should be prioritised against other work, however, the project can judge better than I can.

Why the chain held

None of the following points is a vulnerability on its own. Only their combination produces the finding – and that is why a spot like this one can go undetected for years:

  • The table framework takes over the sort field from the request without checking it against the registered columns.
  • The suitable allowlist exists in the framework, but is tied exclusively to the branch for the saved sort order – and thus not to the path by which the value actually arrives from outside.
  • The general input handling only removes HTML markup and lets through exactly the characters that matter in an SQL expression.
  • The Trash table – unlike many other views – explicitly sorts in the database and therefore passes the field all the way through to the query.
  • There the ORDER BY clause is assembled as a string, while the filters of the same query are properly escaped.
  • Access to this screen depends on a single condition, write permission on a container. That lowers the required privileges from “administration” to “running a course somewhere”.

4.Impact

Under MySQL and MariaDB, full expressions are permitted in the ORDER BY clause, including subqueries. The clause is therefore not a restricted special case; it allows read access to the entire database – password hashes, personal data, session data, stored interface secrets.

A second circumstance adds to the rating, one that has nothing to do with this code and that many PHP applications share: by default, the database driver in use allows multiple statements per call. As a result, in the worst case it does not stop at read access – write database operations then become possible as well, up to and including overwriting credentials.

I verified this second point as part of the report on instances set up specifically for that purpose with explicit authorisation to test – solely to be able to substantiate the rating as a write rather than merely a read vulnerability. This mattered for the report, because it determines how urgently a finding is classified.

Rationale for the key CVSS 4.0 metrics
Metric Value Rationale
Attack vector Network An ordinary authenticated HTTP request is sufficient.
Complexity Low Deterministic, a single parameter, no guessing, no time window.
Privileges required Low Write permission on any container; no administrative access.
User interaction None The attacker acts in their own session.
Confidentiality High Arbitrary read queries across the entire database.
Integrity / Availability High Write operations are possible because multiple statements are permitted.

Without the ability to issue multiple statements, the score would be 6.9 (Medium) with CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N. Both values are given here because otherwise the vector cannot be recalculated – and because the gap between them shows how strongly a single default setting of the database driver shifts the rating.

A side effect prolongs the impact: the table framework remembers the most recently chosen sort order in the table settings of the acting account. An injected sort parameter was therefore executed again on every subsequent visit to this screen, until it was replaced by a valid sort order.

Impact in one sentence: Full read access to the instance’s database for every account that has write permission anywhere – and, with the database driver’s default setting, write access as well, which puts a takeover of the instance within reach.

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.

Why a configuration change is not enough here

Whether an instance is affected depends on the version in use, not on its settings – so there is no way around the update. If you cannot apply it immediately, you can restrict access outside the application in the meantime; the following points are intended as a stopgap, not as a replacement.

Stopgap if you cannot update immediately

  • Filtering at the upstream web server. In normal operation, the sort parameter consists solely of a column name. Requests whose table navigation parameter contains characters other than letters, digits, full stop and underscore can be rejected. This is an emergency measure, not a replacement for the patch.
  • Review write permissions. This is sensible anyway, but here it directly reduces the number of accounts that could reach the vulnerable code.
  • Prevent multiple statements. Where the operating environment allows it, this generally limits the impact of an SQL injection to the read case – a general hardening measure, independent of this finding.

Checking for past exploitation

The vulnerability is triggered via the URL. You can therefore search the web server’s access logs specifically for:

  • Requests with a table navigation parameter whose value does not look like a column name – in particular with parentheses, quotation marks, semicolons, comment characters or conspicuous length.
  • Calls of the Trash command on containers by accounts that otherwise never use this screen.
  • Database errors in the application log that coincide in time with such requests.

If there is reasonable suspicion, the credentials of accounts with administrative rights should be renewed and sessions invalidated. Because of the side effect described above, it is also worth checking stored table settings: an injected sort parameter could remain there persistently.

Note for operators

If you have questions about whether your own installation is affected or about evaluating log data, you can contact security@schweigertit.de.

6.Disclosure timeline

  1. Identification and verification of the finding
  2. Report to the vendor Mantis 0048128
  3. Patch released by the vendor ILIAS 9.22, 10.10 and 11.3
  4. Publication of this advisory
  5. Publication of the CVE record CVE-2026-82538

7.References

  • CVE CVE-2026-82538 – published 2026-09-04, CWE-89.
  • Vendor ILIAS open source e-Learning e.V. – report recorded as Mantis 0048128; fix included in releases 9.22, 10.10 and 11.3 of 2026-08-12.
  • CWE CWE-89 (SQL injection), contributing CWE-1236 – unvalidated use of a sort parameter from the request.
  • 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.