1

I have a very simple asp.net core 2.0 application and what it looks like a very simple problem, but I can't find the solution.

My application store diferent private images for users like photo, copy of their id...those images of course have to be private and only the web application can show them in certain pages. For example the administrator can see them when browsing the users in the admin panel, or a user can see his images...

The files are in a folder "users/1/photo.jpg" or "users/243/id.jpg". Of course those folders have to be private and you can't browse them.

How can I do so when I use a image tag I can see the image:

<img src="???">

Without showing the real path and also preventing anyone to access that file but the pages I want.

Thanks.

UPDATE
First of all, thanks to Mark Redman and T.S., you helped a lot.

Finally what I'm doing is to have the sensible images outside the StaticFiles public folder and the non-sensible ones in the wwwroot folder.

PART 1. SENSIBLE IMAGES
For the sensible images I'm using a IActionResult to return the file, but after I encrypt the file name. This is just an example...

public IActionResult ViewUser(int id)
{
    var model = new Model();
    ....
    model.EncryptedId = _protector.Protect(id.ToString("D6"));

    return View(model);
}

This way I can return the encrypted id to retrieve the image I want without publishing the real id.

In my View:

<img src="/home/GetImage?id=@Model.EncryptedId" />

And the GetImage would look like this:

public IActionResult GetImage(string encryptedId)
{
    var decryptedId = _protector.Unprotect(encryptedId);

    var file = Path.Combine(_hostingEnvironment.ContentRootPath, "MyPrivateFiles", decryptedId + ".jpg");

    return PhysicalFile(file, "image/jpeg");
} 

This way, as far as I understand:
- I'm protecting my private files by not storing in a public folder such as wwwroot, so no one can download them directly.
- Also no one can get the id of an existing user and try to call my Action GetImage?id=232 because I have encrypted that id.

Other protection level I can have is only authorize certain users to access the GetImage Action, for example allowing users only to get their images or allowing administrators to download any.

PART 2. NON SENSIBLE IMAGES
For the non sensible images (such as user public photos) I'm storing them in the wwwroot because I need them to be public.

Using the asp-append-version="true" I can cache images, which is a very good improvement of this Asp.Net Core.

The only thing left for me would be to obfuscate those image names so I'm not showing "domain.com/users/1234.jpg" and show, for example, "domain.com/users/sdfjknkjSD2.jpg".

I don't know how to do this WITHOUT LOSING the advantage of the caching. Is there a way to do this?

Thanks in advance.

