Lichess · Report #3175928
ImageId Format Injection in Image Upload Endpoint
How it was found
Summary
Hello Again Lichess Team,
I found an input validation issue in the image upload endpoint that lets me inject special characters into the rel parameter. This breaks the expected ImageId format that the application uses to identify uploaded images. While testing upload functionality, I noticed that colon characters aren't properly sanitized, which could cause parsing issues in other parts of the application
Description
The /upload/image/user/{rel} endpoint doesn't validate the rel parameter properly. This parameter gets combined with random strings to create an ImageId that should follow a specific format: {rel}:{random12}:{random8}.{extension}
The issue is that the application accepts colon characters in the rel parameter without sanitization. This happens because the validation only checks for MongoDB injection patterns (like $ne) but misses format-breaking characters
Looking at the code in modules/memo/src/main/Picfit.scala:
val image = PicfitImage(
id = ImageId(s"$rel:${ThreadLocalRandom.nextString(8)}.$extension"),
// rel parameter used directly without validation
rel = rel,
// ...
)
So when I inject colons, instead of getting the expected 3-part format, I get multiple parts that could break parsing logic elsewhere
Steps to Reproduce
- Login to Lichess and grab your session cookie
- Create a small test image file (test.png)
# Create a small test image (1x1 pixel PNG)
echo -e '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x16\x1d\xb3\x00\x00\x00\x00IEND\xaeB`\x82' > test.png
- Send this request:
curl -X POST "https://lichess.org/upload/image/user/test:evil:format:break" \
-b "lila2=YOUR_SESSION_COOKIE" \
-H "Origin: https://lichess.org" \
-H "Referer: https://lichess.org/" \
-F "[email protected]"
Expected Result: The application should reject the request or sanitize the colons Actual Result: The request succeeds and returns:
Normal Request :
{F4409970}
{F4409974}
Actual Request with inject :
{F4409975}
{F4409976}
{"imageUrl":"https://image.lichess1.org/display?...&path=test:evil:format:break:ePU9oRLnNvCz:iFZRITKQ.png&..."}
Notice how the ImageId now has 6 parts instead of the expected 3: test:evil:format:break:ePU9oRLnNvCz:iFZRITKQ.png
https://cwe.mitre.org/data/definitions/20.html
Mitigation
To fix this, you should sanitize the rel parameter in the image upload handler. Something like:
// Validate rel parameter format
val sanitizedRel = rel.replaceAll("[^a-zA-Z0-9_-]", "_")
Or add validation in the uploadSource method in PicfitApi.scala to reject rel values containing colons or other format-breaking characters.Also consider documenting the expected ImageId format since the current code assumes a specific structure that could break if the format changes.
Impact
An attacker could potentially cause issues in other parts of the application that expect ImageIds to follow the standard 3-part format. I noticed there's regex parsing logic in findInMarkdown that might not handle malformed ImageIds correctly .This could cause issues in logging, image lookup, or markdown parsing where the format of imageId is implicitly assumed. In production environments with chained handlers or analytics relying on this structure, this might lead to edge-case bugs or silent failures
However, I couldn't demonstrate concrete security impact beyond breaking the data format. The images still upload successfully and get stored properly
Thank you for your time and reviewing my report