Jump to content

How can I make an image folder only accessible by local web pages?


DeX

Recommended Posts

I have a contract and I'm using a signature_pad API I downloaded from Github to allow the user to draw their signature on screen and then it's saved into a folder on my server. When the page reloads, it embeds the signature image into the contract so it appears signed when it's printed out.

 

Since I'm saving these signatures onto my server as images, I think it's best if they're not accessible for people to just browse through and download at will so I was saving them to a directory above my web folder so that they're not accessible to the web. The problem now is I can't reference them in any way to have them accessible by the PDF generator when creating a PDF of the quote.

 

I could move them into a web accessible folder to solve this but then the public can view them, how do I stop this? Which approach would be best? Thanks.

Link to comment
Share on other sites

Actually I should keep the images in an outside folder because I have a /var/www/development folder and a /var/www/production folder. I have a script that deploys from development to production when a new version is ready for release and I wouldn't want to copy all of the development testing signatures over the production signatures on a deployment. It would probably be easier if I didn't have the signatures inside that folder at all.

Link to comment
Share on other sites

Ask your web host.  I have been able to keep documents outside of my "www" folder with some web hosts, and others I had no choice but to store it in a sub folder.

 

One simple thing you can do, is let's say you have your www/production/ folder, simply put a blank file in that folder as index.html and you cannot use a web browser to peruse the other files.

Link to comment
Share on other sites

I agree with Jacques1. If you can store the file outside you web root you should be able to access it with the PHP code. You may have an issue in how you are trying to reference the file. The only reason I can think of what this would not be viable is if you are using an external, web-based PDF creation process. If that is the case, I would suggest building/obtaining a solution that is completely hosted on your server - else your application can break if that third party solution was to change anything. Otherwise, if you have no option to build the solution on your server (either because of time, cost, etc.) you could copy an image file to a temp location, call the PDF creation process, then delete the temp file. But, that seems a cludgy solution.

Link to comment
Share on other sites

You're right, I was doing some more testing today and got this far:


            // display
            $imageSource = '/includes/api.php?function=getSignature&quote=' . $_GET['quote'];
            // PDF
            $imageSource = '/var/signatures' . $this->controller->getBranch() . $_GET['quote'] . '.png';
            

And my API function does this:


            $mime_type = mime_content_type("/var/signatures" . $controller->getBranch() . $_GET['quote'] . ".png");
            header('Content-Type: '.$mime_type);
            return readfile("/var/signatures" . $controller->getBranch() . $_GET['quote'] . ".png");

Then it's displayed like so:

<img width="16" height="16" alt="star" src = "' . $imageSource . '" />

As you can see I have one imageSource that works with display it and another that works with the PDF generator (DOMPDF). If I use the one that works with the PDF, it doesn't display and that's because it's trying to load the image at www.domain.com/var/signatures/.......

Why would it do that?

 

EDIT - By display, I mean that I'm also trying to display the HTML to the user so they can see the contract. It's the same HTML passed to DOMPDF to create the PDF.

Link to comment
Share on other sites

Your current approach doesn't make any sense.

 

First off, it seems you don't perform any input validation whatsoever. If that's actually your code, then users can steal arbitrary images (or even arbitrary files) from your server simply by manipulating the file path through the quote parameter:

/var/signatures/../../path/to/completely/different/image.png

I also don't see any concept for user authentication. How do you even know who the current user is and which signatures they own? Keeping the signatures outside of the document root doesn't help when your API script makes them available to everybody again.

 

So this needs a major overhaul:

  • Whatever you do, you must validate all input and take extra care when building file paths. You don't want anybody to freely traverse your file system.
  • You need an authentication system (e. g. user accounts protected with passwords).
  • If you insist on this HTML preview, then you need a secure access script which authenticates the user and makes sure they cannot access any files they aren't supposed to access. I strongly recommend you refrain from implementing your own file access code with readfile() and instead delegate the task to the webserver through the “sendfile” mechanism (see the mod_xsendfile module for Apache or X-Accel for nginx). This is much more secure and efficient.
Link to comment
Share on other sites

 

Your current approach doesn't make any sense.

 

First off, it seems you don't perform any input validation whatsoever. If that's actually your code, then users can steal arbitrary images (or even arbitrary files) from your server simply by manipulating the file path through the quote parameter:

/var/signatures/../../path/to/completely/different/image.png

I also don't see any concept for user authentication. How do you even know who the current user is and which signatures they own? Keeping the signatures outside of the document root doesn't help when your API script makes them available to everybody again.

 

So this needs a major overhaul:

  • Whatever you do, you must validate all input and take extra care when building file paths. You don't want anybody to freely traverse your file system.
  • You need an authentication system (e. g. user accounts protected with passwords).
  • If you insist on this HTML preview, then you need a secure access script which authenticates the user and makes sure they cannot access any files they aren't supposed to access. I strongly recommend you refrain from implementing your own file access code with readfile() and instead delegate the task to the webserver through the “sendfile” mechanism (see the mod_xsendfile module for Apache or X-Accel for nginx). This is much more secure and efficient.

 

