How do i prevent direct linking to a few pdf files? - asp.net

I'm working on a website where I need to prevent the direct linking to a few pdf files. I'm using ASP.net 2.0. Is there an easy way in code to do this? or some simple IIS setting?
Right now i'm just using a standard anchor tag to link to the files. i can validate the user on the page containing the anchor tag but that still doesn't stop the user from nabbing the url and passing it on to someone else.

Create an HttpHandler to process all requests for .pdf. Since ASP.Net doesn't process PDF files out of the box, the HttpHandler will intervene and the web.config tell IIS to do so.
The Code
namespace YourNameSpace
{
public class HttpHandlerClassName: IHttpHandler
{
public bool IsReusable
{
//We dont want this class to be reused simply       
//because we dont want other requests to wait and lie pending       
//suggests that an instance of this class cannot
//be reused to serve more than one request.
get { return false; }
}
public void ProcessRequest(HttpContext context)
{       
string MyReferrer = context.Request.UrlReferrer.ToString();
//do some regex to determine if the request is originating from
//your site.
//Put logic for the behavior your want for the outcome of your
//RegEx match here
}
}
}
web.config
<system.web>
<httpHandlers>
<add verb="*" path="*.pdf" type="YourNameSpace.HttpHandlerClassName,YourAssemblyName"/>
</httpHandlers>
</system.web>

I've solved this in the past by using a file-fetcher asp page and streaming the bytes of the desired file, using the correct content-type in the header. Roughly:
Create page fetchfile.aspx
Sample URL: yoursite/fetchfile.aspx?n=pdfname.pdf
In fetchfile:
- Verify user is permitted to access file.
- Check chosen directory for presence of file from query string.
- Set content-type appropriately for file type.
- Open the file and stream all bytes of it to the client.
- Close the file and end the response.
Do not send anything in the response except the bytes of the file. If an error occurs, either redirect or return error html with the content-type set correctly.
The last time I did this I was serving up images, so I found a suitable free library and wrote my error message into a new custom image that could then display. For fun, you can use this technique to serve up content that is unexpected when anyone steals your bandwidth by hot-linking like this:
Create a blacklist with unwanted referrer names, in a database table or text file.
When a request for a file comes in, if the user is not allowed, check if the referrer is in the blacklist, and if not, let the file be read.
On a regular basis, check the referring pages and decide a suitable pdf to return to punish that site.
Add the content you want to return to your blacklist for each referrer, and when the referrer IS in the blacklist, then return the alternate diddled content.
This is a great way to make people REALLY unlikely to steal your stuff. Their experience looks like this:
They find some nice content they want to "borrow."
They post it on their site and it works just fine.
The next day you see their action and add them to the blacklist table but make it so anyone trying to obtain the content instead gets a nice little message of some sort: "example.com is STEALING this content from the owners at somecoolsite.com. Please let them know that you are displeased with their actions."
You have a good laugh.
This can be especially wonderful if the content being stolen is an image. Embarrassments galore are in store for bandwidth thieves!
Some extra clever IP-address and time comparison could possibly make you show the correct content to the actual perpetrator but the wrong content to any of his site visitors... evil and delicious.

Place the files in your App_Data folder and serve them using an HttpHandler. Inside the handler you can check the Session.IsNewSession property to see if the request is coming from outside your website. But you'd probably want to include some kind of authentication and validation inside the handler as well.

Put the PDF files in a directory. Use .htaccess and or robots.text.

Related

ASP.Net Core static file security (images specifically)

