In April 2024, the semester ticket of the university of Potsdam was replaced by a German National Ticket (“Deutschlandticket”) that is provided digitally to all students. To get the ticket, students have to log into the “Campus Portal” on the RIDE Ticketing platform. This platform is provided by Digital H GmbH.
Since I found the login mechanism suspicious (see below), I took a closer look at the platform and discovered several vulnerabilities, the most critical of which is a full authentication bypass.
Severity: High
The login functionality requires a matriculation number and birthdate. Both the matriculation number and birthdate are public information.
Additionally, matriculation numbers in many German universities, such as the University of Potsdam, are usually assigned sequentially, with a couple thousand matriculation numbers assigned each year. Matriculation numbers are displayed on the student ID; they cannot be considered secret.
Birthdates of students are also quite predictable with most students being in an age range of 18 to 27 years, yielding about 3650 informed guesses for a given matriculation numbers.
Therefore, the login mechanism used by the RIDE ticketing platform is weak. An attacker knowing any matriculation number can brute-force the corresponding birth date in about 4000 tries. Similarly, an attacker can take any birth date and brute-force a matching matriculation number in about 1 Million tries. If they know any matriculation number, the amount of tries needed reduces to the scale of 1000 - 10000 tries.
Instead, a secure login mechanism should be used. This mechanism could be based on university Single Sign-On (SSO) or email and password authentication.
Severity: Medium
When clicking “login”, the following request is sent to an HTTP API (partner: University of Potsdam):
POST /v1/campus/login HTTP/2
Host: abo.ride-ticketing.de
Content-Type: application/json
Content-Length: [...]
{
"birthday":"[...]",
"matriculationNumber":"[...]",
"partnerId":"[partner id of uni potsdam]",
"authRealmName":"ride"
}
When the login is unsuccessful, the API returns a technical error message:
HTTP/2 404 Not Found
[...]
{
"success":false,
"msg":"Could not find CampusAboUser for matriculationNumber: abc, birthday: 2002-03-26, and partnerId: [partner id of uni potsdam]: \"Could not find any entity of type \"CampusAboUser\" matching: {\n \"where\": {\n \"matriculationNumber\": \"[...]\",\n \"birthday\": \"[...]\",\n \"partnerId\": \"[partner id of uni potsdam]\"\n },\n \"relations\": {\n \"user\": true\n }\n}\"",
"noticeCode":"userNotFound"
}
This gives information about how the API handler interacts with the database which could be useful to an attacker (see next section). Instead, only a generic error message should be returned in the production environment.
Severity: Critical
The login handler does not sufficiently validate the input data.
Particularly, it does not validate whether the parameters birthday
, matriculationNumber
and partnerId
are set.
If any of the parameters are not set, the corresponding conditions in the database query are removed, leading to possible authentication bypasses.
For instance, consider the case of an attacker who wants to get the token for any user at University of Potsdam. They can send the following query:
POST /v1/campus/login HTTP/2
Host: abo.ride-ticketing.de
Content-Length: 71
Content-Type: application/json
{
"partnerId":"[partner id of uni potsdam]",
"authRealmName":"ride"
}
The server returns the following information, resulting in obtaining an access token for a user:
HTTP/2 201 Created
[...]
{
"access_token":"[...]",
"expires_in":300,
"refresh_expires_in":432000,
"refresh_token":"[...]",
"token_type":"Bearer",
"not-before-policy":0,
"session_state":"[...]",
"scope":"profile email"
}
Similar to the attack above, if an attacker knows the matriculation number of a specific user, they can request an access token for that specific user.
The attacker can go on and access all user data for the victim, including their semester ticket. This also leaks their first name, last name, email and birthdate.
To remediate the vulnerability, all parameters should be checked for their type before being passed downstream. All other API endpoints should be checked for similar functionality.
The cooperation from Digital H GmbH was exemplary. The vendor responded in the following way to my report (translated into English):
Update 2024-04-30: After receiving an email from a student from HTW Berlin, it does seem like a captcha has been implemented, contrary to my impression before. The captcha seems to be hand-created, relying on SHA-256. I did not perform further analysis on the security against brute-force attacks that the captcha provides. However, the best solution would be to integrate Campus Portal into the Shibboleth instance of University of Potsdam, avoiding any hand-made authentication code.
Update 2024-04-30: Apparently, on 2024-04-29, Digital H GmbH has reported a data protection incident regarding the students at TU Berlin because attackers identified combinations of matriculation number and birth date. It is unclear whether logins were brute-forced using Issue #1 or Issue #3 was exploited.
No access tokens from other users were ever used for authentication. The vulnerabilities were not shared with any third parties. No bounty was paid as part of the disclosure process.