John Mathison
  • 904
  • 1
  • 11
  • 36
  • you have to code for this. This problem already solved. Try google – T.S. Oct 02 '18 at 20:52
  • Possible duplicate of [How can i hide/secure image path?](https://stackoverflow.com/questions/4192984/how-can-i-hide-secure-image-path) – T.S. Oct 02 '18 at 20:53
  • Sorry, but I have been googleing a lot and can't find a solution. The asnwer you provided is from 2 years ago and not based in core asp.net 2.0. Also I'm not trying to stop people linking to my images from another site, I just don't want anybody to be able to access the images but my site where I want, not all the pages and not, of course, directly in the url. – John Mathison Oct 02 '18 at 21:06
  • 2 years ago and 1 year from now - there will be difference in technicalities but principals are the same. *"Also I'm not trying to stop people linking to my images from another site, I just don't want anybody to be able to access the images but my site "* - this is oxymoron to me. sounds like 1st part contradicts 2nd. Please make it clear what you need. Google for: `how to hide image location in asp.net` – T.S. Oct 02 '18 at 21:13
  • First thanks for your time here...Imagine a user upload his id photo to the site. I want that user to be able to see his id photo, and also, I want the administrator of the page to be able to play around with that image (printing, viewing...). But I don't want anybody to be able to see those images, or guess someone's images...for example, if the path is "domain.com/files/user/123/photoid.jpg" I don't want them to see that and say, for example: let's see user 2342, and switch user's numbers and access the file...what would be the best approach? – John Mathison Oct 02 '18 at 21:19
  • For what you described, you don't store images on the drive but rather in database. even if you store it on the drive outside your web app, you still need some database pointer to where it is stored. Definitely, don't store in website hierarchies. then again. Imagine how many websites are in the world. And if not all then many need to solve same issue. This is already solved and you can find solution. – T.S. Oct 02 '18 at 22:01
  • [https://stackoverflow.com/questions/15896818/hide-the-src-string-of-an-image-tag] [https://stackoverflow.com/questions/12935508/asp-net-show-and-hide-image-at-runtime] [https://stackoverflow.com/questions/2676051/can-i-hide-an-image-path-on-asp-net-page-without-http-handler] endless – T.S. Oct 02 '18 at 22:05

2 Answers2

1

It is best practice to not store your images in the web folders.

here is a very basic action, something like this...

[HttpGet]
public FileResult GetImage(string userId, string id)
{
    return File($"{YourRootPath}users/{userId}/{id}.jpg"), "image/jpeg");
}

<img src="/YourController/GetImage/?userId=243&id=123"/>

for .net core you might want to do this;

public async Task<IActionResult> GetImage(string userId, string id)
 {
        Stream stream = await [get stream here__]

        if(stream == null)
            return NotFound();

        return File(fileStream, "image/jpeg", $"{id}.jpg");
    }   
Mark Redman
  • 24,079
  • 20
  • 92
  • 147
  • Thanks for the help, but in this case any user could see in the image src this "/YourController/GetImage/?userId=243&id=123", so they can guess, if I change the userId to 2133 then I get that user's images...don't you think? Knowing that I can't use the database, what else I can do? Also I'd like to take avdantage of the cache solution of asp.net core and use the "asp-append-version" – John Mathison Oct 02 '18 at 21:49
  • Indeed, I was just using your details to make sense to you, Ideally you would validate the ID or use a GUID etc. The main gist was how you get the file from a location outside the web folders referenced by the src attribute. – Mark Redman Oct 02 '18 at 21:51
  • well, I could check if the connected user is the same as the requested image or it is an administrator, then send the image..if not don't do it. But also it'd be great to hide those user ids and show something more abstract – John Mathison Oct 02 '18 at 21:55
  • "one" way to do it would be to reference your files in your table by a guid, then save the file using the same GUID and use that to get the file. This would prevent duplicates etc etc....assuming you have few files.. if you have millions of files, then you need to something better... but that is a different question. – Mark Redman Oct 02 '18 at 21:57
  • we can have +75K users, so many files...For the compromised images such as user id cards we can have that, and for the user photos and other images in the wwwRoot folder so we can take advantage of caching, but in both cases we would need to show different name than the ids – John Mathison Oct 02 '18 at 22:02
  • In your database you will want a table that references your files, this will store fileId, guid, originalFilename, size and other properties you might need... and possibly a reference or data that describes where your file is, either on disk (which I wouldn't do anyways) or in the cloud.. and use the guid, original filename and storage reference to get the actual file, possibly using the above code...in a nutshell.. use something like Azure blob storage or service like cloudinary and don't have the files on disk. – Mark Redman Oct 02 '18 at 22:06
  • @MarkRedman also possible to create temporary file id that will not be repeatable and exist for duration of page rendering. Once rendered, deleted – T.S. Oct 02 '18 at 22:10
  • why not having the files on disk? Sorry...maybe silly question, but since I can't have on the database... – John Mathison Oct 02 '18 at 22:13
  • You can have them on the disk, there are just better ways to have files stored these days and not sure what you mean about the database? You just need to think of a way to abstract the reference to users. – Mark Redman Oct 02 '18 at 22:18
  • One possible solution that I have come with, thanks to you guys, is in the action where I retrieve the id of the user I can encrypt that id, then in the view, use that encrypted id to call "/Controller/GetImage/?id=@Model.EncryptedId" so this way the id would be "hidden" and then in the GetImage I just decrypt the id and use it to get the file and return the PhysicalFile...what do you think? – John Mathison Oct 02 '18 at 22:34
0

One solution i can suggest to this problem is, creating a folder using GUID id and storing the image of the user in that folder. So this happens for each user so now while showing using img tag, only the app would be able to render the correct image as the path is derived from the db while hackers would find it almost impossible to guess the guid id which essentially is a folder name and required for guessing the image and rendering it on webpage.

  • Is this solution secure for search engines? Google can also search images by input image instead of text. You think this approach will also prevent search engines to list all the files inside images directory? – Muhammad Saqib Mar 02 '20 at 13:04