In our endeavor to enhance the security of the open-source realm and gain a deeper understanding of real-world vulnerabilities, we are constantly conducting audits of open-source projects, and the outcomes of this are presented in our two articles on Moodle security. This is the second blog covering another critical finding we discovered when auditing Moodle for security vulnerabilities.
In the first blog, we demonstrated how an unauthorized attacker could turn an arbitrary folder creation into a Cross-Site Scripting (XSS) vulnerability, ultimately resulting in Remote Code Execution (RCE). The second part of the series follows the same line of starting with a considerably low-impact bug at first glance, but with some steps, attackers can leverage it to a full account takeover.
Moodle versions before 4.2.2, 4.1.5, 4.0.10, 3.11.16, and 3.9.23 are susceptible to Account Takeover (ATO) via self-XSS in the WYSIWYG editor – this is tracked as CVE-2023-40320. On Moodle instances where OAuth authentication is enabled, victims' accounts can be compromised with a simple click on a link.
In this section, we will discuss the technical details of the vulnerability and explain how attackers might exploit this kind of vulnerability.
One of the initial steps we do when auditing an application is to use it as intended. Doing so helps us understand how it is supposed to behave and also brings many ideas to mind on how to manipulate the intended behavior the same way an attacker would. Pretty quickly we ran into the WYSIWYG editor in Moodle.
Being one of the core features of Moodle, it appears when editing a description of a user, writing an answer to a forum, submitting assignments, and many more.
We noticed that there is the possibility to input arbitrary HTML which will be rendered and executed in the editor (making this a self-XSS). But when submitting the payload to a public page (such as a forum, assignment, etc.), it gets sanitized on the server side and dangerous elements are removed – other users will never be affected by the payload.
In addition, the editor has a feature that automatically saves a user's WYSIWYG content by sending the unsanitized data periodically after a couple of seconds to the
When loading the page again, the autosaved data is fetched from the same endpoint using the
actions[action] parameter set to
resume. In case a malicious payload was stored before, it will execute again by visiting the WYSIWYG page – this just became a Stored Self-XSS!
One of the ways an attacker could leverage this type of bug to an impactful one is by manipulating a victim into logging in to a malicious account -> triggering the self-XSS -> raising the impact depending on the application. With it, this was the first exploitation idea we tested. After a small check, we saw that the login and logout features are CSRF-protected, meaning an attacker can’t log in or out on the victim’s behalf by manipulating them to visit a malicious website.
In this case, an attacker needs to find some kind of “magic link” (a single link that logs in a user without a password, usually using a one-time token). The first idea we wanted to test is via an OAuth login. Yet again this endpoint was protected by a GET parameter
sesskey which acts as a CSRF token. At this point, we decided that code auditing would yield better results than quick tests.
Following the normal login procedure, the function that logs in a user is called
complete_user_login. This function is called after the authentication is verified and would also log out the current user if there is one. Upon examining all the calls made to this function, we discovered several endpoints. However, we observed that they either verifying new accounts (Moodle accounts must be verified before users can access them, meaning an attacker can’t pre-deploy the self-XSS) or prohibited logging in if a session already existed. Changing the email of an existing account would send a confirmation message but the link provided only confirms and does not login, unlike the confirmation link when registering a new account.
But then we came across
Here, if the link is valid, a login will happen. Without any verification that another user is already logged in, this is the only endpoint that does that. In addition to that, there is the possibility to pass a local
$redirect URL that will redirect the user after the login!
But what is
oauth2/confirm-linkedlogin.php and how an attacker would get here?
First, we need to understand that this is possible only in a Moodle instance with some kind of OAuth enabled. In it, a user can log in via their OAuth account or link/unlink OAuth to an existing account. In case it's the first OAuth login a new account will be created with linked OAuth. But in case there is already an account with the same email address as the OAuth account, Moodle will link those accounts and send this
confirm-linkedlogin confirmation link by email.
Here are the specific number of steps an attacker would need to do to craft an account takeover attack:
1. The attacker has an account with a controlled email same as the OAuth provider (for example, if Moodle has Google’s OAuth then the email should be a Gmail address). In this demonstration, let's say an attacker is logged in with firstname.lastname@example.org.
2. The attacker’s account shouldn’t be linked to OAuth (can be unlinked in the user options in case it's already linked).
3. Attacker creates a self-XSS payload that logs in using the current browser’s OAuth (done automatically without requiring credentials) using an iframe pointing to:
M.cfg.sesskey is the current session’s CSRF protection). Since the Iframe has the same origin as the main page, the XSS code can freely access the newly created session in the Iframe.
4. An attacker account adds the self-XSS payload to a WYSIWYG input and waits for the autosave.
5. Attacker logs out.
6. The attacker logs in with OAuth (using email@example.com). Moodle will see that there is already an account with the same email address and will generate a confirmation URL that links the Moodle account to the OAuth. That URL will be sent by email.
7. Attacker adds the
redirect parameter to the URL that will point to the self-XSS containing page:
8. Any user who clicks on the newly crafted link will be logged in to the attacker’s account and redirected to the self-XSS page.
9. The victim triggers the self-XSS payload in the context of the attacker's account. It creates a new frame in which the victim is authenticated back in their own account via OAuth. Both the parent document (attacker's session) and the frame (victim's session) share the same origin, so the payload has full access to everything inside the frame.
10. From here, the attacker has full control over the victim's account. For example, using the following iframe’s onload event code will show an alert with the victim’s cookie:
alert('hijacked cookie:' + document.cookie);. Any other action can be done directly in the frame on the victim's behalf. In case the victim account has admin privileges, code execution on the server can be achieved (as demonstrated in our previous blog).
The vulnerability was fixed in versions 4.2.2, 4.1.5, 4.0.10, 3.11.16, and 3.9.23 by removing the call to the
complete_user_login function, causing the
confirm-linkedlogin.php endpoint to not automatically login the user by clicking the link.
Clicking a malicious link now will not log in to the attacker’s account and thus no self-XSS is executed on the victim (though stored self-XSS is still possible in the WYSIWYG editor).
|We report all issues to the vendor
|Vendor patched the vulnerability
|Vendor released security advisory and CVE-2023-40320 was assigned
In this article, covering our second critical vulnerability found in Moodle, we demonstrated how attackers can leverage the self-XSS vulnerability to an impactful Account Takeover. Considering that, in addition to our first blog in the series covering another innocent initial bug to RCE, it is important to not overlook those innocuous issues.
By focusing on clean code practices, developers write software that is clear, maintainable, and understandable. These qualities make it easier to spot and address vulnerabilities during development, reducing the risk of introducing security flaws that could be exploited by attackers. It is important to address all security issues in order to reduce the chance of bug chains.
We would also like to thank Moodle again for their responsiveness and great communication.