Hijack Session Variables in ASP.NET

download.gif  Download C# Source Code
Stupid user tricks...At least that's what we call them where I work.  Those users who can't get something on a Web site to work and claim they are typing in exactly what you are telling them and it still doesn't work.  As well all know, this is rarely the case.  Wouldn't it be nice to be able to see exactly what they are typing in and experience their current environment as close as possible?  Funny how mother necessity tends to inspire us these days.
It just so happens that I was working on a newly requested feature that uses modules to intercept http requests for tracking member specific page utilization across our Web sites (I'll briefly touch on this below) that I was asked this very question.  After a couple of days of tinkering, I've come up with a prototype or proof of concept that this is in fact possible with limited changes to an existing Web site.  The code techniques for intercepting http requests has been covered on just about every tutorial site or programming book.  So, I won't bore you with the basics.  This link from MSDN pretty much does that for us: Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET.
The goal was to develop a way to that allows a technical support representative to grab or hijack a user's session variables and automatically route the tech support rep directly to the last page the user was on.  There would no need for a rep to lookup the user and their login information or ask a ton of questions about where they were on the site and how they got there.  If you have ever overheard technical support calls, you know how difficult and frustrating this can be for both the user and the support rep.
As a secondary goal, it would be helpful for the support rep to be able to monitor form element submissions in real time.  Identifying miskeys from a user can be critical to resolving problems.  And last, but not least, implement this solution with little to no changes to existing production Web sites and make it reusable across multiple sites.  This is a pretty tall order for a production ready application.  So for the purposes of this article, I kept things pretty simple and scaled down.  I also didn't implement security percautions that would have made the code sample more complex to step through.

buycontent.gif
New Message Board Posts
Reading line feed
MSMQ
Embedding ShockWave Files Into POwerpoint With C#
Mono runtime
Async Callback To Update GUI Question
<SCRIPT type=text/javascript><!-- google_ad_client = "pub-8682474657542641"; google_alternate_ad_url = "http://www.eggheadcafe.com/alternateads.asp"; google_ad_width = 300; google_ad_height = 250; google_ad_format = "300x250_as"; google_ad_channel ="0679176943"; google_color_border = "FFFFFF"; google_color_bg = "FFFFFF"; google_color_link = "6D99CA"; google_color_url = "008000"; google_color_text = "000000"; //--></SCRIPT> <SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript> </SCRIPT> <IFRAME name=google_ads_frame marginWidth=0 marginHeight=0 src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-8682474657542641&amp;random=1092912878375&amp;lmt=1092912878&amp;alternate_ad_url=http%3A%2F%2Fwww.eggheadcafe.com%2Falternateads.asp&amp;format=300x250_as&amp;output=html&amp;channel=0679176943&amp;url=http%3A%2F%2Fwww.eggheadcafe.com%2Farticles%2Fhijacksession.asp&amp;color_bg=FFFFFF&amp;color_text=000000&amp;color_link=6D99CA&amp;color_url=008000&amp;color_border=FFFFFF&amp;ref=http%3A%2F%2Fwww.asp.net%2FDefault.aspx%3Ftabindex%3D0%26tabid%3D1" frameBorder=0 width=300 scrolling=no height=250 allowTransparency><img height="1" width="1" border="0" src="http://pagead2.googlesyndication.com/pagead/imp.gif?client=ca-pub-8682474657542641&random=1092912878375&lmt=1092912878&alternate_ad_url=http%3A%2F%2Fwww.eggheadcafe.com%2Falternateads.asp&format=300x250_as&output=html&channel=0679176943&url=http%3A%2F%2Fwww.eggheadcafe.com%2Farticles%2Fhijacksession.asp&color_bg=FFFFFF&color_text=000000&color_link=6D99CA&color_url=008000&color_border=FFFFFF&ref=http%3A%2F%2Fwww.asp.net%2FDefault.aspx%3Ftabindex%3D0%26tabid%3D1&event=noiframe" /></IFRAME>

In a nutshell, how does it work?
The code sample consists of a sample ASP.NET application called TechSupport and an assembly called IISCapture.  The TechSupport application is merely for demo purposes.  It contains 6 lines of code across two separate pages that are actually needed to implement this solution.  The first three are in askforhelp.aspx.  This creates a new trouble ticket, stores the current session information in the application cache assigned to the new trouble ticket, stores the last page the user was on, flags this user as being in tech support mode, and sets a reasonable expiration date/time on the cache so values don't get left out in memory.  Tech support mode will allow us to transfer updated session values, form element values, and just about anything else we feel is necessary to our tech support representative when the user next requests a page.
askforhelp.aspx
  string Url=Request.QueryString["techsupporturl"].ToString();
  IISCapture.TroubleTicket oTicket = new IISCapture.TroubleTicket();
  msSessionKey = oTicket.LoadNewSessionToApplication(System.Web.HttpContext.Current,Url);

The second is the technical support logon page itself.  In the sample application, the tech support rep simply needs to enter the trouble ticket number and click submit.  From there, we'll use the trouble ticket to find the user's session data, form element data, and anything else in the application cache, iterate through those values and assign them to the tech support rep, and then redirect them to the last page the user was on.  Your production application of this solution should include far more secure measures for support reps to sign in.
techsupportlogin.aspx
  IISCapture.TroubleTicket oTicket = new IISCapture.TroubleTicket();
  oTicket.LoadSessionFromApplication(System.Web.HttpContext.Current,this.TextBox1.Text);	
  oTicket.TransferToUrl(System.Web.HttpContext.Current);

Since these two pages are new to any production Web site, we've managed to implement this solution by only adding a hyperlink on all of the applicable Web pages and an entry in the web.config file to define the http module.  All of the magic happens in the external IISCapture assembly and its ability to intercept the http request.  The TroubleTicket class handles the data and the Log class handles the interception of the http request itself.
After first glance, you might think that this solution isn't workable in a web farm environment.  That's not entirely true.  The IISCapture.TroubleTicket.LoadSessionToApplication could be modified slightly to store the server's ip address in the GotoUrl instead of the domain name.  As long as you were utilizing Session Affinity (forcing each subsequent page request to route to the same server on the farm) and the tech support rep logged onto the same server (which you could determine and include in a logon link in your application), you'd be fine.  If this wasn't an option, then simply use a database to store the objects converted to byte arrays as your storage mechanism rather than the application cache I used in this prototype.
Before we take a look at the source code for the Log and TroubleTicket classes, let's briefly discuss a few of other uses for interception http requests:
 
1. User Specific Web Traffic Analysis
Four months after you'd deployed your web site, you receive a request for reports on what pages specific users or user groups access on your site.  Naturally, since you asked about this requirement in the development stage and they said it wasn't needed, you didn't include the ability track this information so you could meet your deadlines.
You could easily add an http module to intercept the page request and log it to a centralized database.  If you've included the UserID in your session variables or hidden form elements, you can snatch that out of the request and include them in your log.  If not, add some code in your login stored procedure to store the UserID and SessionID to a table in the centralized database.  You can join up the SessionID from your logged page to your UserID.  Little to no changes to your production Web site are needed and you've fulfilled the request.
2. Alter The Output Stream On The Fly
I've left code in the FilterHtml class for you to review.  The ReleaseRequestState event in the Log class has a commented out reference to overriding the Response.Filter class with the FilterHtml class.  The Filter.Html.Write method overrides the Response.Filter.Write method and allows you to modify the output stream to the browser.  The commented out code demonstrates how to do this.  There are a variety of reasons you might want to alter the stream.  One that came to mind for me was for security reasons.  As one more hurdle for hackers to overcome, you could put a search in the output stream for mission critical information about your web server, database server, or various other items a hacker might attempt to retrieve through methods such as SQL injection, malicious formatted requests, etc...  If you know your current Web site is vulnerable to these types of attacks, this could act as a good stop gap measure until you clean up your code.
The FilterHtml class could be adjusted to pass in a string reference byref that returns the html stream that is being sent to the browser for applications that monitor what is being sent back to the client.
3. Transferring Session Values To Other Sites
You could trap server.transfers to other web sites that you operate.  An http intercept module that exists on both site A and site B could react to domain name changes both when exiting and entering the site.  If a change is found, capture and store the session data in a centralized location (most likely a database), create a unique ticket and add it to the url, and execute the server.transfer to the desired page.  When site B gets the request, it loads up the session values and issues a response.redirect to the desired page minus the ticket in the Url.
 
When you step through the code sample, be sure to read the AskForHelp.aspx text for instructions.  Essentially, you'll start on the webform1.aspx page and click the button to sign in with whatever username you like.  It routes you to webform2.aspx where you can click the technical support link.  A new window pops up and assigns a trouble ticket to the user.  Copy/paste the url shown into a new browser session to log in as the tech support representative.  Put breakpoints in the various methods to enable you to step through the code and watch how it works.  Let's take a look at the Log and TroubleTicket classes to see how it works in its entirety:
Please take a moment to rate this article (opens new browser window).  Rate Article
 
Log.cs
using System;
using System.Web;
using System.Web.SessionState; 
using System.Data.SqlClient;
using System.Diagnostics; 
using System.Collections; 
using System.Collections.Specialized; 
using System.IO;
using System.Text;

namespace IISCapture
{
	 
  public class Log : IHttpModule
  {
		 
   HttpApplication HttpApp;
   HttpRequest HttpReq;
   HttpResponse HttpRes;
		
   public void Init(HttpApplication App)
   {
    // First event in the event chain
    App.BeginRequest += new EventHandler(OnBeginRequest);

    // Session state for the request isn't available until this event fires.
    App.AcquireRequestState += new EventHandler(AcquireRequestState);

    // If you want to alter the output stream on the fly, do it here.
    App.ReleaseRequestState  += new EventHandler(ReleaseRequestState);

   }
 
// Application Context Events


   private void OnBeginRequest(object sender, EventArgs eventArgs )
   {
    HttpApp = (HttpApplication)sender;
    HttpReq = HttpApp.Context.Request;
    HttpRes = HttpApp.Context.Response; 
		 
    try
    {   
				 
    }
    catch (Exception err) { ProcessError(err.Message); }
		 
   }

 
   private void AcquireRequestState(object sender, EventArgs eventArgs )
   {    
            
    string Ticket="";
    string Url="";
    try
    { 
      // Reload application cache with the user's session info and
      // also grab the last posted form elements and their values.
				
      if (this.IsSessionInTechSupportMode()==true)
      {
       IISCapture.TroubleTicket oTicket = new IISCapture.TroubleTicket(); 
       Ticket = HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportKey].ToString();
       Url = HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportUserLastPage].ToString();
       oTicket.LoadSessionToApplication(HttpApp.Context,Ticket,Url); 
       oTicket.LoadFormToApplication(HttpApp.Context,Ticket); 
       return;
      }

      // get the problem user's session info again.
				
      if (this.IsSessionATechSupportRep()==true)
      {
       IISCapture.TroubleTicket oTicket = new IISCapture.TroubleTicket(); 
       Ticket = HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportKey].ToString();
       oTicket.LoadSessionFromApplication(HttpApp.Context,Ticket); 
				   
      // Here is a sample of how to read the posted form values from the user
      // needing technical support:

      try
      {

        object[,] oValues = oTicket.GetFormFromApplication(HttpApp.Context,Ticket);
				
        for(int i=0;i<=oValues.GetUpperBound(1);i++)
        {
          Debug.Write(oValues[0,i].ToString() + ": ");
          Debug.WriteLine(oValues[1,i].ToString()); 
        }

      }
      catch { }
				
      }

     }
     catch (Exception err) { ProcessError(err.Message); }
   }

 


   private void ReleaseRequestState(object sender, EventArgs eventArgs )
   {
    // This event is a good choice for apply filters to the output
    // stream to the browser/client.  By overriding the filter, you
    // can modify the output stream after all the response.write's are
    // done and before it gets to the browser.
    // Just add your own business rules to determine whether a filter
    // should be applied at all.
    try
    {
      if(HttpRes.ContentType != "text/html") { return; }  
     // HttpRes.Filter = new IISCapture.FilterHtml(ref CaptureSession,HttpRes.Filter); 	 
    }
    catch (Exception err) { ProcessError(err.Message); }
   }


