Direct links to image remain accessible after share is revoked #657

Closed
opened 2026-02-19 23:13:23 -05:00 by deekerman · 7 comments
Owner

Originally created by @squirrelchew on GitHub (Jan 4, 2021).

Originally assigned to: @lastzero on GitHub.

Album share links should be tied to the direct links to a given image, being created and destroyed alongside the album share link.

Scenario

  1. Share an album, with or without custom slug, with or without expiration (/s/slug/albumname)
  2. Navigate to album, fetch direct link to image (/api/v1/t/hash/hash/fit_res)
  3. Allow album share to expire or manually revoke it

Expected result

  • Album is no longer accessible
  • Direct link no longer returns an image

Actual result

  • Album is no longer accessible
  • Direct link still returns the original image
Originally created by @squirrelchew on GitHub (Jan 4, 2021). Originally assigned to: @lastzero on GitHub. Album share links should be tied to the direct links to a given image, being created and destroyed alongside the album share link. **Scenario** 1) Share an album, with or without custom slug, with or without expiration (`/s/slug/albumname`) 2) Navigate to album, fetch direct link to image (`/api/v1/t/hash/hash/fit_res`) 3) Allow album share to expire or manually revoke it **Expected result** - Album is no longer accessible - Direct link no longer returns an image **Actual result** - Album is no longer accessible - Direct link still returns the original image
deekerman 2026-02-19 23:13:23 -05:00
  • closed this issue
  • added the
    question
    label
Author
Owner

@squirrelchew commented on GitHub (Jan 4, 2021):

Further testing reveals that right clicking an image that already fits in the window will dismiss it, making it slightly more challenging to retrieve the image URL. This can be bypassed by looking at the source, or simply making your viewport smaller, left clicking the image to zoom it, then right clicking the image.

Also, turns out the URL for a given image is the same for an image regardless of whether or not it's shared or even in an album. The URL fetched using this method from the admin login on album-less images is publicly accessible.

It would make sense to me to dynamically create/destroy the API endpoints for a given image based on the slug of the album share, keep that stored in the database or however the endpoints are currently mapped for images, no?

@squirrelchew commented on GitHub (Jan 4, 2021): Further testing reveals that right clicking an image that already fits in the window will dismiss it, making it slightly more challenging to retrieve the image URL. This can be bypassed by looking at the source, or simply making your viewport smaller, left clicking the image to zoom it, then right clicking the image. Also, turns out the URL for a given image is the same for an image regardless of whether or not it's shared or even in an album. The URL fetched using this method from the admin login on album-less images is publicly accessible. It would make sense to me to dynamically create/destroy the API endpoints for a given image based on the slug of the album share, keep that stored in the database or however the endpoints are currently mapped for images, no?
Author
Owner

@lastzero commented on GitHub (Jan 5, 2021):

This isn't Snapchat. When you share an image with Friends & Family, expect it a) being cached by their browser and b) being downloaded.

There even is a download button. You don't need to hack it.

We could continuously change all URLs, but that would give a false sense of security and cause a huge performance impact as pictures can't be properly cached by browsers anymore. Note that album share links are much shorter, and may be much easier to guess. You might also post them somewhere external and then forget.

Let us know when you find a way to guess the URL without having full access in the first place.

