When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, September 7, 2005

Creating Expiring Web Pages

By Scott Mitchell


Introduction


In last week's article, Passing Tamper-Proof QueryString Parameters, I illustrated one technique for passing tamper-proof querystring parameters from one Web page to another. (In reality, I've not used the techniques discussed in last week's article to create tamper-proof URLs within a website; rather, I've used it as a means to pass authentication information from one website to a partner website.) In my discussion in last week's article I did omit one very important safe-guard for such systems: how to prevent replay attacks (thanks to alert 4Guys reader Phil D. for pointing out this ommission).

The problem with the tamper-proof querystring parameter values approach I shared last week was that the tamper-proof check does not do any sort of expiration check. That means once a URL has been created it is good forever. To see where this can cause problems, imagine that we are using this technique to pass authentication information from one website (Site A) to a partner website (Site B). Specifically, Site A sends to Site B a querystring that looks like: ?UserName=username&Digest=HashOfUsernameAlongWithSecretSalt. Both websites will have to agree upon the secret salt and share that with one another, but as long as the end user is not privy to this knowledge, they cannot craft their own, valid authenticating querystring parameters. If end user Sally clicks from Site A onto Site B, she'll see the UserName parameter in the querystring. However, if she tries to alter it, changing it from "Sally" to, say, "Maria", the receiving page's digest check will fail.

The problem of replay attacks remains, however. If Sally bookmarks the link from Site A to Site B, and her evil coworker Theo finds this bookmark, Theo can then visit Site B authenticated as Sally. Theo can do this days, weeeks, or months after Sally last visits Site B. What we'd like to do, is make that link from Site A to Site B only "active" for a short period of time, say 60 seconds. That way, even if Sally bookmarks the link directly from Site A to Site B, if she - or anyone else - visits that link more than 60 seconds after the link was created, they'll get an error on Site B, saying that the link has expired.

In this article we'll see how to prevent against these types of replay attacks. The method examined can be used in the authentication scenario just described, or can be used to creating "self-expiring" links. Read on to learn more!

- continued -

Creating a Link That Expires


Last week's article illustrated how to pass tamper-proof querystring parameters (if you have yet to read that article, please do so before continuing on). To create a link that expires, all we need to do is pass the time the link was created in the querystring as a tamper-proof URL. The receiving page, then, can examine the time and determine whether or not the link has expired. I prefer to let the receiving page determine when a link expires. However, if you trust the sender you could adjust the algorithm to allow the sender to specify for how long the link is valid. In that case, rather than having the sender create a tamper-proof querystring value with the time the link was created, you'd add a tamper-proof querystring value indicating when the link expires.

Let's look at a very simple example. Imagine that I have a site with two pages, PageA.aspx and PageB.aspx. I want to only allow people to visit PageB.aspx if they have first read through PageA.aspx and checked an "I Have Read this Page" checkbox. (Clearly there are many ways to accomplish this, and using an "expiring page" might be a bit of overkill, but it should illustrate the point nicely.) Let's first look at PageA.aspx:

PageA.aspx
<form runat="server">
  Blah blah blah... boring legalese... <b>READ IT ALL!</b>
  <p>
  <asp:CheckBox runat="server" id="IHaveReadThis" Text="I Have Read this Page" />
  <asp:Label runat="server" id="ReadMeMsg" Visible="False" ForeColor="Red" 
         Font-Italic="True">Please read this page and 
                                 check this checkbox...</asp:Label>
  <br />
  <asp:Button Runat="server" id="btnGoToPageB" Text="Go to PageB.aspx" />
</form>

<script runat="server" language="VB">
  Sub GotoPageB(sender as Object, e as EventArgs)
    'Make sure the checkbox is checked
    If IHaveReadThis.Checked Then
      'Redirect user to URL
      Response.Redirect(CreateTamperProofURL("ExpiringDemoB.aspx", _
                        String.Empty, _
                        "Time=" & DateTime.Now.ToString("yyyyMMddHHmmss")))
    Else
      ReadMeMsg.Visible = True
    End If
  End Sub

  ... Code emitted for brevity, see live demo for complete code ...
</script>
[View a Live Demo!]

This page presents the legalese (the "Blah blah blah" part) followed by a CheckBox and a Button Web control. When the Button is clicked, a postback ensues and the GotoPageB event handler is executed. This event handler first checks to ensure that the CheckBox has indeed been checked. If it has not, the user is kept on this page and shown an error message. If the user has checked the CheckBox they are redirected via a Response.Redirect() to ExpiringDemoB.aspx, passing in the querystring the current date/time formatted as the four digit year, followed by the two-digit month, followed by the two-digit day of the month, followed by the two-digit hour as of a 24-hour clock, followed by the two-digit minute, followed by the two-digit second. The CreateTamperProofURL() method, which is omitted for brevity from the above code section, was discussed in detail in Passing Tamper-Proof QueryString Parameters. As you'll recall from that article, CreateTamperProofURL() uses MD5 to create a digest of the tamper-proof querystring parameter(s) (Time, in this example).

That's all there is for PageA.aspx. All that's left is to create PageB.aspx, where we'll parse the querystring and determine if request is too dated or not.

Creating the Receiving Page and Determining if the Request is Stale