// Custom methods


 private bool IsSessionInTechSupportMode()
 {
  bool Ret = false;
  try
  {	
    if (HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportEnable] != null)
    {
     if (HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportEnable].ToString() =="1")
     {
        Ret=true;
     }
    } 
  }
  catch { }
  return Ret;
  }

  private bool IsSessionATechSupportRep()
  {
    bool Ret = false;
    try
    {
      if (HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportRep] != null)
       {
         if (HttpApp.Context.Session[IISCapture.TroubleTicket.TechSupportRep].ToString() =="1")
         {
            Ret=true;
         }
       } 
    }
    catch { }
    return Ret;
  }



  private void ProcessError(string ErrMsg)
  {
    HttpApp.Context.Response.Write(ErrMsg);
  }

 
  public void Dispose(){}
	}
}


TroubleTicket.cs
using System;
using System.Web;
using System.Collections;
using System.Text;
using System.IO; 
using System.Diagnostics; 

namespace IISCapture
{
	 
 public class TroubleTicket
 {
 
   public const string TechSupportUserLastPage = "TechSupportUserLastPage";
   public const string TechSupportEnable = "TechSupportEnable";
   public const string TechSupportKey = "TechSupportKey";
   public const string TechSupportRep = "TechSupportRep";
   public const string TechSupportCache = "TechSupportCache_";
   public const string TechSupportCacheSession = "Session_";
   public const string TechSupportCacheForm = "Form_";
 