@lastzero commented on GitHub (Jan 5, 2021): This isn't Snapchat. When you share an image with Friends & Family, expect it a) being cached by their browser and b) being downloaded. There even is a download button. You don't need to hack it. We could continuously change all URLs, but that would give a false sense of security and cause a huge performance impact as pictures can't be properly cached by browsers anymore. Note that album share links are much shorter, and may be much easier to guess. You might also post them somewhere external and then forget. Let us know when you find a way to guess the URL without having full access in the first place.
Author
Owner
@lastzero commented on GitHub (Jan 5, 2021): ![Screenshot 2021-01-05 at 08 42 50](https://user-images.githubusercontent.com/301686/103619438-096c8c80-4f32-11eb-94e9-997b534bf400.png) - https://live.staticflickr.com/1932/45424204711_fa906764d0_b.jpg - https://live.staticflickr.com/1932/45424204711_389d0e639e_b.jpg
Author
Owner

@lastzero commented on GitHub (Jan 5, 2021):

As you can see, this "security issue" exists in all image sharing services to a certain extend. In addition, you can manually change PhotoPrism's preview & download tokens. Download tokens are also being regenerated on restart. When you don't know the token, you can't download images anymore. Previews don't have full resolution and don't contain metadata.

@lastzero commented on GitHub (Jan 5, 2021): As you can see, this "security issue" exists in all image sharing services to a certain extend. In addition, you can manually change PhotoPrism's preview & download tokens. Download tokens are also being regenerated on restart. When you don't know the token, you can't download images anymore. Previews don't have full resolution and don't contain metadata.
Author
Owner

@lastzero commented on GitHub (Jan 5, 2021):

I've closed this as it works as designed - there are always tradeoffs, and you can't have it both ways:

  • Super fast / usable
  • and super secure

Also I remember to have had this discussion before, just no time to search for existing GitHub issues. In the end, everyone agreed it is good enough for now.

Tokens & permissions will be revisited when implementing multi-user support, see #98

@lastzero commented on GitHub (Jan 5, 2021): I've closed this as it works as designed - there are always tradeoffs, and you can't have it both ways: - Super fast / usable - and super secure Also I remember to have had this discussion before, just no time to search for existing GitHub issues. In the end, everyone agreed it is good enough for now. Tokens & permissions will be revisited when implementing multi-user support, see #98
Author
Owner

@Leonetienne commented on GitHub (Jun 1, 2023):

The gist of this issue still persists. Even images which have never, ever been publicized, can be accessed via the direct image url.

An attacker could just brute-force urls to mine private pictures. Sure, the urls are not predictable, but guessable. If one has a LOT of images in an album (seen people talk about north of 40k images), I could imagine bombarding their server with requests from multiple attacking hosts for weeks could yield a few private images in a reasonable timeframe.

Why serve the file directly instead of just checking for a valid session id? Sure, it's a small performance hit, but exposing private images to anyone able to guess the url is bad.

To be fair, I did not check if Photoprism just refuses to answer after lots of failed attempts (kinda like fail2ban), but somehow I think that's unlikely.

Another aspect: I suppose the direct image uris are generated by some hash value? Please tell me it's not the original files unsalted hash. If it is, Mallory could check if Bob hosts specific images on his server by deriving the uri herself, and checking if it yields an image.

Maybe I'm missing something, but this seems like a glaring issue to me.

@Leonetienne commented on GitHub (Jun 1, 2023): The gist of this issue still persists. Even images which have never, ever been publicized, can be accessed via the direct image url. An attacker could just brute-force urls to mine private pictures. Sure, the urls are not predictable, but guessable. If one has a LOT of images in an album (seen people talk about north of 40k images), I could imagine bombarding their server with requests from multiple attacking hosts for weeks could yield a few private images in a reasonable timeframe. Why serve the file directly instead of just checking for a valid session id? Sure, it's a small performance hit, but exposing private images to anyone able to guess the url is bad. To be fair, I did not check if Photoprism just refuses to answer after lots of failed attempts (kinda like fail2ban), but somehow I think that's unlikely. Another aspect: I suppose the direct image uris are generated by some hash value? Please tell me it's not the original files unsalted hash. If it is, Mallory could check if Bob hosts specific images on his server by deriving the uri herself, and checking if it yields an image. Maybe I'm missing something, but this seems like a glaring issue to me.
Author
Owner

@lastzero commented on GitHub (Jun 1, 2023):

@Leonetienne See our Developer Guide for a detailed discussion of the architecture and the best practices it is based on:

Since then, we've additionally implemented individual download and preview tokens for each user, except if you're running your instance in public mode anyway. These completely random tokens change when you change your password and become inactive when you log out or a user account is deleted, so the URLs no longer work.

An attacker could just brute-force urls to mine private pictures. Sure, the urls are not predictable, but guessable.

Extremely unlikely. If you have that much time and resources you might as well raid the house or data center where the server is located and get a good lawyer to defend you (or get the first flight our of the country). If you think otherwise, please do the math and/or show a proof-of-concept.

@lastzero commented on GitHub (Jun 1, 2023): @Leonetienne See our [Developer Guide](https://docs.photoprism.app/developer-guide/) for a detailed discussion of the architecture and the best practices it is based on: - https://docs.photoprism.app/developer-guide/media/thumbnails/#server-api Since then, we've additionally implemented individual download and preview tokens for each user, except if you're running your instance in public mode anyway. These completely random tokens change when you change your password and become inactive when you log out or a user account is deleted, so the URLs no longer work. > An attacker could just brute-force urls to mine private pictures. Sure, the urls are not predictable, but guessable. Extremely unlikely. If you have that much time and resources you might as well raid the house or data center where the server is located and get a good lawyer to defend you (or get the first flight our of the country). If you think otherwise, please do the math and/or show a proof-of-concept.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/photoprism#657
No description provided.