1
votes

I maintain an ancient website written mostly in Classic ASP/VBScript (not ASP.NET). When it was written, HTTPS wasn’t really a thing yet and everything on the site was horribly vulnerable when I took over, but I’ve since forced HTTPS wherever I can.

In the most recent versions of Firefox, I’ve been getting oodles of console warnings about cookies when visiting the site:

Cookie “XYZ” will be soon treated as cross-site cookie against “https://example.com/page.asp” because the scheme does not match.

I’m assuming – though I haven’t been able to find any confirmation – that this is because the cookies set by the server are not set as secure, something I’d failed to consider when I moved the site to HTTPS.

So now I need to change everything to set all cookies as secure. Trouble is, a quick search tells me there are about 850 cookies set in about 175 files on the server, and… well, I’m lazy and don’t feel much like going through 175 files and adding Response.Cookies("XYZ").Secure = True 850 times.

Is there some way to make the server (IIS, I’m guessing?) automatically force all cookies to be set as secure?

Edit: A lot of the more recent stuff I’ve changed/built/added to the website is done in PHP, just to avoid Classic ASP. I’ve just noticed that this list of cookies that are said to “be soon treated as cross-site” only appears on PHP pages, not on actual ASP pages. So perhaps “the scheme does not match” isn’t just about HTTP vs HTTPS but also about ASP vs PHP somehow? How would I go about making sure that cookies set in ASP are not considered cross-site on PHP pages? I didn’t think the language that creates the cookies would make a difference since they’re all just plain text cookies made over an HTTPS connection…

1
Sounds like you needed a centralised method for adding the cookies instead of just hard-coding them in every page. With a centralised method for cookie creation adding the Secure = True would have been a one-line change. - user692942
@Lankymart Indeed – unfortunately, there’s pretty much nothing about the way this website was built that is centralised or intelligent. It may be easier in the long run to actually make such a function and try to see how far I can get with grepping to replace calls to Response.Cookie("XYZ") = "abc" with function calls. - Janus Bahs Jacquet
If you are going to have to do the work anyway, would recommend it to avoid the pain in the future. - user692942
I realise this is an older thread, but just to clarify, I think the console warning messages you are getting are about the "SameSite" attribute. Browsers are starting to take this more seriously now. This articles explains it better than I can: web.dev/samesite-cookies-explained - Adam
@Adam But the pages are on the same site. They’re served from the same domain, even from the same folder much of the time. The cookies are being in example.com/folder/fileA.asp and then the warnings appear in example.com/folder/fileB.php. Neither Secure nor SameSite is set in them, but they are set and read through secure connections on the same site. - Janus Bahs Jacquet

1 Answers

2
votes

Making the session cookie secure using web.config:

This rewrite will change:

ASPSESSIONIDXXXXXXXX=YYYYYYYYYYYYYYYYYYYYYYYY

into:

__Secure-session=XXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYY

