public class PortalSecurity
{
#region FilterFlag enum
///-----------------------------------------------------------------------------
/// <summary>
/// The FilterFlag enum determines which filters are applied by the InputFilter
/// function. The Flags attribute allows the user to include multiple
/// enumerated values in a single variable by OR'ing the individual values
/// together.
/// </summary>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000120, #000121
/// </history>
///-----------------------------------------------------------------------------
[Flags]
public enum FilterFlag
{
MultiLine = 1,
NoMarkup = 2,
NoScripting = 4,
NoSQL = 8,
NoAngleBrackets = 16,
NoProfanity =32
}
/// <summary>
/// Determines the configuration source for the remove and replace functions
/// </summary>
public enum ConfigType
{
ListController,
ExternalFile
}
/// <summary>
/// determines whether to use system (host) list, portal specific list, or combine both
/// At present only supported by ConfigType.ListController
/// </summary>
public enum FilterScope
{
SystemList,
PortalList,
SystemAndPortalList
}
#endregion
#region Private Methods
///-----------------------------------------------------------------------------
/// <summary>
/// This function uses Regex search strings to remove HTML tags which are
/// targeted in Cross-site scripting (XSS) attacks. This function will evolve
/// to provide more robust checking as additional holes are found.
/// </summary>
/// <param name="strInput">This is the string to be filtered</param>
/// <returns>Filtered UserInput</returns>
/// <remarks>
/// This is a private function that is used internally by the FormatDisableScripting function
/// </remarks>
/// <history>
/// [cathal] 3/06/2007 Created
/// </history>
///-----------------------------------------------------------------------------
private string FilterStrings(string strInput)
{
//setup up list of search terms as items may be used twice
string TempInput = strInput;
var listStrings = new List<string>();
listStrings.Add("<script[^>]*>.*?</script[^><]*>");
listStrings.Add("<script");
listStrings.Add("<input[^>]*>.*?</input[^><]*>");
listStrings.Add("<object[^>]*>.*?</object[^><]*>");
listStrings.Add("<embed[^>]*>.*?</embed[^><]*>");
listStrings.Add("<applet[^>]*>.*?</applet[^><]*>");
listStrings.Add("<form[^>]*>.*?</form[^><]*>");
listStrings.Add("<option[^>]*>.*?</option[^><]*>");
listStrings.Add("<select[^>]*>.*?</select[^><]*>");
listStrings.Add("<iframe[^>]*>.*?</iframe[^><]*>");
listStrings.Add("<iframe.*?<");
listStrings.Add("<iframe.*?");
listStrings.Add("<ilayer[^>]*>.*?</ilayer[^><]*>");
listStrings.Add("<form[^>]*>");
listStrings.Add("</form[^><]*>");
listStrings.Add("onerror");
listStrings.Add("onmouseover");
listStrings.Add("javascript:");
listStrings.Add("vbscript:");
listStrings.Add("alert[\\s( )]*\\([\\s( )]*'?[\\s( )]*[\"(")]?");
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
string strReplacement = " ";
//check if text contains encoded angle brackets, if it does it we decode it to check the plain text
if (TempInput.Contains(">") && TempInput.Contains("<"))
{
//text is encoded, so decode and try again
TempInput = HttpContext.Current.Server.HtmlDecode(TempInput);
foreach (string s in listStrings)
{
TempInput = Regex.Replace(TempInput, s, strReplacement, options);
}
//Re-encode
TempInput = HttpContext.Current.Server.HtmlEncode(TempInput);
}
else
{
foreach (string s in listStrings)
{
TempInput = Regex.Replace(TempInput, s, strReplacement, options);
}
}
return TempInput;
}
///-----------------------------------------------------------------------------
/// <summary>
/// This function uses Regex search strings to remove HTML tags which are
/// targeted in Cross-site scripting (XSS) attacks. This function will evolve
/// to provide more robust checking as additional holes are found.
/// </summary>
/// <param name="strInput">This is the string to be filtered</param>
/// <returns>Filtered UserInput</returns>
/// <remarks>
/// This is a private function that is used internally by the InputFilter function
/// </remarks>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000120
/// [cathal] 3/06/2007 Added check for encoded content
/// </history>
///-----------------------------------------------------------------------------
private string FormatDisableScripting(string strInput)
{
string TempInput = strInput;
TempInput = FilterStrings(TempInput);
return TempInput;
}
///-----------------------------------------------------------------------------
/// <summary>
/// This filter removes angle brackets i.e.
/// </summary>
/// <param name="strInput">This is the string to be filtered</param>
/// <returns>Filtered UserInput</returns>
/// <remarks>
/// This is a private function that is used internally by the InputFilter function
/// </remarks>
/// <history>
/// [Cathal] 6/1/2006 Created to fufill client request
/// </history>
///-----------------------------------------------------------------------------
private string FormatAngleBrackets(string strInput)
{
string TempInput = strInput.Replace("<", "");
TempInput = TempInput.Replace(">", "");
return TempInput;
}
///-----------------------------------------------------------------------------
/// <summary>
/// This filter removes CrLf characters and inserts br
/// </summary>
/// <param name="strInput">This is the string to be filtered</param>
/// <returns>Filtered UserInput</returns>
/// <remarks>
/// This is a private function that is used internally by the InputFilter function
/// </remarks>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000120
/// </history>
///-----------------------------------------------------------------------------
private string FormatMultiLine(string strInput)
{
string TempInput = strInput.Replace(Environment.NewLine, "<br />");
return TempInput.Replace("\r", "<br />");
}
///-----------------------------------------------------------------------------
/// <summary>
/// This function verifies raw SQL statements to prevent SQL injection attacks
/// and replaces a similar function (PreventSQLInjection) from the Common.Globals.vb module
/// </summary>
/// <param name="strSQL">This is the string to be filtered</param>
/// <returns>Filtered UserInput</returns>
/// <remarks>
/// This is a private function that is used internally by the InputFilter function
/// </remarks>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000121
/// [Tom Lucas] 3/8/2004 Fixed Bug #000114 (Aardvark)
/// 8/5/2009 added additional strings and performance tweak
/// </history>
///-----------------------------------------------------------------------------
private string FormatRemoveSQL(string strSQL)
{
const string BadStatementExpression = ";|--|create|drop|select|insert|delete|update|union|sp_|xp_|exec|/\\*.*\\*/|declare";
return Regex.Replace(strSQL, BadStatementExpression, " ", RegexOptions.IgnoreCase | RegexOptions.Compiled).Replace("'", "''");
}
///-----------------------------------------------------------------------------
/// <summary>
/// This function determines if the Input string contains any markup.
/// </summary>
/// <param name="strInput">This is the string to be checked</param>
/// <returns>True if string contains Markup tag(s)</returns>
/// <remarks>
/// This is a private function that is used internally by the InputFilter function
/// </remarks>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000120
/// </history>
///-----------------------------------------------------------------------------
private bool IncludesMarkup(string strInput)
{
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
string strPattern = "<[^<>]*>";
return Regex.IsMatch(strInput, strPattern, options);
}
#endregion
#region Public Methods
///-----------------------------------------------------------------------------
/// <summary>
/// This function converts a byte array to a hex string
/// </summary>
/// <param name="bytes">An array of bytes</param>
/// <returns>A string representing the hex converted value</returns>
/// <remarks>
/// This is a private function that is used internally by the CreateKey function
/// </remarks>
/// <history>
/// </history>
///-----------------------------------------------------------------------------
private string BytesToHexString(byte[] bytes)
{
var hexString = new StringBuilder(64);
int counter;
for (counter = 0; counter <= bytes.Length - 1; counter++)
{
hexString.Append(String.Format("{0:X2}", bytes[counter]));
}
return hexString.ToString();
}
///-----------------------------------------------------------------------------
/// <summary>
/// This function creates a random key
/// </summary>
/// <param name="numBytes">This is the number of bytes for the key</param>
/// <returns>A random string</returns>
/// <remarks>
/// This is a public function used for generating SHA1 keys
/// </remarks>
/// <history>
/// </history>
///-----------------------------------------------------------------------------
public string CreateKey(int numBytes)
{
var rng = new RNGCryptoServiceProvider();
var buff = new byte[numBytes];
rng.GetBytes(buff);
return BytesToHexString(buff);
}
public string EncryptString(string message, string passphrase)
{
byte[] results;
UTF8Encoding utf8 = new UTF8Encoding();
//hash the passphrase using MD5 to create 128bit byte array
MD5CryptoServiceProvider hashProvider = new MD5CryptoServiceProvider();
byte[] tdesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));
TripleDESCryptoServiceProvider tdesAlgorithm = new TripleDESCryptoServiceProvider();
tdesAlgorithm.Key = tdesKey;
tdesAlgorithm.Mode = CipherMode.ECB;
tdesAlgorithm.Padding = PaddingMode.PKCS7;
byte[] dataToEncrypt = utf8.GetBytes(message);
try
{
ICryptoTransform encryptor = tdesAlgorithm.CreateEncryptor();
results = encryptor.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
tdesAlgorithm.Clear();
hashProvider.Clear();
}
//Return the encrypted string as a base64 encoded string
return Convert.ToBase64String(results);
}
public string DecryptString(string message, string passphrase)
{
byte[] results;
UTF8Encoding utf8 = new UTF8Encoding();
//hash the passphrase using MD5 to create 128bit byte array
MD5CryptoServiceProvider hashProvider = new MD5CryptoServiceProvider();
byte[] tdesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));
TripleDESCryptoServiceProvider tdesAlgorithm = new TripleDESCryptoServiceProvider();
tdesAlgorithm.Key = tdesKey;
tdesAlgorithm.Mode = CipherMode.ECB;
tdesAlgorithm.Padding = PaddingMode.PKCS7;
byte[] dataToDecrypt = Convert.FromBase64String(message);
try
{
ICryptoTransform decryptor = tdesAlgorithm.CreateDecryptor();
results = decryptor.TransformFinalBlock(dataToDecrypt, 0, dataToDecrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
tdesAlgorithm.Clear();
hashProvider.Clear();
}
return utf8.GetString(results);
}
public string Decrypt(string strKey, string strData)
{
if (String.IsNullOrEmpty(strData))
{
return "";
}
string strValue = "";
if (!String.IsNullOrEmpty(strKey))
{
//convert key to 16 characters for simplicity
if (strKey.Length < 16)
{
strKey = strKey + "XXXXXXXXXXXXXXXX".Substring(0, 16 - strKey.Length);
}
else
{
strKey = strKey.Substring(0, 16);
}
//create encryption keys
byte[] byteKey = Encoding.UTF8.GetBytes(strKey.Substring(0, 8));
byte[] byteVector = Encoding.UTF8.GetBytes(strKey.Substring(strKey.Length - 8, 8));
//convert data to byte array and Base64 decode
var byteData = new byte[strData.Length];
try
{
byteData = Convert.FromBase64String(strData);
}
catch //invalid length
{
strValue = strData;
}
if (String.IsNullOrEmpty(strValue))
{
try
{
//decrypt
var objDES = new DESCryptoServiceProvider();
var objMemoryStream = new MemoryStream();
var objCryptoStream = new CryptoStream(objMemoryStream, objDES.CreateDecryptor(byteKey, byteVector), CryptoStreamMode.Write);
objCryptoStream.Write(byteData, 0, byteData.Length);
objCryptoStream.FlushFinalBlock();
//convert to string
Encoding objEncoding = Encoding.UTF8;
strValue = objEncoding.GetString(objMemoryStream.ToArray());
}
catch //decryption error
{
strValue = "";
}
}
}
else
{
strValue = strData;
}
return strValue;
}
public string Encrypt(string strKey, string strData)
{
string strValue = "";
if (!String.IsNullOrEmpty(strKey))
{
//convert key to 16 characters for simplicity
if (strKey.Length < 16)
{
strKey = strKey + "XXXXXXXXXXXXXXXX".Substring(0, 16 - strKey.Length);
}
else
{
strKey = strKey.Substring(0, 16);
}
//create encryption keys
byte[] byteKey = Encoding.UTF8.GetBytes(strKey.Substring(0, 8));
byte[] byteVector = Encoding.UTF8.GetBytes(strKey.Substring(strKey.Length - 8, 8));
//convert data to byte array
byte[] byteData = Encoding.UTF8.GetBytes(strData);
//encrypt
var objDES = new DESCryptoServiceProvider();
var objMemoryStream = new MemoryStream();
var objCryptoStream = new CryptoStream(objMemoryStream, objDES.CreateEncryptor(byteKey, byteVector), CryptoStreamMode.Write);
objCryptoStream.Write(byteData, 0, byteData.Length);
objCryptoStream.FlushFinalBlock();
//convert to string and Base64 encode
strValue = Convert.ToBase64String(objMemoryStream.ToArray());
}
else
{
strValue = strData;
}
return strValue;
}
///-----------------------------------------------------------------------------
/// <summary>
/// This function applies security filtering to the UserInput string.
/// </summary>
/// <param name="UserInput">This is the string to be filtered</param>
/// <param name="FilterType">Flags which designate the filters to be applied</param>
/// <returns>Filtered UserInput</returns>
/// <history>
/// [Joe Brinkman] 8/15/2003 Created Bug #000120, #000121
/// </history>
///-----------------------------------------------------------------------------
public string InputFilter(string UserInput, FilterFlag FilterType)
{
if (UserInput == null)
{
return "";
}
string TempInput = UserInput;
if ((FilterType & FilterFlag.NoAngleBrackets) == FilterFlag.NoAngleBrackets)
{
bool RemoveAngleBrackets;
if (Config.GetSetting("RemoveAngleBrackets") == null)
{
RemoveAngleBrackets = false;
}
else
{
RemoveAngleBrackets = Boolean.Parse(Config.GetSetting("RemoveAngleBrackets"));
}
if (RemoveAngleBrackets)
{
TempInput = FormatAngleBrackets(TempInput);
}
}
if ((FilterType & FilterFlag.NoSQL) == FilterFlag.NoSQL)
{
TempInput = FormatRemoveSQL(TempInput);
}
else
{
if ((FilterType & FilterFlag.NoMarkup) == FilterFlag.NoMarkup && IncludesMarkup(TempInput))
{
TempInput = HttpUtility.HtmlEncode(TempInput);
}
if ((FilterType & FilterFlag.NoScripting) == FilterFlag.NoScripting)
{
TempInput = FormatDisableScripting(TempInput);
}
if ((FilterType & FilterFlag.MultiLine) == FilterFlag.MultiLine)
{
TempInput = FormatMultiLine(TempInput);
}
}
if ((FilterType & FilterFlag.NoProfanity)== FilterFlag.NoProfanity)
{
Remove(TempInput, ConfigType.ListController, "ProfanityFilter", FilterScope.SystemAndPortalList);
}
return TempInput;
}
public string Replace(string userInput, ConfigType configType, string configSource, FilterScope replaceRemoveScope)
{
//function not required for initial global inputfilter version
return userInput;
}
public string Remove(string userInput, ConfigType configType, string configSource, FilterScope replaceRemoveScope)
{
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
string strReplacement = "";
var listController = new ListController();
IEnumerable<ListEntryInfo> listEntryHostInfos = listController.GetListEntryInfoItems("ProfanityFilter", "", Null.NullInteger);
PortalSettings settings = PortalController.GetCurrentPortalSettings();
IEnumerable<ListEntryInfo> listEntryPortalInfos = listController.GetListEntryInfoItems("ProfanityFilter", "", settings.PortalId);
switch (replaceRemoveScope)
{
case FilterScope.SystemList:
userInput = listEntryHostInfos.Aggregate(userInput, (current, removeItem) => Regex.Replace(current, removeItem.Value, strReplacement, options));
break;
case FilterScope.SystemAndPortalList:
userInput = listEntryHostInfos.Aggregate(userInput, (current, removeItem) => Regex.Replace(current, removeItem.Value, strReplacement, options));
userInput=listEntryPortalInfos.Aggregate(userInput, (current, removeItem) => Regex.Replace(current, removeItem.Value, strReplacement, options));
break;
case FilterScope.PortalList:
userInput=listEntryPortalInfos.Aggregate(userInput, (current, removeItem) => Regex.Replace(current, removeItem.Value, strReplacement, options));
break;
}
return userInput;
}
public void SignIn(UserInfo user, bool createPersistentCookie)
{
if (PortalController.IsMemberOfPortalGroup(user.PortalID) || createPersistentCookie)
{
//Create a custom auth cookie
//first, create the authentication ticket
FormsAuthenticationTicket authenticationTicket = createPersistentCookie
? new FormsAuthenticationTicket(user.Username, true, Config.GetPersistentCookieTimeout())
: new FormsAuthenticationTicket(user.Username, false, Config.GetAuthCookieTimeout());
//encrypt it
var encryptedAuthTicket = FormsAuthentication.Encrypt(authenticationTicket);
//Create a new Cookie
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedAuthTicket)
{
Expires = authenticationTicket.Expiration,
Domain = GetCookieDomain(user.PortalID),
Path = FormsAuthentication.FormsCookiePath,
Secure = FormsAuthentication.RequireSSL
};
HttpContext.Current.Response.Cookies.Set(authCookie);
if (PortalController.IsMemberOfPortalGroup(user.PortalID))
{
var domain = GetCookieDomain(user.PortalID);
var siteGroupCookie = new HttpCookie("SiteGroup", domain)
{
Expires = authenticationTicket.Expiration,
Domain = domain,
Path = FormsAuthentication.FormsCookiePath,
Secure = FormsAuthentication.RequireSSL
};
HttpContext.Current.Response.Cookies.Set(siteGroupCookie);
}
}
else
{
FormsAuthentication.SetAuthCookie(user.Username, false);
}
}
public void SignOut()
{
//Log User Off from Cookie Authentication System
var domainCookie = HttpContext.Current.Request.Cookies["SiteGroup"];
if (domainCookie == null)
{
//Forms Authentication's Logout
FormsAuthentication.SignOut();
}
else
{
//clear custom domain cookie
var domain = domainCookie.Value;
//Create a new Cookie
string str = String.Empty;
if (HttpContext.Current.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
{
str = "NoCookie";
}
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, str)
{
Expires = new DateTime(1999, 1, 1),
Domain = domain,
Path = FormsAuthentication.FormsCookiePath,
Secure = FormsAuthentication.RequireSSL
};
HttpContext.Current.Response.Cookies.Set(authCookie);
var siteGroupCookie = new HttpCookie("SiteGroup", str)
{
Expires = new DateTime(1999, 1, 1),
Domain = domain,
Path = FormsAuthentication.FormsCookiePath,
Secure = FormsAuthentication.RequireSSL
};
HttpContext.Current.Response.Cookies.Set(siteGroupCookie);
}
//Remove current userinfo from context items
HttpContext.Current.Items.Remove("UserInfo");
//remove language cookie
HttpContext.Current.Response.Cookies["language"].Value = "";
//remove authentication type cookie
HttpContext.Current.Response.Cookies["authentication"].Value = "";
//expire cookies
HttpContext.Current.Response.Cookies["portalaliasid"].Value = null;
HttpContext.Current.Response.Cookies["portalaliasid"].Path = "/";
HttpContext.Current.Response.Cookies["portalaliasid"].Expires = DateTime.Now.AddYears(-30);
HttpContext.Current.Response.Cookies["portalroles"].Value = null;
HttpContext.Current.Response.Cookies["portalroles"].Path = "/";
HttpContext.Current.Response.Cookies["portalroles"].Expires = DateTime.Now.AddYears(-30);
}
#endregion
#region Public Shared/Static Methods
public static void ClearRoles()
{
HttpContext.Current.Response.Cookies["portalroles"].Value = null;
HttpContext.Current.Response.Cookies["portalroles"].Path = "/";
HttpContext.Current.Response.Cookies["portalroles"].Expires = DateTime.Now.AddYears(-30);
}
public static void ForceSecureConnection()
{
//get current url
string URL = HttpContext.Current.Request.Url.ToString();
//if unsecure connection
if (URL.StartsWith("http://"))
{
//switch to secure connection
URL = URL.Replace("http://", "https://");
//append ssl parameter to querystring to indicate secure connection processing has already occurred
if (URL.IndexOf("?") == -1)
{
URL = URL + "?ssl=1";
}
else
{
URL = URL + "&ssl=1";
}
//redirect to secure connection
HttpContext.Current.Response.Redirect(URL, true);
}
}
public static string GetCookieDomain(int portalId)
{
string cookieDomain = String.Empty;
if (PortalController.IsMemberOfPortalGroup(portalId))
{
//set cookie domain for portal group
var groupController = new PortalGroupController();
var group = groupController.GetPortalGroups()
.Where(p => p.MasterPortalId == PortalController.GetEffectivePortalId(portalId))
.SingleOrDefault();
if (@group != null
&& !string.IsNullOrEmpty(@group.AuthenticationDomain)
&& PortalSettings.Current.PortalAlias.HTTPAlias.Contains(@group.AuthenticationDomain))
{
cookieDomain = @group.AuthenticationDomain;
}
if (String.IsNullOrEmpty(cookieDomain))
{
cookieDomain = FormsAuthentication.CookieDomain;
}
}
else
{
//set cookie domain to be consistent with domain specification in web.config
cookieDomain = FormsAuthentication.CookieDomain;
}
return cookieDomain;
}
public static bool IsInRole(string role)
{
UserInfo objUserInfo = UserController.GetCurrentUserInfo();
HttpContext context = HttpContext.Current;
if ((!String.IsNullOrEmpty(role) && role != null && ((context.Request.IsAuthenticated == false && role == Globals.glbRoleUnauthUserName))))
{
return true;
}
else
{
return objUserInfo.IsInRole(role);
}
}
public static bool IsInRoles(string roles)
{
UserInfo objUserInfo = UserController.GetCurrentUserInfo();
//super user always has full access
bool blnIsInRoles = objUserInfo.IsSuperUser;
if (!blnIsInRoles)
{
if (roles != null)
{
HttpContext context = HttpContext.Current;
//permissions strings are encoded with Deny permissions at the beginning and Grant permissions at the end for optimal performance
foreach (string role in roles.Split(new[] {';'}))
{
if (!String.IsNullOrEmpty(role))
{
//Deny permission
if (role.StartsWith("!"))
{
//Portal Admin cannot be denied from his/her portal (so ignore deny permissions if user is portal admin)
PortalSettings settings = PortalController.GetCurrentPortalSettings();
if (!(settings.PortalId == objUserInfo.PortalID && settings.AdministratorId == objUserInfo.UserID))
{
string denyRole = role.Replace("!", "");
if (((context.Request.IsAuthenticated == false && denyRole == Globals.glbRoleUnauthUserName) || denyRole == Globals.glbRoleAllUsersName ||
objUserInfo.IsInRole(denyRole)))
{
blnIsInRoles = false;
break;
}
}
}
else //Grant permission
{
if (((context.Request.IsAuthenticated == false && role == Globals.glbRoleUnauthUserName) || role == Globals.glbRoleAllUsersName || objUserInfo.IsInRole(role)))
{
blnIsInRoles = true;
break;
}
}
}
}
}
}
return blnIsInRoles;
}
#endregion
}