The only users logged in are approved salesmen, they're the ones creating the quotes and contracts on the system. They effectively have ownership to all signatures so they can create the quotes, it's the public that should not have access to any signatures.

 

I'm relying on the fact nobody knows the API location to be able to view signatures, is this incorrect? Should I assign a token to each signature and have the token required to view? I'd much rather just point to the root folder instead, using the API method is not ideal for me, I was just exploring it as an option.

 

All users are authenticated with passwords, no pages are visible except the login page without a valid user account. This is all working perfectly already, it's only the API that sends commands through to the controller from outside if I allow them.

 

The preview happens when the salesman is creating the quote with the customer, they will sit down together, logged in as the salesman, and they will keep editing options until they achieve the quote price they are happy with. The quote is displayed to the user on the page as they're changing inputs and this is loaded as HTML from the quoteView page. Once the quote is saved, a PDF is created from the HTML code and this PDF generator (DOMPDF) places all the images where they're supposed to go. However when DOMPDF creates the PDF, it's having trouble finding the images using the same path that works with the inline HTML just fine.

 

Since the quote is viewed live on screen and also used by the PDF generator, I create it in the quoteView and return it when requested by a logged in user.

 

I may be doing something wrong at the very root here but if you'd give me some ways to verify that I would greatly appreciate it. All I know right now is the entire system is working perfectly except I can only choose to load this signature properly inline or on the PDF, not both. Otherwise any other images loaded on the quote work fine, I have the company's logo at the top and that shows on both. That's inside the web directory though.

Link to comment
Share on other sites

So you do have an authentication system. Then use it: Make the API reject any request from a client who isn't logged in. And fix the other two vulnerabilities I pointed out.

 

A proper API workflow looks like this:

if not logged_in():
   error("not authorized")
   exit

// validate the input to prevent users from messing with the path 
if not is_valid_number(quote_id):
   error("invalid ID parameter")
   exit

// delegate file access to webserver (the exact header depends on the webserver software)
header("X-Accel-Redirect: /path/to/internal/signature/image.png")
exit

Whether or not your DomPDF manages the resolve the image paths properly is a different story. Google says that remote requests require extra settings in the library.

Link to comment
Share on other sites

So you do have an authentication system. Then use it: Make the API reject any request from a client who isn't logged in. And fix the other two vulnerabilities I pointed out.

 

A proper API workflow looks like this:

if not logged_in():
   error("not authorized")
   exit

// validate the input to prevent users from messing with the path 
if not is_valid_number(quote_id):
   error("invalid ID parameter")
   exit

// delegate file access to webserver (the exact header depends on the webserver software)
header("X-Accel-Redirect: /path/to/internal/signature/image.png")
exit

Whether or not your DomPDF manages the resolve the image paths properly is a different story. Google says that remote requests require extra settings in the library.

Currently I'm doing that, the page sends a request to the controller -> model to create the PDF and this checks for a valid login before proceeding. Then the DOMPDF class is included, the object is initialized and PDF is created / stored on the server. I guess I could flip this around to keep the working PDF code from above where it uses the /var/signatures..... path and creates the quote properly. Then instead I can focus on why displaying the quote on the page breaks the image, maybe that's an easier way of looking at it.

 

Why would the page try and load www.domain.com/var/signatures/.....? When I right click the broken image and select the option to load the image in a new tab, this is where it's looking. Why would that be? Can I reference files outside the web directory like this for inline display? Is there a single way to reference it so it works for both?

 

EDIT - your comment about extra code needed for external access in DOMPDF, that's setting isRemoteEnabled to true but I believe this is only for referencing files on a separate web server.

Link to comment
Share on other sites

You've been given detailed instructions for what to do. If you reject them all, there isn't much we can do for you.

 

A web page cannot directly reference files outside of the document root. It's not possible. When you give a browser something like

<img src="/some/internal/image.png" alt="...">

then it will not somehow access the raw filesystem on your server (that would be insane). It resolves the path relative to the base URL, so it makes a request for

https://www.yoursite.com/some/internal/image.png

And clearly this file doesn't exist.

 

If you want to make internal files accessible on the website, then you either need a secure(!) API as explained above, or you can embed the images themselves in data URLs (not recommended unless you know what you're doing). Using an insecure API and relying on the naive assumption that nobody will find it is not an option. Keeping the files inside the document root and hide them with long random filenames is theoretically possible, but it's very hacky and comes with several problems (for example, once an URL is known, the only way to hide the file again is to rename it and break the old URL for everybody).

 

Got it?

 

Now to your PDFs. How the PDF converter interprets image paths depends entirely on the library. Appearently paths are treated as paths on the file system (contrary to what a browser would do). Great. Then create two HTML documents:

  • one HTML document for your website with API URLs as image sources
  • another document for your PDF converter with path URLs as image sources

It might be nice to use the same document for both, but not if this compromises security. Can your PDF converter handle data URLs? Then that might be an option, otherwise just use two documents.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.