// /r:"C:\Program Files\Microsoft Visual Studio 10.0\VSTSDB\Deploy\Microsoft.Data.Schema.ScriptDom.dll";"C:\Program Files\Microsoft Visual Studio 10.0\VSTSDB\Deploy\Microsoft.Data.Schema.ScriptDom.Sql.dll" namespace Microsoft.Security.Application.SecurityRuntimeEngine.PlugIns { using System; using System.Collections.Generic; using System.IO; using Data.Schema.ScriptDom; using Data.Schema.ScriptDom.Sql; public class SqlInjectionInspector //: IRequestInspector, IConfigurablePlugIn { /// <summary> /// Returns a value indicating if the specified input could be a SQL injection attempt. /// </summary> /// <param name="input">The input to check.</param> /// <returns>True if the input is a possible SQL injection attempt, otherwise false</returns> private static bool IsSqlInjectible(string input) { return DetectCompleteSql(input) || DetectPartialSql(input) || DetectLogicInjection(input); } /// <summary> /// Detects logic short circuit attempts. /// </summary> /// <param name="input">The input to check.</param> /// <returns>True if a logic short circuit has been detected, otherwise false.</returns> private static bool DetectLogicInjection(string input) { // First determine whether there are two or more instances of single quotes. // This could potentially be SQL that attempts to change logic in a SQL query. int firstSingleQuoteIndex = input.IndexOf("'", StringComparison.Ordinal); // If there is no single quote in the input, then logic injection attack is unlikely. if (firstSingleQuoteIndex == -1) { return false; } int lastSingleQuoteIndex = input.LastIndexOf("'", StringComparison.Ordinal); // If there is only one single quote in the input, then logic injection attack is unlikely. if (firstSingleQuoteIndex == lastSingleQuoteIndex) { return false; } /* At this point, there is more than one single quote in the input. * Strip out any quotes after the first instance and then pass it to the parser. * For example, consider the query: select * from users where username=’ ” + param + “ ‘ “. * * Input attack vectors: * (a) someText' or username='alias * (b) a' or 'a'='a * * This function is designed to catch these types of attacks. */ /* split the input into two sections. First variables contains everything from the * start to the first occurrence (including) of the single quote. * The second half contains everything after the first occurrence of the single quote. */ string firstHalf = input.Substring(0, firstSingleQuoteIndex + 1); string secondHalf = input.Substring(firstSingleQuoteIndex + 1); /* Strip out any single quotes */ secondHalf = secondHalf.Replace("'", string.Empty); string finalScrubbedInput = firstHalf + secondHalf; TSql100Parser parser = new TSql100Parser(false); IList<ParseError> errors; /* Try input with permutation. If input completes an invalid and partial SQL, * then there is potential for an attack. This will capture cases where * attackers attempt to escape out of valid SQL or modify logic. (i.e. OR 1=1) */ const string SqlCommandPrefix = "SELECT * FROM USERS WHERE ID='"; string completeSql = SqlCommandPrefix + finalScrubbedInput; using (StringReader sr = new StringReader(completeSql)) { parser.Parse(sr, out errors); } return errors == null || errors.Count <= 0; } /// <summary> /// Detects partial SQL command injection. /// </summary> /// <param name="input">The input to check.</param> /// <returns>True if a partial command has been detected, otherwise false.</returns> private static bool DetectPartialSql(string input) { // Only processing input greater than 2 characters if (input.Length < 3) { return false; } TSql100Parser parser = new TSql100Parser(false); IList<ParseError> errors; // Try input with permutation. If input completes an invalid and partial SQL, // then there is potential for an attack. This will capture cases where // attackers attempt to escape out of valid SQL or modify logic. (i.e. OR 1=1) const string StartSql = "SELECT * FROM USERS WHERE ID='"; string completedSql = StartSql + input; using (StringReader sr = new StringReader(completedSql)) { parser.Parse(sr, out errors); } return errors == null || errors.Count <= 0; } /// <summary> /// Detects a complete SQL command in the specified input. /// </summary> /// <param name="input">The input to check.</param> /// <returns>True if a logic short circuit has been detected, otherwise false.</returns> private static bool DetectCompleteSql(string input) { // Parser treats strings of length 1 or 2 as stored procedure calls. These will be skipped as they do not form complete SQL. input = input.Trim(); string[] numberOfTokens = input.Split(' '); if (numberOfTokens.Length < 3) { return false; } // Try input in raw format. Detects complete SQL. using (StringReader sr = new StringReader(input)) { TSql100Parser parser = new TSql100Parser(false); IList<ParseError> errors; parser.Parse(sr, out errors); return errors == null || errors.Count <= 0; } } } }