I have an issue that seems like a very common requirement, but I'm unable to find any help. Let's say I have an authenticated user uploading private photos to non browsable folder on my server. Each user has their own folder in a large file store, like...
/FileStore/{UserId}/Photos/my_cute_cat.jpg
The file is uploaded and I save a thumbnail of the photo like...
/FileStore/{UserId}/Photos/Thumbs/my_cute_cat_thumb.jpg
That user wants to download their photo. No problem...
User sends a request to download
I authorize the user and make sure they own that particular photo
I serve the file
I need display the thumbnail in a plain old img tag on the user's dashboard. The /Thumbs/ folder is not set up to serve static images. I don't want the /thumbs/ folder to be able to serve static images because they should only be visible to authorized users. What am I supposed to do?
If its just a small thumb nail, consider using embedded base64 image with more details here:
How to display Base64 images in HTML?
You can pass base64 down to the View by encoding the file into a base 64 format as a string explained here:
http://www.devcurry.com/2009/01/convert-string-to-base64-and-base64-to.html
Using this approach or even using a FileActionResult to serve the file through a controller has the big disadvantage of not being able to use a CDN to deliver the cached content. What you can do to help with this is still serve the images statically but give them obscenely long random names which is unguessable. When someone requests the image from you, then you simply provide them with the unguessable url.
First and foremost, if a static file should not be available to the whole world, then your web server should not serve the directory it is in at all. Nothing else will do on that front. If the directory is served, then the image can leak.
That then presents the problem of how to allow the user to access it. The simple answer there is that you need an authorized action that returns the image(s). Basically, that's going to look something like:
[Authorize]
public async IActionResult Image(string image)
{
var file = $"/FileStore/{User.Identity.GetUserId()}/Photos/{image}";
if (!File.Exists(file))
return NotFound();
return File(file);
}
Now, obviously, I don't know about your "FileStore", so the exact code here may need to change. The general idea is that you simply see if this file exists under the user's directory. If it does, they can have it. If not, they don't own it. You also should probably add in a bit more security, such as restricting the image param to only image types, so someone can't try to pull just any arbitrary file. They'd still have to somehow manage to get some aberrant file there, in the first place, but just in case it's better to not take chances.
The return methodology may need to change as well. Right now, I'm assuming a file on the filesystem of the server, so I just return a FileResult with the path. If you're pulling it from some sort of storage account in Azure, AWS, etc. then you'd use HttpClient to make the request, and then you should stream the response from that directly to a FileStreamResult.
I've not tested on linux, but if you make a folder where you have you pictures, you can actually just create a controller method that returns a file. Now, even if you are using a view served from another method, you can still call this file method from this razor view and display the image.
in controller called App I serve the image from a folder called Documents:
public IActionResult File(string id)
{
if (String.IsNullOrEmpty(id))
{
return PhysicalFile(Path.Combine(_env.ContentRootPath, "Documents", "Wrong.png"), "image/jpg");
}
return PhysicalFile(Path.Combine(_env.ContentRootPath, "Documents", id), "image/jpg");
}
in razor (using a bit bootstrap):
<img src="~/App/File/#profilePicture" class="img-fluid" />
use the location of your method that serves the file.

Using Request.Url.AbsoluteUri to construct a breadcrumb link

