I noticed something funny while using Fiddler to debug a cookie issue we are having. If the cookie path value is just the beginning of another site’s path value then the second site sees the first site’s cookies.
This is easier to show than to describe.
I created a simple site with the following ASPX code
<div>
Cookie Value <asp:Label ID="lblTest" runat="server"></asp:Label><br />
See Foo <asp:Label ID="lblSeeFoo" runat="server"></asp:Label><br />
See Foobar <asp:Label ID="lblSeeFoobar" runat="server"></asp:Label><br />
See Foonot <asp:Label ID="lblSeeFoonot" runat="server"></asp:Label>
</div>
In the code behind I create a cookie with a path based on ApplicationPath. The created cookie name includes the ApplicationPath name to make it easy to see in Fiddler. This code also looks for cookies from three specific web sites.
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim CookieName As String = "Test " + Me.Request.ApplicationPath.Replace("/"c, "")
Dim myCookie As HttpCookie = Me.Request.Cookies(CookieName)
If (myCookie Is Nothing) Then
myCookie = New HttpCookie(CookieName)
myCookie.Path = Me.Request.ApplicationPath
myCookie.Expires = DateTime.Now.AddDays(1)
myCookie.Value = DateTime.Now.ToString()
Response.Cookies.Add(myCookie)
End If
Me.lblTest.Text = myCookie.Value
Dim TestCookie As HttpCookie
TestCookie = Me.Request.Cookies("Test Foobar")
Me.lblSeeFoobar.Text = CStr(TestCookie IsNot Nothing)
TestCookie = Me.Request.Cookies("Test Foonot")
Me.lblSeeFoonot.Text = CStr(TestCookie IsNot Nothing)
TestCookie = Me.Request.Cookies("Test Foo")
Me.lblSeeFoo.Text = CStr(TestCookie IsNot Nothing)
End Sub
This application is then published to three web sites named Foo, Foobar and Foonot.
Viewing each site shows that Foobar and Foonot can see the cookie for Foo.
Here is Foobar’s result
Cookie Value 7/3/2014 10:40:01 AM
See Foo True
See Foobar True
See Foonot False
Foobar can read Foo’s cookies. Foonot can also see Foo's cookies. Foonot and Foobar do not see each other's cookies.
Here is the raw header information from Fiddler:
GET /Foobar/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: webdev
Cookie: Test Foobar=7/3/2014 10:40:01 AM; Test Foo=7/3/2014 10:39:43 AM
I tried searching for information on this issue but couldn’t come up with anything.
Is this a known thing?
Is there a way to prevent this?
Isn’t this a big security issue? In a shared hosting environment couldn’t I create a domain name that is longer that an existing one and pickup their cookies?
I'm not sure what the actual URL of Foo, Foobar and Foonot.
However, a cookie can be shared among multiple subdomains.
For example, IE10 shares the cookie among the following sites -
mysite.com
Foo.mysite.com
Foobar.mysite.com
Foono.mysite.com
If you want cookie to be available in the specific domain only, you can set the cookie's Domain property.
myCookie.Domain = "Foo.mysite.com";
When determining if a cookie is included in the request the cookie path is not examined as a ‘path’ but simply as a string value.
If it was processed as a path then the fact that one site happens to begin with the same text as another would not result in a match. MyDomain.com/Foo is a different web site than MyDomain.com/Foobar. I see it as a different site, IIS serves it up as a different web site, but the browser sees them as the same path as far as the cookies are concerned. The browser is simply doing a string compare on the path values and “/Foo” is the beginning of “/Foobar” so the browser includes those cookies.
Normally this is used so the cookies for a parent web site are available to the child we sites. If there is a site named “Parent” and a nested site named “Child” then the parent’s cookies with a path of “/Parent” would be available to the child site. The child’s cookies with a path of “/Parent/Child” would not be available to the parent. The situation I came across is that a site can pick up the cookies for one if its siblings when the sibling’s path is the start of that site’s path.
I suspect that this string comparison is the one that causes the cookie paths to be case sensitive.
So the answers to my questions are:
Is this a known thing?
Yes. The browser is doing a simple string comparison on the cookie’s path to determine if it should include the cookie in the request.
Is there a way to prevent this?
Yes. Append a slash to the end of the path. “/Foo/” is no longer a subset of “/Foobar/”
myCookie.Path = Me.Request.ApplicationPath + "/"
Isn’t this a big security issue? In a shared hosting environment couldn’t I create a domain name that is longer that an existing one and pickup their cookies?
No. As #the_lotus points out, the domains would be different. The paths only have an effect within a domain. I was only thinking of the paths and didn’t consider the domains.
Related
I've been trying to build a local proof of concept to move over our current solution to a different URL structure. I'm using ASP.NET with 3 projects that currently has these URLs mapped to them:
mysite.com
mysite.com/api
mysite.com/app
To setup the proof of concept I've setup 3 sites locally in IIS with the following URLs:
mysite.com
api.mysite.com
app.mysite.com
And have added the following entries into the HOSTS file:
127.0.0.1 mysite.com
127.0.0.1 app.mysite.com
127.0.0.1 api.mysite.com
Currently app.mysite.com talks to api.mysite.com to perform a user login, which returns a cookie back in the response. The issue is that the cookie is not being stored under mysite.com. Subsequent requests to api.mysite.com don't have the cookie attached in the request header, and therefore fail.
I've experimented setting the cookie's domain property with no success, as well as not including a domain property.
An example of a cookie returned in the request:
Set-Cookie: MyCookie=somestuff; domain=.mysite.com; expires=Sat, 06-Sep-2014 00:02:04 GMT; path=/; HttpOnly
Yet the cookie is never attached to any requests to api.mysite.com nor can i see it in the cookie browser of Chrome, Firefox, IE etc...
Note that I've enabled CORS in web.config to enable cross domain requests.
EDIT:
In response to Owain's answer. I'll clarify my current setup a little more.
Regarding <machineKey> I have created a machineKey and used the same values on both applications in the web.config file. This was already working locally and in production when using mysite.com/api and mysite.com/app It wasn't till moving to subdomains that i ran into this issue.
Here is my code for creating and attaching the cookie:
private void EncryptAndAttachCookieToHeaders(FormsAuthenticationTicket ticket)
{
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie newCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
newCookie.Domain = ".mysite.com";
newCookie.Expires = DateTime.Now.AddMonths(3);
newCookie.HttpOnly = true;
newCookie.Secure = false;
System.Web.HttpContext.Current.Response.Cookies.Add(newCookie);
// For testing purposes
HttpCookie hc = new HttpCookie("cookie1", "value");
hc.Domain = ".mysite.com";
hc.Expires = DateTime.Now.AddMonths(3);
HttpContext.Current.Response.Cookies.Add(hc);
HttpCookie hd = new HttpCookie("cookie2", "value");
hd.Domain = ".api.mysite.com";
hd.Expires = DateTime.Now.AddMonths(3);
HttpContext.Current.Response.Cookies.Add(hd);
All of these cookies (real one plus the two tests) are visible when viewing the response in Fiddler. However subsequent requests to api.mysite.com do NOT have any cookies attached in the request header. The browser doesn't seem to want to store the cookie now that I've moved to the subdomain structure.
To make cross domain login work you first need to edit the <machineKey> element inside the web.config.
By default it is something like this
<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
validation="SHA1"
decryption="Auto" />
With these settings, the .NET framework uses the automatically generated validationKey and decrytionKey
With the default settings the validation key and the decryption key will get generated by ASP.NET and will get used for authentication ticket and cookie, if you want multiple applications to share the same authentication ticket and cookie, you just need to set the validationKey and decrytionKey in all the applications to the same values
Make sure they are the same in both application something like for example
<machineKey
validationKey="FBFF3D4EFD359FD58AA480E0A63A9C817463A30EF5AFF4E212AD3321C122AECFFEC427C26D24B67296F5EBBB6A3736BF37A5027718E5426B92C9AC606F9AD66F"
decryptionKey="048F8A9F3D6A7D2E88738B36BEEB85FF0B4E23EEF11976D1C0F6F03B91CCFC37"
validation="SHA1"
decryption="AES" />
You can easily create some keys on this site http://aspnetresources.com/tools/machineKey
To make the authentication cookie visible at all other sub domains, you'll need to modify the cookie’s domain attribute.
// Call SetAuthCookie method
FormsAuthentication.SetAuthCookie("Test account", false);
//modify the Domain attribute
System.Web.HttpCookie TestCookie =
System.Web.Security.FormsAuthentication.GetAuthCookie(User.Identity.Name.ToString(), false);
TestCookie.Domain = "mysite.com"; // (Also try ".mysite.com" i'm unsure about this one) the second level domain name
Response.AppendCookie(TestCookie);
This should be everything you need to make cross domain cookies work
I found out the answer. The issue actually resided in how the AJAX request was being formed. I had to add an AJAX property called withCredentials to allow the browser to send the cross domain cookie. A drawback to this approach is that IE 9 and older don't support it....
From post Setting a cookie on a subdomain from an ajax request :
Set the allow Credentials header on api
Access-Control-Allow-Credentials: true
Use withCredentials for the request
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
Otherwise the XMLHttpRequest will not send the cookies, regardless of the Access-Control-Allow-Credentials header.
Remove the wildcard on Access-Control-Allow-Origin
Access-Control-Allow-Origin: http://www.example.com
The wildcard * will not work. The browser will discard the response if withCredentials was set.
In Asp.Net 4.0 I am writing a cookie using the HttpCookie Class. Debugging with IE9. The cookie is being saved to "/". Shouldn't it be saving to [user]\AppData\Local\Microsoft\Windows\Temporary Internet Files? When I try to find it there it does not exist. And how can I change this?
Dim cookie_name As String = "ProjectId"
Dim cookie As New HttpCookie(cookie_name)
cookie.Value = "123456789"
cookie.Expires = DateTime.Now.AddHours(8)
Response.Cookies.Add(cookie)
Debug.Print(cookie.Path.ToString) 'cookie is saving to "/"
cookie.path defines the base domain URLs the cookie applies to, its got nothing to do with the client file-system (which you don't need to worry about).
This describes how to find the cookie's physical path.
Alex K is quite correct, here's the MSDN reference for the Cookie.Path property which states:
The Path property specifies the subset of URIs on the origin server to
which this Cookie applies. If this propery is not specified, then this
Cookie will be sent to all pages on the origin server or servers.
I'm having this issue with Firefox browser when deploy our application to the QA environment. We have an ASP.NET 4 application with Form Authentication. In our application, we have 2 cookies: 1 for authentication ticket, another for other information.
The issue is: everytime I login to the system using Firefox, I'm bounced back to the login page.
When I use Fiddle to investigate the issue, I found out that for some reason Firefox doesn't "accept" our cookies: The 1st request to the Login page, our server returns the cookies just fine in the Headers:
Set-Cookie: .ASPXAUTH_Imp=...; expires=Thu, 07-Jun-2012 06:37:24 GMT; path=/
Set-Cookie: .ASPXAUTH=...; expires=Wed, 06-Jun-2012 09:57:24 GMT; path=/
However, in the next response, our cookies do not present in the request header.
This issue does not happen in any other browsers (IE, Chrome, etc). In other browsers, the cookies are accepted and passed along in the next requests.
When I view the cookies stored in Firefox, I can see my website, but it has only the ASP.NET_sessionID cookie. There's no trace of the other 2 cookies.
One more interesting point is this issue only happens in QA environment ( which has the LAN IP 10.16.x.x. I tried to use the machine name the the issue persists). When I debug in Visual Studio using localhost, it works perfectly fine.
This is my code for sending Cookie to client:
' ASP.NET authentication cookie '
Dim cookieExpiration As DateTime = DateTime.Now.AddMinutes(Constants.WebSettingsConst.TimeOut)
Dim authenticationTicket = New FormsAuthenticationTicket(2, CurrentContext.UserContextID(), DateTime.Now, cookieExpiration, True, String.Empty, FormsAuthentication.FormsCookiePath)
Dim encryptedTicket As String = FormsAuthentication.Encrypt(authenticationTicket)
Dim authCookie = New HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
authCookie.Expires = authenticationTicket.Expiration
authCookie.Path = FormsAuthentication.FormsCookiePath
'HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName)'
HttpContext.Current.Response.Cookies.Add(authCookie)
It sounds obvious, but have you checked the cookie settings in FireFox. If you go into privacy and pick use custom history you can specify not to accept cookies from third parties or only from specific sites.
I'm doing exactly the same as you and have no problems.
Dim authTicket As New FormsAuthenticationTicket(1, userIdentity, Date.Now, _
Date.Now.AddMinutes(15), False, userData)
Dim encTicket As String = FormsAuthentication.Encrypt(authTicket)
Dim faCookie As New HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
Response.Cookies.Add(faCookie)
I know how to handle this in ASP.NET, but is there a way to force the Classic ASP Session ID to be cleared? It is a randomly generated ID like ASPSESSIONIDG32423E that does not seem to be available in RESPONSE.COOKIES collection thus I can not clear it. We have a class ASP site still hanging around and recently it was an audit finding that after the user logs out the same session ID is reused.
MORE CLARIFICATION:
First visit to page, I see this in the proxy editor in Response:
Set-Cookie: ASPSESSIONID=PUYQGHUMEAAJPUYL; path=/Webapp
After a logout, I call Session.RemoveAll and Session.Abandon and then redirect user to login page. At which point I should see a new Set-Cookie with a different value for SessionID. Instead, I do not get a new cookie and the new login session reuses the original session cookie. This is an audit finding that we have to resolve in some way but there does not seem to be a way to control this.
So I did come up with a solution for this as follows. I added two pages called Start.asp and Start2.asp. The original login page was changed to check for a post variable which is now set on Start2.asp, so if login.asp does not see that post variable, it redirects to Start.asp. Start.asp invalidates the ASPSessionID by setting it to 0. The key is using Response.AddHeader "Set-Cookie" in order to do this since Response.Cookies("ASPSESSIONID...") gives an error that you can't access the element:
Code for Start.ASP
<%
If instr(Request.ServerVariables("HTTP_COOKIE"), "ASPSESSIONID") > 0 Then
Dim Allcookies
AllCookies = Split(Request.ServerVariables("HTTP_COOKIE"),";")
For i = 1 to UBound(AllCookies)
If instr(AllCookies(i), "ASPSESSIONID") > 0 Then
Response.AddHeader "Set-Cookie", Left(AllCookies(i),instr(AllCookies(i),"=") -1) & "=0; path=/;secure;httponly"
End if
Next
End if
Response.Redirect("start2.asp")
%>
Next, it calls Start2.asp which looks for all ASPSEssionID cookies and appends Secure; httponly (I had to add these for another finding, ASP metabase setting to add secure only works if the SSL cert. is on the web server. In our case the SSL cert is on a load balancer in front of the web server).
Code for Start2.asp
<%
'CODE for authorization/authentication
'...
Session.Contents.RemoveAll
Session.Abandon
If instr(Request.ServerVariables("HTTP_COOKIE"), "ASPSESSIONID") > 0 Then
Dim Allcookies
AllCookies = Split(Request.ServerVariables("HTTP_COOKIE"),";")
For i = 1 to UBound(AllCookies)
if left(Request.ServerVariables("HTTP_HOST"),2) = "65" and instr(AllCookies(i), "ASPSESSIONID") > 0 Then
Response.AddHeader "Set-Cookie", AllCookies(i) & "; path=/;secure;httponly"
End if
Next
End if
%>
<html>
<body>
<form action="login.asp" method="post">
<input type="hidden" name="start2" id="start2" value="Yes" />
</form>
<script type="text/javascript">
document.forms[0].submit();
</script>
</body>
</html>
Really, though, the new ASPSessionID is not generated until within Start2.asp so that Set-Cookie code for secure and httponly has to also be done in login.asp. So the same code above was copied to the top of login.asp just after this code:
If request.form("Start2") = "" Then
Response.Redirect("start.asp")
End if
IMO - you need to end the session rather than just clear out the session ID. In this case, Session.Abandon is the solution. Ref.: https://devguru.com/content/technologies/asp/session-abandon.html
HTH.
This relates to ASP.NET but describes the behaviour you are seeing in ASP
When you abandon a session, the session ID cookie is not removed from
the browser of the user. Therefore, as soon as the session has been
abandoned, any new requests to the same application will use the same
session ID but will have a new session state instance.
http://support.microsoft.com/?kbid=899918
This behaviour will only occur if using the same browser for the same session, as soon as the browser is closed the session cookie will be lost (providing an explicit expiry date has not been set).
You could try calling Session.Abandon then redirect the user to a page which uses JavaScript to clear all cookies, then redirect to the login page, or whatever page you like.
Clearing all cookies with JavaScript
I have an HttpModule that I created and am running on IIS 6 using Windows Server 2003. I can send cookies to the browser, but I can not read them on the next request, cookie is always null.
If I run this module on IIS 7 though, it works just fine. IIS 7 not an option at the moment as we have not switched over and this needs to get done before that will happen.
Also, I've already tried using the PostAcquireRequestState hook.
public void Init(HttpApplication httpApp)
{
httpApp.BeginRequest += OnBeginRequest;
}
public void OnBeginRequest(Object sender, EventArgs e)
{
var httpApp = (HttpApplication)sender;
var context = httpApp.Context;
const string cookieName = "sId";
if (!string.IsNullOrEmpty(context.Request.QueryString["cookie"]))
{
var ck = new HttpCookie(cookieName)
{
Value = httpApp.Context.Request.QueryString["cookie"],
Expires = DateTime.Now.AddDays(1)
};
httpApp.Response.Cookies.Add(ck);
}
else
{
var cookie = httpApp.Request.Cookies[cookieName]
}
}
I ran into a similar problem, but had a different solution, so I thought I'd share, in case it helps someone. I took zengchun's suggestion as well to use some tools to inspect request & response headers. Since I'm using IE, the F12 Dev Tools works great for this. As soon as I saw the response header for the cookie, I noticed the secure flag was set. Sure enough, I had copied code from a production SSL-hosted site to a test site that did not use SSL, so the secure flag on the cookie prevented the code from being able to read it. I updated the web.config to remove requireSSL from the httpcookies node, and my site started working. :)
your code look worked.the problem may be occur in the client-side how to request the next page.you can use the firebug with firefox or the fidder tools that can log your client-side request and see the request whether send cookd value in the request header to the server.
for example
the request headers:
get /1.aspx
.....
Cookie: sId=123 [if the client has a cookie then it will appear in here.]
the response headers:
Set-Cookie: sId=123; expires=Fri, 30-Mar-2012 07:20:23 GMT;
path=/
if the server add cookie to the response,then response it look like the above.
now,i guess the problem in your cook domain or you cookie path is different.
the best method to set cookie is like the follow code:
var ck = new HttpCookie(cookieName)
{
Value = httpApp.Context.Request.QueryString["cookie"],
Expires = DateTime.Now.AddDays(1),
Path="/",
Domain="your domain"
};
good luck.
Thanks to zhengchun I was able to get to the root of the problem. It turns out I was unable to set the cookie using requests to static files. I created .aspx files for my initial requests that redirected to the static files after setting the cookie. My HttpModule could then read the cookie after being set in the .aspx file. Not sure why I need a .aspx file to set the cookie instead of the HttpModule, but this fixed it.