In order to determine if the incoming request to PageB.aspx is stale, PageB.aspx needs to perform the following checks:
  1. Ensure that a Time querystring parameter is included,
  2. Ensure that the Digest matches up with the Time value,
  3. Determine the delta between the Time value in the querystring and the current date/time, and see if that delta is greater than the expiry of the page.
This can be accomplished with the following code:

PageB.aspx
<h2><asp:Label runat="server" id="msg" /></h2>

<script runat="server" language="VB">
  Sub Page_Load(sender as Object, e as EventArgs)
    'Make sure the Time is provided
    Dim t as String = Request.QueryString("Time")
    If t is Nothing OrElse t.Length = 0 Then
      Msg.Text = "ERROR: No time value was passed in the querystring"
      Msg.ForeColor = System.Drawing.Color.Red
    Else
      'Make sure the digest matches up
      EnsureURLNotTampered(String.Format("Time={0}", Request.QueryString("Time")))

      'Finally, make sure that the time is within the 'legal' window
      Dim reqTime As DateTime = DateTime.ParseExact(Request.QueryString("Time"), _
           "yyyyMMddHHmmss", System.Globalization.DateTimeFormatInfo.InvariantInfo)
      
      Dim RequestWindowInSeconds as Integer = 15
      If Math.Abs(reqTime.Subtract(DateTime.Now).TotalSeconds) > RequestWindowInSeconds Then
        Msg.Text = "ERROR: Link is stale!"
        Msg.ForeColor = System.Drawing.Color.Red
      Else
        Msg.ForeColor = System.Drawing.Color.Black
        Msg.Text = "Congrats, this is the protected message!"
      End If
    End If
  End Sub

  ... Code emitted for brevity, see live demo for complete code ...
</script>
[View a Live Demo!]

As with the previous code snippet, some of the code here has been omitted for brevity; refer to the live demo for the complete source code and consult Passing Tamper-Proof QueryString Parameters for an explanation as to how the receiving page ensures that the digest matches up.

As aforementioned, PageB.aspx does three things: first it makes sure that the Time querystring parameter is provided; next, it makes a call to EnsureURLNotTampered() to verify that the user hasn't attempted to tinker with the Time or Digest values; lastly, it reads in the Time value passed in the querystring and determines how many seconds have elapsed between the passed-in time and the web server's current time. If that delta is greater than 15 seconds, it displays a warning message, informing the user that the link has expired.

Real-World Considerations


The example just presented was designed to highlight the concept, not to make any claims as to the best practices for implementing expiring URLs. For example, in the code snippets we just examined, all of the logic is placed squarely in each page. This approach would be much less tedious to apply if the timestamp checking and expirary rules were moved to a base class or, even better, an HTTP Module.

Furthermore, the expiry for this example was set very low - 15 seconds - which is likely too short in real applications. Users might be coming over dial-up modems, or there may be a hiccup between the client and your web server. In any case, if it takes longer than 15 seconds between when the Response.Redirect() crafts the Time parameter and when the client's request for that page reaches the web server, then the user will arrive at the linked to page seeing the expirary message. Talk about a less than ideal user experience!

In this example the expiring page was arrived at from another web page in the same site. If you are using this technique to pass information from one website to another, realize that these web servers may be in different time zones. The code provided as-is does not take into affect any time zone differences when calculating the delta between the time the querystring Time parameter and the current date/time. To overcome this, both web servers should agree to talk about times in terms of UTC time. The DateTime structure has methods to convert local time to and from UTC time (see ToUniversalTime() and ToLocalTime()). Additionally, different web servers may have a skew between their system clocks. (That is, the receiving web server might think the UTC time is 8:23:08, but the sending time server's clock might be slow and think the UTC time is 08:21:52.) Hopefully both are using some Internet-based time service to periodically update their clocks, but even so, between refreshes there may be some drift. Hence, you must take care not to make the window that a link is valid too small.

Lastly, if you are creating such expiray links and placing them on web pages where they may be spidered by search engines, the search engines may store the resulting page's URL using the time-expiring URL. This means that a potential visitor who clicks on a search engine result may see the "This link has expired" message, which would be annoying for the user.

FYI, I have used this technique in two real-world applications I've worked on, both of which involved passing authentication information from one website to a partner website. With the first project, the time window was rather short at about five minutes. The system was used as follows: a person would visit Site A and on their Site A homepage there'd be a link to complete a certain task that was handled by partner Site B. Clicking that link would build up a URL that included their authentication information and the time the URL was created (in UTC time), sending it to the receiving web server. The receiving web server, then, would verify the integrity of the digest and then make sure that the delta between the current time and sent time was no greater than 10 minutes. The second project used this expiring URL to offer people a "window" during which they could sign up for the site. A person would receive an email with instructions to sign up via a link provided in the email; the link in the email would "expire" in 30 days, meaning that if the user attempted to go signup 30 or more days after receiving the email, they'd be shown a message informing them that the opportunity they had to sign up had passed.

Conclusion


In this article we examined how to create expiring web pages. This feat is accomplished by creating tamper-proof querystring parameters (a technique examined in a previous article) and sending along the time in the querystring. The receiving page, then, is able to determine whether or not the link used to arrive at the page has expired or not based on some business logic.

Happy Programming!

  • By Scott Mitchell



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article