Inside my _layout view I have added the following link , to refresh the current page as part my breadcrumb bar:
#(aoutput == "Index" ? "Home" : aoutput)
The above is working well on my development environment , but I am not sure If using the Request.Url.AbsoulteUri is the correct way to reference the current page URL ?
AbsoluteUri includes the scheme (such as http), the host, the port, the path, query string data and the fragment. As far as I know the fragment will not be included in the current URL because it is not sent with the request (that is, it's handled by the browser client-side).
This should be fine to use and is unlikely to cause you issues. Just be aware that query string data is included, which means that if you had a (poorly-designed) data manipulation system such as /Users/Index?action=deleteMostRecentUser then the user might accidently delete users because the URI would include the action.
Alternatives are
Absolute Path: /Home/Index
Path & Query: /Home/Index?query=keyword
You can use it as you mentioned.
Better way you make a separate partial view to implement breadcrumb.
And pass wanted model from Controller side. (Or in ViewBag)
The main benefit is you'll get is better control over Logic also flexible for future changes.
Hope helps.

aspx, response.write, image and link referencing

I've spent a while trying to find out whether what I want is possible.
I have 3 websites on different domains. Two are in English, one in French. We have one page in english, one in french which are identical apart from the text. These pages and relevant images (we'll call common content) are stored on a separate domain (reasons beyond my control) and use response-writefile to insert the content into the two english pages.
Got all that working fine. However, the images in these common pages have a path relative to domain on which they are stored, which means when the pages are written into the main pages, the images dont show. I understand why and can get around it by putting in the full path of the image.
I would prefer not to go through every single page changing the image path, is there any way of the server knowing or being told that the image is relative to the common content and not the rendered page?
I wouldn't have thought so, but it would save my day if there was!
Further explanation:
Relative path of image:
abc.png
Path of common content file:
http://domain1.com/CommonContent/123.html
Code in final pages (domain2.com/english.html):
<% Response.WriteFile("/CommonContent/123.html"); %>
Rendered path of image (what I don't want):
http://domain2.com/abd.png
Ideal path of image in rendered page: ie, what I want to happen:
http://domain1.com/CommonContent/abd.png
what you can do is use WebClient class to get the page content
String URI = "http://domain1.com/CommonContent/123.html";
WebClient webClient = new WebClient();
Stream stream = webClient.OpenRead(URI);
String request = reader.ReadToEnd();
then you perform a string replace, here I'm not sure what can match, maybe something like this can match:
request.Replace("<src=", "<src=http://domain1.com/CommonContent/")
then you render this string to the browser.
You can make Application Settings having the path for each domain on each application, and then maybe add a function that will be in charge of writing the full path of the pictures. Also you can make an HTTP Module to address this issue as well as a Generic Handler that will receive all requests for images and load them from different domains/applications.
Good luck!

.NET MVC3: Prevent 404 for missing CSS files? Or ensure files exist before writing HTML?

We have a requirement to provide skinning capabilities to an ASP.NET MVC3 app.
My approach so far has been to tackle this with a cookie and child actions for the css files:
Client links to our app using a URL like www.ourapp.com/as/www.clientapp.com/then-go-to/path/in/ourapp.
The above url is routed to an action method that writes a cookie named "skin" with value "www.clientapp.com" and then redirects to /path/in/ourapp.
Our layout (masterpage) has an #Html.Action in the <head> section where the css files are to be rendered.
The child action inspects the cookie and creates a viewmodel that will tell the partial view which <link> tags to render.
The css file structure is based on the cookie value. So our css content might look like this:
/content
/www.clientapp.com
/style1.css
/style1.css
/www.client2app.com
/style1.css
/style2.css
I am open to hearing better patterns / alternatives to the above for applying skins to the layout. However this isn't the reason for my question.
There is currently a problem with this approach when the css files are not present in the filesystem. The request causes IIS to return a 404. We override the 404 page with a custom page (not using the same layout that has the #Html.Action in the <head>). This causes IIS to do some additional processing which is not necessary (for example partials & child actions to render sign-in/sign out links, horizontal nav, etc, on the 404 page's layout).
The way I see it there are 2 ways to solve this:
In the child action that configures the css, check to make sure the files exist on disk before telling the viewmodel that they should be rendered. Pro with this approach is that it should be fairly easy. Cons are that to unit test it, would have to wrap file I/O in a service that can be injected. Also the app will be deployed to Azure. I think Azure can read the filesystem (which is what would be needed), but not write to it.
Somehow prevent a 404 from being returned for css files. To do this, would we have to implement logic in global.asax Application_Error? Or is there another way to prevent missing CSS files from triggering a 404 response?
Which approach is correct? Or is there another that I'm not considering?
Update
We ended up solving this in the custom 404 error page like so:
[ActionName("not-found")]
public virtual ActionResult NotFound()
{
// do not return 404 for missing css files
if (Request.RawUrl.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
{
Response.StatusCode = 404;
return new EmptyResult();
}
return View();
}
One option would be to not override 404 errors for CSS requests.
For missing CSS files, return the 404 status, but leave the body empty. The body doesn't matter with a CSS file, since a human doesn't see it, and all the browser cares about is the status.

Problem passing parameters via Iframe in IE

I'm trying to execute an HTTP GET from my website to another website that is brought in via iframe.
On Firefox, you can see in the source that the correct url is in the iframe src along with it's correct parameters-- and it works.
On IE, you can see in the source that the correct url is in the iframe src along with it's correct parameters-- and it doesn't work...
Is there something about IE that doesn't let you pass parameters through an iframe in the querystring?
I've tried refreshing the iframe in IE, I've tried refreshing my page & the iframe in IE, and I've tried copying the url and re-pasting it into the iframe src (forcing it to refresh as if I just entered it into the address bar for that iframe window). Still no luck!
Anyone know why this is happening, or have any suggestions to try to get around this?
Edit: I cannot give a link to this because the site requires a password and login credentials to both our site and our vendor's site. Even though I could make a test account on our site, it would not do any good for the testing process because I cannot do the same for the vendor site. As for the code, all it's doing is creating the src from the backend code on page load and setting the src attribute from the back end...
//Backend code to set src
mainIframe.Attributes["src"] = srcWeJustCreated;
//Front end iframe code
<iframe id="mainIframe" runat="server" />
Edit: Problem was never solved. Answer auto accepted because the bounty expired. I will re-ask this question with more info and a link to the page when our site is closer to going live.
Thanks,
Matt
By the default security settings in IE query parameters are blocked in Iframes. On the security tab under internet options set your security level to low. If this fixes your problem then you know that is your issue. If the site is for external customers then expecting them to turn down their security settings is probably unreasonable, so you may have to find a work around.
Let's say your site is www.acme.com and the iframe source is at www.myvendor.com.
IIRC, most domain-level security settings don't care about the hostname, so add a DNS CNAME to your zone file for myvendor.acme.com, pointed back to www.myvendor.com. Then, in your IFRAME, set the source using your hostname alias.
Another solution might be to have your Javascript set the src to a redirector script on your own server (and, thus, within your domain). Your script would then simply redirect the IFRAME to the "correct" URL with the same parameters.
If it suits you, you can communicate between sites with fragment identifiers. You can find an article here: http://tagneto.blogspot.com/2006/06/cross-domain-frame-communication-with.html
What BYK said. I think what's happening is you are GETting a URL that is too large for IE to handle. I notice you are trying to send variable named src, which is probably very long, over 4k. I ran into this problem before, and this was my code. Notice the comment about IE. Also notice it causes a problem with Firefox then, which is addressed in another comment.
var autoSaveFrame = window.frames['autosave'];
// try to create a temp form object to submit via post, as sending the browser to a very very long URL causes problems for the server and in IE with GET requests.
var host = document.location.host;
var protocol = document.location.protocol;
// Create a form
var f = autoSaveFrame.document.createElement("form");
// Add it to the document body
autoSaveFrame.document.body.appendChild(f);
// Add action and method attributes
f.action = protocol + '//' + host + "/autosave.php"; // firefox requires a COMPLETE url for some reason! Less a cryptic error results!
f.method = "POST"
var postInput = autoSaveFrame.document.createElement('input');
postInput.type = 'text'
postInput.name = 'post';
postInput.value = post;
f.appendChild(postInput);
//alert(f.elements['post'].value.length);
// Call the form's submit method
f.submit();
Based on Mike's answer, the easiest solution in your case would be to use "parameter hiding" to convert all GET parameters into a single URL.
The most scalable way would be for each 'folder' in the URL to consist of the parameter, then a comma, then the value. For example you would use these URLs in your app:
http://example.com/app/param,value/otherparam,othervalue
http://example.com/app/param,value/thirdparam,value3
Which would be the equivalent of these:
http://example.com/app?param=value&otherparam=othervalue
http://example.com/app?param=value&thirdparam=value3
This is pretty easy on Apache with .htaccess, but it looks like you're using IIS so I'll leave it up to you to research the exact implementation.
EDIT: just came back to this and realised it wouldn't be possible for you to implement the above on a different domain if you don't own it :p However, you can do it server-side like this:
Set up the above parameter-hiding on your own server as a special script (might not be necessary if IE doesn't mind GET from the same server).
In Javascript, build the static-looking URL from the various parameters.
Have the script on your server use the parameters and read the external URL and output it, i.e. get the content server-side. This question may help you with that.
So your iframe URL would be:
http://yoursite.com/app/param,value/otherparam,othervalue
And that page would read and display the URL:
http://externalsite.com/app?param=value&otherparam=othervalue
Try using an indirect method. Create a FORM. Set its action parameter to the base url you want to navigate. Set its method to POST. Set its target to your iframe and then create the necessary parameters as hidden inputs. Finally, submit the form. It should work since it works with POST.

Resources