Using ASP MVC 4.5, how can one apply security measures in order to prevent users from accessing content directly?
Like for example preventing the access of images or other files stored on the web server just by entering their link.
Place your image in a non-web accessible folder.
Create a server side script (for example, an HttpHandler) that can read the image file and return its contents in the HTTP response.
In that script, perform your user validation to make sure the user has access to that file.
In the HTML, have the src attribute of the img tag point to your script.
The user can still directly type in the URL to your script to see the image. But you can at least require that the user is logged into your site and is authorized to view the image.
Use an authentication system such as ASP .NET Membership and require certain credentials to access the content. Other than that, there really isn't a way. If a user has a direct link and access to that area of your website, by nature of how web servers work, there isn't a way to stop it.
There are certain security measures you can take to help prevent users from getting a direct link though, a simple one would be disabling a right click.
I have produced the following HTTPHandler in order to prevent hotlinking.
It seems to work on my project, however I do not certainly know if this is the best practice.
public void ProcessRequest(HttpContext context)
{
//write your handler implementation here.
//Http
HttpRequest request = context.Request;
HttpResponse response = context.Response;
//Header - Properites
int Index = -1;
string[] Keys = request.Headers.AllKeys;
List<string[]> Values = new List<string[]>();
//Header - Loop to get key values
for (int i = 0; i < Keys.Length; i++)
{
Values.Add(request.Headers.GetValues(i));
//Check if property "Accept" exists
if (Keys[i].CompareTo("Accept") == 0)
Index = i;
}
//Check if URL and URL Referrer are null
if (context.Request.Url != null && context.Request.UrlReferrer != null && Index >= 0)
{
//Check image types
if (!context.Request.UrlReferrer.AbsolutePath.EndsWith(".bmp") ||
!context.Request.UrlReferrer.AbsolutePath.EndsWith(".jpg") ||
!context.Request.UrlReferrer.AbsolutePath.EndsWith(".jpeg") ||
!context.Request.UrlReferrer.AbsolutePath.EndsWith(".png"))
{
//Check header "Accept"
if (Values[Index][0].CompareTo("*/*") == 0)
{
//Get bytes from file
byte[] MyBytes = File.ReadAllBytes(context.Request.PhysicalPath);
//new HttpContext(context.Request, context.Response).Request.MapPath(context.Request.RawUrl).ToString()
context.Response.OutputStream.Write(MyBytes, 0, MyBytes.Length);
context.Response.Flush();
}
else
//Redirect
context.Response.Redirect("/Home");
}
else
//Redirect
context.Response.Redirect("/Home");
}
else
//Redirect
context.Response.Redirect("/Home");
}
Also the Web.config was modified as follows:
<system.webServer>
<handlers>
<!--My-->
<add name="PhotoHandler-BMP" path="*.bmp" verb="GET" type="MVCWebApplication.Handlers.PhotoHandler" resourceType="File" />
<add name="PhotoHandler-JPG" path="*.jpg" verb="GET" type="MVCWebApplication.Handlers.PhotoHandler" resourceType="File" />
<add name="PhotoHandler-JPEG" path="*.jpeg" verb="GET" type="MVCWebApplication.Handlers.PhotoHandler" resourceType="File" />
<add name="PhotoHandler-PNG" path="*.png" verb="GET" type="MVCWebApplication.Handlers.PhotoHandler" resourceType="File" />
</handlers>
</system.webServer>
Feel free to comment on any improvements.
There is little you can do unless you want to bug your users. One possible (and widely used) thing could be checking your referrer (and make it be on your application), but that can easily be spoofed.
If security for this is something critical, the only thing that comes into mind is having everything downloaded through a script which would check for credentials (or any other security measure you might want), but there's not much else you can do.
If the browser has indeed downloaded something to the local machine, there's absolutely no way you can prevent that user to use that data (you can put some barriers, like avoiding right-clicking, etc., but all of them can be avoided in some way or another).
Related
We have a payment sub-system which goes like this;
User enters payment data
Payment data sent to the 3rd party service url (with form post / eg. POST https://3rdparty.com/form )
3rd party service checks data and sends some other data (again using form post but this time to our url / eg POST http://localhost/Success)
In other words;
http://localhost/Page1 -> POST https://3rdparty.com/Service -> POST http://localhost/Success
But when second redirection (3rd party service posting data to our side), session object becomes null.
I've mimicked the POST scenario to http://localhost/Success from both same origin and other origin.
When posting from the same origin
http://localhost/Test -> POST -> http://localhost/Success => OK. Session IS NOT null
But when posting from another origin
http://testdomain/Test -> POST -> http://localhost/Success => OK. Session IS null
BTW; this happens regardless of other origin's procotol, be it http or https
What is the source of this behaviour ? I couldn't find anything meaningful...
EDIT :
I've added the code of the ActionFilterAttribute which intercepts every request and checks for session (due to #mxmissile 's comment)
public class LoginFilter : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Session[Constants.SessionNames.USER_OBJECT_NAME] == null)
// this is where I check the Session object and it's null
// it doesn't have any key in it
{
filterContext.HttpContext.Session.Abandon();
filterContext.Result = new RedirectResult("/Login/Login");
}
}
}
EDIT 2 :
Using the configuration below, which re-writes Set-Cookie header's flags, I was able to modify flags. (suggested at https://blog.elmah.io/the-ultimate-guide-to-secure-cookies-with-web-config-in-net);
<rewrite>
<outboundRules>
<clear />
<rule name="Add SameSite" preCondition="No SameSite">
<match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
<action type="Rewrite" value="{R:0}; SameSite=lax" />
</rule>
<preConditions>
<preCondition name="No SameSite">
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=lax" negate="true" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
But SameSite=Lax wasn't sending cookies to the 3rd party service as stated here https://www.thinktecture.com/identity/samesite/prepare-your-identityserver/ , due to Chrome's 80.x updates
So I've changed SameSite=lax to SameSite=none and it worked.
But as I imagine, this would generate new issues with security. Such as CSRF attacks.
What would be the best way to do it ?
I'll assume that you've got the SameSite=None; Secure working and just point out a few things that might be of use.
What is the source of this behaviour?
The Chromuim SameSite FAQ page has quite good summary of what the change is:
Q: What are the new SameSite changes?
Chrome is changing the default behavior for how cookies will be sent in first and third party contexts.
as I imagine, this would generate new issues with security.
Based on my interpretation of the quote above, I don't believe so: Chrome will send less cookies by default now. By reverting to sending these cookies, you are not increasing your attack surface - you merely restoring your existing flow.
Actually, if you read that page further, there's one thing that is now required - for the cookies to be sent over HTTPS. So you are actually better off by acknowledging the fact that these cookies are sensitive and requiring the user to always opt for secure connections.
Such as CSRF attacks.
I don't believe this point depends on Chrome Cookie policy. It still is valid. If not doing that already - check out the Microsoft docsumentation and see if a standard Razor Html helper will be feasible option for you:
#using (Html.BeginForm("Login", "Login")) {
#Html.AntiForgeryToken()
}
What would be the best way to do it?
Looking at how Google is pushing it as the default behaviour, it seems your best bet is to adopt it I'm afraid.
I have a mature ASP.NET web application using FormsAuthentication (FA) to manage logins. Under certain situations, I would like to redirect the "just logged in" user to a different URL to the one that FA uses. As per standard functionality, FA will redirect to our normal homepage (specified in web.config) unless a redirectUrl was used when it hits a page that requires an authenticated user.
In my system, after the user's username/password is validated I typically use
FormsAuthentication.RedirectFromLoginPage(userName, createPersistentCookie: true); // Also calls SetAuthCookie()
which handles most situations. However, depending on certain conditions (primarily based on the newly logged in user's role) I want to redirect to a different destination. My thoughts for doing this are to call SetAuthCookie() myself and then use Response.Redirect(myUrl, false); and ApplicationInstance.CompleteRequest().
Despite doing this, the very next request comes in using for the URL defined in my tag of web.config.
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="120" cookieless="UseCookies" defaultUrl="~/?raspberry=true" />
</authentication>
Here is the actual code I am using (if a different url is required, it is specified by the overrideUrl parameter:
internal static void CreateTicket(string userName, string overrideUrl)
{
// Ref: http://support.microsoft.com/kb/301240
if (overrideUrl == null)
{
FormsAuthentication.RedirectFromLoginPage(userName, createPersistentCookie: true); // Includes call to SetAuthCookie()
}
else
{
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie: true, strCookiePath:FormsAuthentication.FormsCookiePath);
HttpContext.Current.Response.Redirect(overrideUrl, false);
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
If I pass in a value of /special/path for overrideUrl I would like the next request to come in to be '/special/path'. Instead I am seeing /?raspberry=true
Is something else forcing defaultUrl?
Is there a way to "snoop" into the Response object while debugging to see if a Redirect is already in place? or set a breakpoint whenever it gets set so I can look at the call stack?
EDIT: At the end of my method, the Response object is showing the following properties:
RedirectLocation: "/special/path"
Status: "302 Found"
StatusCode: 302
StatusDescription: "Found"
IsRequestBeingRedirected: true
HeadersWritten: false
which all looks absolutely correct.
Thanks for any advise.
Okay, I can see what is causing it. It is the fact that I am using the Login Web Control and running my code as part of the Authenticate event. Looking at the reference source for the Login Web Control, the Authenticate event is raised by its AttemptLogin method (search for it in ref source). After raising the event and seeing that Authentication was successful, it then goes on to:
Call SetAuthCookie itself (I've already done this myself but presumably the only thing I should be doing in my code is determining if authentication was successful or not, and not messing with AuthCookie or redirects)
Performing a Redirect (overwriting my carefully crafted Redirect)
I'm going to have to figure out a solution as there these methods are private (can't override by inheriting the usercontrol) and there appears to be no option for overring or suppressing the user of it's GetRedirectUrl().
hello I'm trying so hard for this, I cant understand most question since this is my first time developing in ASP.NET here is my problem.
Im declaring session variable when the user click the submit in the login page then redirecting them to somepage.aspx
if (dt.Rows.Count > 0)
{
Session["usersId"] = usersId;
Session["usersLevel"] = usersLevel;
Session["usersRegion"] = usersRegion;
Session["notification"] = "";
Response.Redirect("pages/Dashboard.aspx");
}
So after that that I put something in my Web.config
<system.web>
<compilation debug="true" targetFramework="4.0"/>
<sessionState timeout = "1" mode = "InProc" />
</system.web>
So ofcourse the session will expire?/timeout? then in my Global.asax I put this
void Session_End(object sender, EventArgs e)
{
Response.Redirect("Login.aspx");
}
However an HttpExeception rises, that says
Response is not available in this context.
Why did the response is not available? when it said that the sessionstate mode must be set to InProc? I just want the user to be redirected in that page when the session expires/timeout(I dont know their difference but looks same to me)
thank you
You may consider to do the redirect in AuthenticateRequest event. Only inProc sessionstate provider supports session end event and it may happen any time(even after the relevant request is responsed, that's why you saw that exception).
I have requirement to append USERNAME to the URL in server side using URL Rewrite module.
Why?:
I have website site1, when USER logs in to site1, he will see a link to site2., This link is URL or reports. (Tableau).
Authenticated ticket has been created using FormAuthentication in site1.
When USER clicks the link, authenticated username should be passed to site2.
I could append username from client side, but due to security issues I have to append username to URL in server side before it gets executed.
So I have decided to use URL rewrite provider, which grabs the username by decrypting the cookie value as shown below
namespace PlatformAnalysisUrlProvider.PlatformAnalysisProvider
{
class AnalysisRewriteProvider: IRewriteProvider, IProviderDescriptor
{
public void Initialize(IDictionary<string, string> settings,
IRewriteContext rewriteContext)
{
}
public string Rewrite(string value)
{
string[] cookievalues = value.Spli('=');
FormAuthentication ticket = FormAuthentication.Decrypt(cookievalues[1]);
//Decrypt throws error as shown below
}
}
}
Cookie Values
cookievalues [0] = has the key
cookievalues [1] = has the value
Example:
233AWJDKSHFHFDSHFJKDFDKJFHDKJFKDJFHDHFDHFKJHDFKJHDFJHDKJFHDSKJFHDF
It's a cookie value. But decrypt is not happening
I am getting following error
Unable to validate data.
at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(
Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start,
Int32 length, IVType ivType, Boolean useValidationSymAlgo,
Boolean signData)
Here is my settings in IIS for URL Rewrite
Requested URL: Matches the Patterns
Using: Regular Expression
Ignore Case - Checked
Conditions -
Input : {HTTP_COOKIE}
Type : Matches the Pattern
Pattern : .*
Action Type - Rewrite
Rewrite URL - http://11.155.011.123{HTTP_URL}&USERNAME={PlatformAnalysisUrlProvider:{C:0}}
I have also set up MACHINE KEY as suggested by this forum
I have referred this post for development
One of the stack overflow post suggested that it might be firewall or antivirus issue. But I do not have antivirus installed or firwall enabled.
It really helps if someone direct me to code sample where web site hosted in IIS and URL Rewrite provider is used.
Updating Error Log
MODULE_SET_RESPONSE_ERROR_STATUS
Notification - "PRE_BEGIN_REQUEST"
HttpReason - "URL Rewrite Module Error"
Updating post with Machine Key Info
<MachineKey Description="AES" validation="SHA1"
descriptionKey="******"
validationKey="******" CompatibilityMode="Framework20SP2">
Reason May be - The website where cookie getting created is developed using .NET Framework 4.5. The provider where we reading the cookie is Framework 3.5. Is this may be the cause? OR Do we need config file for Provider project?
Updates - I have added machine key to Machine.config , but it still did not work :(
Alternative Solution
Add App.config to class Library
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- ... -->
<add key="SecurityKey" value="somevalue"/>
<!-- ... -->
</appSettings>
</configuration>
Copy config to GAC
Follow this blog - http://techphile.blogspot.in/2007/02/2.html
Encrypt the value (refer here) and create custom cookie during Login
Use the Decrption logic inside custom rewrite provider
The good thing about this is that the error is a general decryption error and not one with URL Rewrite itself, so that gives you a wider area to search for help. The mechanics of URL Rewrite seem to be right.
Decrypting means that it must be encrypted by the same method as you're decrypting it. So it has to be the right cookie and the right decryption method.
Since you're not checking which cookie that you're reading from, you could get unexpected results if the wrong cookie is first in the list of cookies.
Here are some steps that I recommend to troubleshoot this:
Create a simple URL Rewrite rule that will give you the value of your cookie. I created a rule to do that in my example below. You can test it by going to yoursite.com/getcookie. It should redirect to yoursite.com/?Cookie={cookievalue}
Then you can test your code outside of the URL Rewrite provider. You can create a simple console app or winforms app to test the rest of the code.
I recommend adding a check for the existence of the cookie and then a check again for the 2nd value. For example: if (cookievalues[1] != null).
When developing the decryption method, you don't have to worry about URL Rewrite. As long as it works in a test app in .NET then you should be set.
<rule name="Get cookie value" stopProcessing="true">
<match url="^getcookie" />
<action type="Redirect" url="/?Cookie={HTTP_COOKIE}" appendQueryString="false" redirectType="Found" />
</rule>
Using System.Web.Providers.DefaultMembershipProvider along with System.Web.Security.MembershipUser, I am attempting to change a users password in the databases. For some reason, no matter what I do, the MembershipUser.ChangePassword(old, new) returns false with no errors.
In the database, my ApplicationID that is associated to the Application name is correct, and my user has that ApplicationID attached to it in both the Memberships and Users tables. The User is not null when it comes to that point, all the data is accurate in the object. I am at a complete loss here, any help would be greatly appreciated as no other source has been able to help me.
EDIT:
User is not locked out, the user is active, and the password is correct. All
password requirements are being fulfilled. This is extremely frustrating since
the MembershipUser.GetUser() finds the user and all of its associated data but
will not change the password.
Additional Steps:
- Tried MembershipUser.UnlockUser() just in case, no luck.
- Tried MembershipUser.ResetPassword() then a change. This fails with the
error message "Value cannot be null" even though it should be able to.
- GetSpecificVerion of the entire solution and working to when it first
broke. Every version worked, including my latest (Yay!), but then it
magically stopped working again a few minutes later, no code changes
were made at all.
Bounty added, looking for any and all possibilities that could lead to a fix...
Config File:
----
<add name="aspnetdb" connectionString="Application Name=appname;Type System Version=SQL Server 2008;server=***,****;Initial Catalog=aspnetdb;Integrated Security=false;pwd=****;user id=****" providerName="System.Data.SqlClient" />
----
----
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="aspnetdb" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" passwordAttemptWindow="10" applicationName="mysite.com" />
</providers>
</membership>
----
Method:
public ActionResult ChangePassword(ChangePasswordViewModel model)
{
// Indicates whether changing the password was successful or not.
Boolean passwordChanged = false;
if (ModelState.IsValid)
{
// Grab the current user.
MembershipUser user = Membership.GetUser(User.Identity.Name, true);
if (user != null)
{
passwordChanged = user.ChangePassword(model.OldPassword, model.NewPassword);
}
}
return Json(new { Success = passwordChanged });
}
Turns out someone else within the company was using the same DEV database to back an internal OpenID Provider. During the development of this OpenID Provider, the data was getting altered causing odd behavior. Thankfully I just happened to notice some data change on me and ask some questions of the other groups, saved me from wasting any more time..
This is still odd that I was able to retrieve a user but not change the password of that user. In the end, clearing up the communication between the two projects seems to have cleared up the issue for now.
Nice lesson learned here when sharing a database. Thanks for those of you who tried to help :)