   public TroubleTicket()
   {
	 
   }

 
   public void TransferToUrl(HttpContext oContext)
   {
     try
     {
      oContext.Response.Redirect(oContext.Session[TechSupportUserLastPage].ToString());  
     }
     catch (Exception) { throw; }
   }
 
   public string LoadNewSessionToApplication(HttpContext oContext,string GotoUrl)
   {
      string TroubleTicket="";
      try
      {
        TroubleTicket = System.Guid.NewGuid().ToString();
        LoadSessionToApplication(oContext,TroubleTicket,GotoUrl);
        LoadFormToApplication(oContext,TroubleTicket);
      }
      catch (Exception) { throw; }
      return TroubleTicket;
   }
 
   public void LoadSessionToApplication(HttpContext oContext,string TroubleTicket,string GotoUrl)
   {
			 
     try
     {
 
       oContext.Session[TechSupportUserLastPage] = GotoUrl; 
       oContext.Session[TechSupportKey] = TroubleTicket; 

       object[,] oValues = new object[2,oContext.Session.Keys.Count];

       for(int i=0;i<oContext.Session.Keys.Count;i++)
       {
          oValues[0,i] = oContext.Session.Keys[i].ToString();
          oValues[1,i] = oContext.Session[oContext.Session.Keys[i].ToString()];
       }

       oContext.Session[TechSupportEnable] = "1";

   if ((object[,])oContext.Cache[TechSupportCache + TechSupportCacheSession + TroubleTicket.Trim()] != null)
   {
     oContext.Cache.Remove(TechSupportCache + TechSupportCacheSession + TroubleTicket.Trim());
   }

   oContext.Cache.Insert(TechSupportCache + TechSupportCacheSession + TroubleTicket, _
         oValues,null,DateTime.MaxValue, TimeSpan.FromMinutes(10));
				 
   }
   catch (Exception) { throw; }
   return;
  }
 
