in src/Sarif.Converters/ContrastSecurityConverter.cs [578:1150]
private Result ConstructFormsWithoutAutocompletePreventionResult(ContrastLogReader.Context context)
{
// The maximum length of a snippet that Contrast Security embeds in their HTML file.
// If the actual <form> element is longer than this, they tack on an ellipsis, which
// we don't want to include in the SARIF file.
const int MaxSnippetLength = 1000;
// autocomplete-missing : Forms Without Autocomplete Prevention
// <properties name="/webgoat/Content/EncryptVSEncode.aspx">{"html":"\u003cform name\u003d\"aspnetForm\"
var locations = new List<Location>();
IDictionary<string, string> properties = context.Properties;
foreach (string key in properties.Keys)
{
if (KeyIsReservedPropertyName(key)) { continue; }
string jsonValue = properties[key];
JObject root = JObject.Parse(jsonValue);
string snippet = root["html"].Value<string>();
int snippetLength = snippet.Length;
if (snippetLength > MaxSnippetLength)
{
snippet = snippet.Substring(0, MaxSnippetLength);
snippetLength = MaxSnippetLength;
}
locations.Add(new Location
{
PhysicalLocation = new PhysicalLocation
{
ArtifactLocation = new ArtifactLocation
{
UriBaseId = SiteRootUriBaseIdName,
Uri = new Uri(key, UriKind.RelativeOrAbsolute)
},
Region = new Region
{
CharOffset = 0, // Unfortunately the XML doesn't actually tell us this, but SARIF requires it.
CharLength = snippetLength,
Snippet = new ArtifactContent
{
Text = snippet
}
}
}
});
}
string pageCount = locations.Count.ToString();
string examplePage = locations[0].PhysicalLocation.ArtifactLocation.Uri.OriginalString;
Result result = CreateResultCore(context);
result.Locations = locations;
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{
pageCount, //'{0}' pages contain a <form> element that do
examplePage, //not have 'autocomplete' set to 'off'; e.g. '{1}'.
}
};
return result;
}
private bool KeyIsReservedPropertyName(string key)
{
return
key == "platform" ||
key == "webforms-page" ||
key == "route-signature";
}
private Result ConstructHttpOnlyCookieFlagDisabledResult(ContrastLogReader.Context context)
{
// http-only-disabled : HttpOnly Cookie Flag Disabled
// default : The configuration in '{0}' had 'httpOnlyCookies' set to 'false' in an <httpCookies> section.
// <properties name="path">\web.config</properties>
// <properties name = "snippet" >35: </compilation>
// 36: <httpCookies httpOnlyCookies="false"/>
// 37: <!--show detailed error messages -->
IDictionary<string, string> properties = context.Properties;
string path = properties[nameof(path)];
string snippet = properties[nameof(snippet)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(path, CreateRegion(snippet))
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The configuration in
path // '{0}' had 'httpOnlyCookies' set to 'false' in an <httpCookies> section.
}
};
return result;
}
private Result ConstructInsecureEncryptionAlgorithmsResult(ContrastLogReader.Context context)
{
// crypto-bad-cyphers : Insecure Encryption Algorithms
// default : '{0}' obtained a handle to the cryptographically insecure '{1}' algorithm.
return ConstructNotImplementedRuleResult(context.RuleId);
}
private Result ConstructOverlyLongSessionTimeoutResult(ContrastLogReader.Context context)
{
// session-timeout : Overly Long Session Timeout
// <properties name="path">\web.config</properties>
// <properties name="section">sessionState</properties>
// <properties name="snippet">52: <trace enabled="false" ...
IDictionary<string, string> properties = context.Properties;
string path = properties[nameof(path)];
string section = properties[nameof(section)];
string snippet = properties[nameof(snippet)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(path, CreateRegion(snippet)),
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The configuration in the
section, // <{0}> section of
path // '{1}' specified a session timeout value greater than 30 minutes.
}
};
return result;
}
private Result ConstructPathTraversalResult(ContrastLogReader.Context context)
{
// path-traversal : Path Traversal
// default : An attacker-controlled path traversal was observed from '{0}' on page '{1}'.
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
LogicalLocation = new LogicalLocation
{
FullyQualifiedName = GetUserCodeLocation(context.MethodEvent.Stack)
}
}
};
string page = context.RequestTarget;
string sources = BuildSourcesString(context.Sources);
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // An attacker-controlled path traversal was observed from
sources, // '{0}' on
page // page '{1}'.
}
};
return result;
}
private Result ConstructRequestValidationDisabledResult(ContrastLogReader.Context context)
{
// request-validation-disabled : Request Validation Disabled
// default : The web page '{0}' had 'ValidateRequest' set to 'false' in the page directive. Request Validation helps prevent several types of attacks including XSS by detecting potentially dangerous character sequences. An exception is thrown by the framework when a potentially dangerous character sequence is encountered. This exception returns an error page to the user and prevents the application from processing the request. An attacker can submit malicious data to the application that may be processed without further input validation. This malicious data could contain XSS or other injection attacks that may have been prevented by ASP.NET request validation. Note that request validation does not provide 100% protection against XSS or other attacks and should be thought of as a defense-in-depth measure.
// <properties name="aspx">\WebGoatCoins\ProductDetails.aspx</properties>
// <properties name="snippet">1: <%@ Page Title="" Language="C#" ValidateRequest="false" ... %></properties>
IDictionary<string, string> properties = context.Properties;
string aspx = properties[nameof(aspx)];
string snippet = properties[nameof(snippet)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(aspx, CreateRegion(snippet)),
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The web page
aspx // '{0}' had 'ValidateRequest' set to 'false' in the page directive. ...
}
};
return result;
}
private Result ConstructRequestValidationModeDisabledResult(ContrastLogReader.Context context)
{
// request-validation-control-disabled : Request Validation Mode Disabled
// default : A control on the web page '{0}' had 'ValidateRequestMode' set to 'Disabled'. Request Validation helps prevent several types of attacks including XSS by detecting potentially dangerous character sequences. An exception is thrown by the framework when a potentially dangerous character sequence is encountered. This exception returns an error page to the user and prevents the application from processing the request. An attacker can submit malicious data to the application that may be processed without further input validation. This malicious data could contain XSS or other injection attacks that may have been prevented by ASP.NET request validation. Note that request validation does not provide 100% protection against XSS or other attacks and should be thought of as a defense-in-depth measure.
return ConstructNotImplementedRuleResult(context.RuleId);
}
private Result ConstructSessionCookieHasNoSecureFlagResult(ContrastLogReader.Context context)
{
// secure-flag-missing : Session Cookie Has No 'secure' Flag
// default : The value of the HttpCookie for the cookie '{0}' did not contain the 'secure' flag; the value observed was '{1}'.
// <properties name="cookieName">ASP.NET_SessionId</properties>
// They also use the "evidence" element to hold the text of the cookie.
IDictionary<string, string> properties = context.Properties;
string cookieName = properties[nameof(cookieName)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(context.RequestTarget)
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The value of the HttpCookie for the cookie
cookieName, // ''{0}' did not contain the 'secure' flag;
context.Evidence // the value observed was '{1}'.
}
};
return result;
}
private Result ConstructSessionRewritingResult(ContrastLogReader.Context context)
{
// session-rewriting : Session Rewriting
// default : The configuration in the {0} section of '{1}' has 'UseCookies' set to a value other than 'cookieless'. As a result, the session ID (which is as good as a username and password) is logged to browser history, server logs and proxy logs. More serious, session rewriting can enable session fixcation attacks, in which an attacker causes a victim to use a well-known session id. If the victim authenticates under the attacker's chosen session ID, the attacker can present that session ID to the server and be recognized as the victim.
// <properties name="path">\web.config</properties>
// <properties name="section">forms</properties>
// <properties name="snippet">39: <!--set up users-->
// 40: <authentication mode="Forms">
 ...
IDictionary<string, string> properties = context.Properties;
string path = properties[nameof(path)];
string section = properties[nameof(section)];
string snippet = properties[nameof(snippet)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(path, CreateRegion(snippet))
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The configuration in the
section, // {0} section of
path // '{1}' has 'UseCookies' set to a value other than 'cookieless'.
}
};
return result;
}
private Result ConstructSqlInjectionResult(ContrastLogReader.Context context)
{
// sql-injection : SQL Injection
// <properties name="platform">ASP.NET Web Forms</properties>
// <properties name="webforms-page">OWASP.WebGoat.NET.ForgotPassword</properties>
// <properties name="route-signature">OWASP.WebGoat.NET.ForgotPassword</properties>
string untrustedData = BuildSourcesString(context.Sources);
string page = context.RequestTarget;
string source = context.PropagationEvents[0].Stack.Frames[0].Location.LogicalLocation?.FullyQualifiedName;
string caller = context.PropagationEvents[context.PropagationEvents.Count - 1].Stack.Frames[0].Location.LogicalLocation?.FullyQualifiedName;
string sink = context.MethodEvent.Stack.Frames[0].Location.LogicalLocation?.FullyQualifiedName;
// default : SQL injection from untrusted source(s) '{0}' observed on '{1}' page. Untrusted data flowed from '{2}' to dangerous sink '{3}' in '{4}'.
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(page),
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{
untrustedData, // SQL injection from untrusted source(s) '{0}'
page, // observed on '{1}' page.
source, // Untrusted data flowed from '{2}'
sink, // to dangerous sink '{3}'
caller // from a call site in '{4}'.
}
};
result.CodeFlows[0].ThreadFlows[0].Locations.Add(context.MethodEvent);
return result;
}
private string BuildSourcesString(HashSet<Tuple<string, string>> sources)
{
const string UnknownSourceTypeName = "<unknown source type>";
var sb = new StringBuilder();
foreach (Tuple<string, string> tuple in sources)
{
if (sb.Length > 0) { sb.Append(", "); }
// Item1 is the name, Item2 is the source type, e.g., parameter
if (!string.IsNullOrWhiteSpace(tuple.Item1)) { sb.Append(tuple.Item1).Append(": "); }
string sourceType = !string.IsNullOrWhiteSpace(tuple.Item2)
? tuple.Item2
: UnknownSourceTypeName;
sb.Append(sourceType);
}
return sb.ToString();
}
private Result ConstructVersionHeaderEnabledResult(ContrastLogReader.Context context)
{
// version-header-enabled : Version Header Enabled
// <properties name="path">\web.config</properties>
IDictionary<string, string> properties = context.Properties;
string path = properties[nameof(path)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(path),
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The configuration in
path, // '{0}' did not explicitly disable 'enableVersionHeader' in the <httpRuntime> section.
}
};
return result;
}
private Result ConstructWebApplicationDeployedinDebugModeResult(ContrastLogReader.Context context)
{
// compilation-debug : Web Application Deployed in Debug Mode
// <properties name="path">\web.config</properties>
// <properties name="snippet">30: <system.web>
IDictionary<string, string> properties = context.Properties;
string path = properties[nameof(path)];
string snippet = properties[nameof(snippet)];
Result result = CreateResultCore(context);
result.Locations = new List<Location>
{
new Location
{
PhysicalLocation = CreatePhysicalLocation(path, CreateRegion(snippet)),
}
};
result.Message = new Message
{
Id = "default",
Arguments = new List<string>
{ // The configuration in
path, // '{0}' has 'debug' set to 'true' in the <compilation> section.
}
};
return result;
}
private Region CreateRegion(string snippet)
{
int? startLine = null;
int endLine = 0;
snippet = NaiveXmlDecode(snippet);
string[] lines = snippet.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
var sb = new StringBuilder();
foreach (string line in lines)
{
string[] lineTokens = line.Split(':');
if (startLine == null)
{
startLine = int.Parse(lineTokens[0]);
endLine = startLine.Value;
}
else
{
endLine++;
}
sb.AppendLine(lineTokens[1]);
}
return new Region
{
StartLine = startLine.Value,
EndLine = endLine != startLine.Value ? endLine : 0,
Snippet = new ArtifactContent { Text = sb.ToString() }
};
}
private string NaiveXmlDecode(string snippet)
{
return snippet.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace(""", "\"").Replace("'", "'");
}
private Result CreateResultCore(ContrastLogReader.Context context)
{
return new Result
{
RuleId = context.RuleId,
RuleIndex = _ruleIdToIndexDictionary[context.RuleId],
Level = GetRuleFailureLevel(context.RuleId),
WebRequest = CreateWebRequest(context),
CodeFlows = CreateCodeFlows(context)
};
}
private WebRequest CreateWebRequest(ContrastLogReader.Context context)
{
return context.HasRequest() ?
new WebRequest
{
Protocol = context.RequestProtocol,
Version = context.RequestVersion,
Method = context.RequestMethod,
Target = context.RequestTarget,
Headers = context.Headers,
Parameters = context.Parameters,
Body = string.IsNullOrEmpty(context.RequestBody)
? null
: new ArtifactContent
{
Text = context.RequestBody
}
} : null;
}
private IList<CodeFlow> CreateCodeFlows(ContrastLogReader.Context context)
{
List<CodeFlow> codeFlows = null;
if (context.PropagationEvents != null)
{
codeFlows = new List<CodeFlow>
{
new CodeFlow
{
ThreadFlows = new List<ThreadFlow>
{
new ThreadFlow
{
Locations = context.PropagationEvents
}
}
}
};
if (context.MethodEvent != null)
{
codeFlows[0].ThreadFlows[0].Locations.Add(context.MethodEvent);
}
}
return codeFlows;
}
private PhysicalLocation CreatePhysicalLocation(string uri, Region region = null)
{
return new PhysicalLocation
{
ArtifactLocation = new ArtifactLocation
{
UriBaseId = SiteRootUriBaseIdName,
Uri = new Uri(uri, UriKind.RelativeOrAbsolute)
},
Region = region
};
}
// Find the user code method call closest to the top of the stack. This is
// the location we should report as being responsible for the result.
private static string GetUserCodeLocation(Stack stack)
{
const string SystemPrefix = "System.";
foreach (StackFrame frame in stack.Frames)
{
string fullyQualifiedLogicalName = frame.Location.LogicalLocation.FullyQualifiedName;
if (!fullyQualifiedLogicalName.StartsWith(SystemPrefix))
{
return fullyQualifiedLogicalName;
}
}
return string.Empty;
}
// Get the failure level for the rule with the specified id, defaulting to
// "warning" if the rule does not specify a configuration.
private FailureLevel GetRuleFailureLevel(string ruleId)
{
return _rules[ruleId].DefaultConfiguration?.Level ?? FailureLevel.Warning;
}
}