Monday, 19 November 2012

How to mitigate framesniffing with the X-Frame-Options header?


Understanding the Security Issue:
Imagine you have a page on your website, mypage1.aspx. This page can be hosted in an iframe and be shown on any pages and under any external domains and by anyone.

The problem is that when a page is hosted in an iframe, the parent page can steal the data which is communicated on the iframe page which means a decent attacker can benefit from this vulnerability.

Solution:
One possible solution to prevent these kind of security issues (doesn't work for old browsers) is to configure your IIS so that it only allows a page only being iframed in the domains you trust and prevent all other domains.

To restrict all external domains:

Put the below Field/Value pairs in the HTTPHeaders tab of your IIS Folder which contains your page:

X-Frame-Options = SAMEORIGIN
To allow an external domain:

X-Frame-Options = ALLOW-FROM http://google.com

Considerations:
  • Instead of adding the X-FRAME-OPTIONS on the site root, put it on each page which requires being protected.
  • For each page or folder, you can only specify one of the three main header values of "DENY, "ALLOW-FROM" or "SAMEORIGIN". You can't mix them unfortunately.
  • ALLOW-FROM does support only 1 origin, not multiple.
  • In IIS 6.0 you can put the header on each page needed but in IIS 7.0 it's not possible via IIS; the workaround for IIS 7.0 is via serverside code for the page.
  • Not all browsers support it. IE 8+ and some other browsers. More details: http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
More details:

Friday, 9 November 2012

How to use Recaptcha?

List of IP Addresses used: http://code.google.com/p/recaptcha/wiki/FirewallsAndRecaptcha

Create a public and private key for your domain: http://www.google.com/recaptcha

Then the below code manages everything you'd need not based on the Session attempts but Application attempts:

public class RecaptchaManager
 {
  #region Local variables

  private static volatile RecaptchaManager _instance;
  private static readonly object SyncRoot = new Object();
  private readonly Byte _maxNumberOfValidFailedAttempts;

  private readonly Byte _durationInMinutes;
  private readonly int _cleanUpMaxSize;
  private readonly string _privateKey;
  private readonly string _publicKey;
  private readonly List _result = new List();

  /// 
  /// Be careful where and how the locks are used to avoid deadlocks or inter locks.
  /// 
  private readonly Object _lockObject = new object();

  #endregion

  #region Constructors and class initialization

  private RecaptchaManager()
  {
   if (!Byte.TryParse(SettingHelper.Instance["Recaptcha.ValidFailedAttempts.MaxNumber"], out _maxNumberOfValidFailedAttempts))
   {
    throw new ApplicationException("Invalid or non-existent config key: Recaptcha.ValidFailedAttempts.MaxNumber");
   }

   if (!Byte.TryParse(SettingHelper.Instance["Recaptcha.ValidFailedAttempts.DurationInMinutes"], out _durationInMinutes))
   {
    throw new ApplicationException("Invalid or non-existent config key: Recaptcha.ValidFailedAttempts.DurationInMinutes");
   }

   if (!int.TryParse(SettingHelper.Instance["Recaptcha.CleanUp.MaxSize"], out _cleanUpMaxSize))
   {
    throw new ApplicationException("Invalid or non-existent config key: Recaptcha.CleanUp.MaxSize");
   }

   // private key
   this._privateKey = SettingHelper.Instance["Recaptcha.PrivateKey"];

   if (string.IsNullOrWhiteSpace(this._privateKey))
   {
    throw new ApplicationException("Invalid or non-existent config key: Recaptcha.PrivateKey");
   }

   this._publicKey = SettingHelper.Instance["Recaptcha.PublicKey"];

   if (string.IsNullOrWhiteSpace(this._publicKey))
   {
    throw new ApplicationException("Invalid or non-existent config key: Recaptcha.PublicKey");
   }
  }

  #endregion

  #region Properties

  public static RecaptchaManager Instance
  {
   get
   {
    if (_instance == null)
    {
     lock (SyncRoot)
     {
      if (_instance == null)
       _instance = new RecaptchaManager();
     }
    }

    return _instance;
   }
  }

  public string PublicKey
  {
   get { return this._publicKey; }
  }

  #endregion

  #region public Methods

  public void AddFailedLoginAttempt()
  {
   // we may want to customize these
   addFailedLoginOrRegistrationAttempt();
  }

  public void AddFailedRegistrationAttempt()
  {
   // we may want to customize these
   addFailedLoginOrRegistrationAttempt();
  }

  public bool ShouldRequireRecaptcha()
  {   
   return getNumberOfFailedAttemptsDuringThePeriod() > this._maxNumberOfValidFailedAttempts;
  }

  public bool IsRecaptchaAvailableOnPage()
  {
   // if this field exists then the Recaptcha is available
   string challengeValue = Utils.GetStringForm("recaptcha_challenge_field", null);
   
   return challengeValue != null;
  }

  public bool IsInputValueVerified()
  {
   string challengeValue = Utils.GetStringForm("recaptcha_challenge_field", null);
   string responseValue = Utils.GetStringForm("recaptcha_response_field", null);
   
   bool result = false;

   try
   {
    var captchaValidtor = new RecaptchaValidator()
    {
     // put it in web.config
     PrivateKey = this._privateKey,

     RemoteIP = HttpContext.Current.Request.UserHostAddress,

     Challenge = challengeValue,

     Response = responseValue
    };

    result = captchaValidtor.Validate().IsValid;
   }
   catch { }

   return result;
  }

  #endregion

  #region Private functions
  
  private void addFailedLoginOrRegistrationAttempt()
  {
   lock (this._lockObject)
   {
    if (this._result.Count > this._cleanUpMaxSize)
    {
     this.removeOldData();
    }

    this._result.Add(DateTime.Now);
   }
  }

  /// 
  /// Removes the old data.
  /// 
  private void removeOldData()
  {
   DateTime startDateTime = getStartDateTime();
   this._result.RemoveAll(item => item < startDateTime);
  }

  private int getNumberOfFailedAttemptsDuringThePeriod()
  {
   DateTime startDateTime = getStartDateTime();

   lock (this._lockObject)
   {
    return this._result.Count(failedAttemp => failedAttemp > startDateTime);
   }
  }

  private DateTime getStartDateTime()
  {
   return DateTime.Now.AddMinutes(-this._durationInMinutes);
  }

  #endregion
 }