  public void LoadFormToApplication(HttpContext oContext,string TroubleTicket)
  {
    bool Found=false;
    try
    {
 
      object[,] oValues = new object[2,oContext.Request.Form.Keys.Count];

      for(int i=0;i<oContext.Request.Form.Keys.Count;i++)
      {
        oValues[0,i] = oContext.Request.Form.Keys[i].ToString();
        oValues[1,i] = oContext.Request[oContext.Request.Form.Keys[i].ToString()];
        Found=true;
      }
 
   if ((object[,])oContext.Cache[TechSupportCache + TechSupportCacheForm + TroubleTicket.Trim()] != null)
   {
     if (Found==true)
     {
       oContext.Cache.Remove(TechSupportCache + TechSupportCacheForm + TroubleTicket.Trim());
     }
   }

   oContext.Cache.Insert(TechSupportCache + TechSupportCacheForm + _
        TroubleTicket,oValues,null,DateTime.MaxValue, TimeSpan.FromMinutes(10));
				 
   }
   catch (Exception) { throw; }
   return;
  }
 
 public string LoadSessionFromApplication(HttpContext oContext,string TroubleTicket)
 {
   string GotoUrl="";

   try
   {
			 
    object[,] oValues = (object[,])oContext.Cache[TechSupportCache + _
                         TechSupportCacheSession + TroubleTicket.Trim()];
 
    for(int i=0;i<=oValues.GetUpperBound(1);i++)
    {
      oContext.Session[oValues[0,i].ToString()] = oValues[1,i];	 
    }

    oContext.Session[TechSupportEnable] = "0";
    oContext.Session[TechSupportRep] = "1";
    GotoUrl = oContext.Session[TechSupportUserLastPage].ToString(); 
				
   }
   catch (Exception) { throw; }
   return GotoUrl;
 }
 
 public object[,] GetFormFromApplication(HttpContext oContext,string TroubleTicket)
 {
   object[,] oValues = null;

   try
   {
     oValues = (object[,])oContext.Cache[TechSupportCache + TechSupportCacheForm + TroubleTicket.Trim()];
   }
   catch (Exception) { throw; }
   return oValues;
  }
 
 }
}


Robbe Morris is a 2004 Microsoft MVP (Visual C#) and Senior Software Engineer (Decision Tools, TCO Schools, CIO Exp, and TVO) at Gartner in Maitland, FL.  He is a co-developer of EggHeadCafe.com which is hosted by his web site development and hosting company RobbeMorris.com Inc.  In his spare time, he also contributes political commentary and satire to GOP MessageBoard.com.
阅读更多
个人分类: WEBFORM
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