// /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;
}
}
}
}