Not only will it make the session cookie secure, but it eliminates the annoying bug IIS seems to have for setting multiple ASPSESSIONIDXXXXXXXX cookies. (This occurs because the session cookie name is not a constant, but by making it a constant, putting all the relevant data inside, then rewriting it back using an inbound rewrite rule, you'll only have one secure session cookie at a time.)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
        <rules>
            <clear />
            <!-- "HTTP_COOKIE" must be added to the "allowed server variables" in IIS under URLRewrite -->
            <rule name="session cookie revert">
                <match url="(.*)" />
                <conditions>
                    <add input="{HTTP_COOKIE}" pattern="(.*)__Secure-session=([0-9a-zA-Z]+)\/([0-9a-zA-Z]+)(.*)" />
                </conditions>
                <serverVariables>
                    <set name="HTTP_COOKIE" value="{C:1}ASPSESSIONID{C:2}={C:3}{C:4}" />
                </serverVariables>
                <action type="None" />
            </rule>
        </rules>
        <outboundRules>
            <rule name="session cookie rewrite">
                <match serverVariable="RESPONSE_Set_Cookie" pattern="ASPSESSIONID([0-9a-zA-Z]+)=([0-9a-zA-Z]+)(.*)" negate="false" />
                <!-- Set the session cookie as HttpOnly during the rewrite. Classic ASP doesn't 
                do this by default, but it's important for preventing XSS cookie stealing. 
                You could also add "; Secure" if you only want the session cookie to be passed 
                over an SSL connection, although this also means the cookie can only be set over 
                an SSL connection too, which could be a problem when testing on localhost. -->
                <action type="Rewrite" value="__Secure-session={R:1}/{R:2}{R:3}; SameSite=None; HttpOnly; Secure" />
            </rule>     
        </outboundRules>
    </rewrite>
    <httpProtocol>
      <customHeaders>
        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <add name="Referrer-Policy" value="strict-origin" />
        <add name="Strict-Transport-Security" value="max-age=31536000" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

You could probably make all cookies secure using web.config, but I use a function:

<%

' Create cookies.

Sub CreateCookies(ByVal NameArray, ByVal DataArray, HttpOnly, ExpireDays)
    
    Dim CookieStr, CookieExpires, i
    
    ' Validate the array parameters.
    
    If NOT IsArray(NameArray) OR NOT IsArray(DataArray) Then Exit Sub
    
    If NOT uBound(NameArray) = uBound(DataArray) Then Exit Sub
    
    ' Set the cookie expiration date.
            
    CookieExpires = CookieExperationDate(ExpireDays)
    
    ' If HttpOnly is true...
    
    If HttpOnly Then CookieStr = "HttpOnly; "
    
    ' If the https protocol is being used, set the cookie as secure.
    
    If uCase(Request.ServerVariables("HTTPS")) = "ON" Then
        
        CookieStr = CookieStr & "Secure; "
        
    End If
    
    ' Loop through the cookies array and set each cookie.
    ' Both the name and value should be encoded using the
    ' Server.URLEncode() function before being passed, if
    ' necessary (usually not, unless your name/data values
    ' contain characters like ";" or "=")
    
    For i = 0 To uBound(NameArray)
    
        Response.AddHeader "Set-Cookie",NameArray(i) & "=" & DataArray(i) & "; Path=/; SameSite=None; " & CookieStr & CookieExpires
    
    Next
    
End Sub

' Deletes all cookies, can easily be changed to delete individual cookies though

Sub DeleteCookies()

    Dim Item

    ' There isn't a header command for deleting a cookie, instead, you
    ' set the expiration date to a time that has already expired, and
    ' the users browser will automatically delete the cookie.
    
    Const CookieDeleteDate = "Expires=Thu, 01 Jan 1970 00:00:00 UTC"
    
    ' Loop through each cookie and set a header to delete it.
    ' NOTE: Request.Cookies doesn't retrieve session cookies, at least
    ' not the ASP session cookie.
    
    For Each Item In Request.Cookies
        
        If NOT InStr(Item,"_") = 1 Then ' For avoiding deleting Google analytics and Cloudflare cookies, plus any cookie beginning with an underscore usually indicates it's some sort of third party cookie.
        
            Response.AddHeader "Set-Cookie",Item & "=; Path=/; " & CookieDeleteDate
        
        End If
                    
    Next
    
End Sub

' Generate and format the cookie expiration date

Function CookieExperationDate(ExpireDays)

    Dim UTCtime, ActualLCID
    
    ' Get the current UTC time.
    
    UTCtime = UTC_DateTime()
            
    ' Change the LCID to 1033 as to be RFC 6265 compliant.
    
    ActualLCID = Response.LCID
    Response.LCID = 1033
    
        UTCtime = DateAdd("d",ExpireDays,UTCtime)
        
        ' Format the cookie experation date
        
        CookieExperationDate = "Expires=" &_ 
        WeekDayName(WeekDay(UTCtime),True) & ", " &_ 
        ZeroPad(Day(UTCtime)) & " " &_ 
        MonthName(Month(UTCtime),True) & " " &_ 
        Year(UTCtime) & " " &_ 
        "00:00:00 UTC"          
    
    ' Change the LCID back to what it originally was.
    
    Response.LCID = ActualLCID
    
End Function

' Prefix numbers less than 10 with a 0, (01,02,03 etc...) this is used for cookie date formating

Function ZeroPad(ByVal theNumber)
    
    ZeroPad = theNumber
    
    If Len(theNumber) = 1 Then
    
        ZeroPad = cStr("0" & theNumber)
        
    End If
    
End Function

%>
<script language="javascript" type="text/javascript" runat="server">
    
    // Return the current UTC date and time regardless of what timezone the server is set to
    
    function UTC_DateTime() {
        
        var date = new Date();
        
        // date.getUTCMonth() returns a value from 0 - 11 (dunno why) so we need to  + 1
        
        var result = date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds();
        
        // Pad month/day/hour/minute/second values with a 0 If necessary
        
        return result.replace(/(\D)(\d)(?!\d)/g, "$10$2");
        
    }
</script>

The CreateCookies sub uses arrays so you can set multiple cookies at once:

Call CreateCookies(Array("cookie1","cookie2","cookie3"), Array("cookie1 value","cookie2 value","cookie3 value"), True, 90)

EDIT: Slight downside to using Response.AddHeader to Response.Cookies:

When you use Response.Cookies that cookie is immediately available, which means you can use Request.Cookies to fetch that cookie from the server cache on the same page load.

So:

Response.Cookies("test") = "test cookie"
Response.Write Request.Cookies("test")

Will output test cookie. I can't really think why this is useful, but I do vaguely recall using it in the past.

With:

Response.AddHeader "Set-Cookie","..."

The cookie will only be available to read using Request.Cookies when the page is resubmitted, but of course you have MUCH more control over the cookie settings. Not a big deal, but worth mentioning.