diff --git a/change-notes/1.19/analysis-csharp.md b/change-notes/1.19/analysis-csharp.md index 674a5fcd3814..d1b2b33974b7 100644 --- a/change-notes/1.19/analysis-csharp.md +++ b/change-notes/1.19/analysis-csharp.md @@ -3,9 +3,9 @@ ## General improvements * Control flow graph improvements: - * The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead. +* The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead. * Code that is only reachable from a constant failing assertion, such as `Debug.Assert(false)`, is considered to be unreachable. - + ## New queries | **Query** | **Tags** | **Purpose** | @@ -16,6 +16,7 @@ | Inconsistent lock sequence (`cs/inconsistent-lock-sequence`) | More results | This query now finds inconsistent lock sequences globally across calls. | | Local scope variable shadows member (`cs/local-shadows-member`) | Fewer results | Results have been removed where a constructor parameter shadows a member, because the parameter is probably used to initialize the member. | +| Cross-site scripting (`cs/web/xss`) | More results | This query now finds cross-site scripting vulnerabilities in ASP.NET Core applications. | | *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* | diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll b/csharp/ql/src/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll index a104477baff1..d5debe55cfea 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/LibraryTypeDataFlow.qll @@ -1746,3 +1746,28 @@ class SystemNetWebUtilityFlow extends LibraryTypeDataFlow, SystemNetWebUtility { preservesValue = false } } + +/** + * The `StringValues` class used in many .NET Core libraries. Requires special `LibraryTypeDataFlow` flow. + */ +class StringValues extends Struct { + StringValues() { this.hasQualifiedName("Microsoft.Extensions.Primitives", "StringValues") } +} + +/** + * Custom flow through StringValues.StringValues library class + */ +class StringValuesFlow extends LibraryTypeDataFlow, StringValues { + override predicate callableFlow( + CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c, + boolean preservesValue + ) { + c = any(Callable ca | this = ca.getDeclaringType()) and + ( + source = any(CallableFlowSourceArg a) or + source = any(CallableFlowSourceQualifier q) + ) and + sink = any(CallableFlowSinkReturn r) and + preservesValue = false + } +} diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/flowsources/Remote.qll b/csharp/ql/src/semmle/code/csharp/dataflow/flowsources/Remote.qll index 920bcac81bc3..92e505242c2f 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/flowsources/Remote.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/flowsources/Remote.qll @@ -11,6 +11,7 @@ private import semmle.code.csharp.frameworks.system.web.Services private import semmle.code.csharp.frameworks.system.web.ui.WebControls private import semmle.code.csharp.frameworks.WCF private import semmle.code.csharp.frameworks.microsoft.Owin +private import semmle.code.csharp.frameworks.microsoft.AspNetCore /** A data flow source of remote user input. */ abstract class RemoteFlowSource extends DataFlow::Node { @@ -28,9 +29,8 @@ class AspNetQueryStringMember extends Member { t instanceof SystemWebHttpRequestClass or t instanceof SystemNetHttpListenerRequestClass or t instanceof SystemWebHttpRequestBaseClass - | - this = t.getProperty(getHttpRequestFlowPropertyNames()) - or + | + this = t.getProperty(getHttpRequestFlowPropertyNames()) or this.(Field).getType() = t or this.(Property).getType() = t or this.(Callable).getReturnType() = t @@ -61,7 +61,7 @@ class AspNetQueryStringRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow t instanceof SystemWebHttpRequestClass or t instanceof SystemNetHttpListenerRequestClass or t instanceof SystemWebHttpRequestBaseClass - | + | // A request object can be indexed, so taint the object as well this.getExpr().getType() = t ) @@ -69,39 +69,35 @@ class AspNetQueryStringRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow this.getExpr() = any(AspNetQueryStringMember m).getAnAccess() } - override - string getSourceType() { result = "ASP.NET query string" } + override string getSourceType() { result = "ASP.NET query string" } } /** A data flow source of remote user input (ASP.NET unvalidated request data). */ -class AspNetUnvalidatedQueryStringRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow::ExprNode { +class AspNetUnvalidatedQueryStringRemoteFlowSource extends AspNetRemoteFlowSource, + DataFlow::ExprNode { AspNetUnvalidatedQueryStringRemoteFlowSource() { this.getExpr() = any(SystemWebUnvalidatedRequestValues c).getAProperty().getGetter().getACall() or - this.getExpr() = any(SystemWebUnvalidatedRequestValuesBase c).getAProperty().getGetter().getACall() + this.getExpr() = any(SystemWebUnvalidatedRequestValuesBase c) + .getAProperty() + .getGetter() + .getACall() } - override - string getSourceType() { result = "ASP.NET unvalidated request data" } + override string getSourceType() { result = "ASP.NET unvalidated request data" } } /** A data flow source of remote user input (ASP.NET user input). */ class AspNetUserInputRemoteFlowSource extends AspNetRemoteFlowSource, DataFlow::ExprNode { - AspNetUserInputRemoteFlowSource() { - getType() instanceof SystemWebUIWebControlsTextBoxClass - } + AspNetUserInputRemoteFlowSource() { getType() instanceof SystemWebUIWebControlsTextBoxClass } - override - string getSourceType() { result = "ASP.NET user input" } + override string getSourceType() { result = "ASP.NET user input" } } /** A data flow source of remote user input (WCF based web service). */ class WcfRemoteFlowSource extends RemoteFlowSource, DataFlow::ParameterNode { - WcfRemoteFlowSource() { - exists(OperationMethod om | om.getAParameter() = this.getParameter()) - } + WcfRemoteFlowSource() { exists(OperationMethod om | om.getAParameter() = this.getParameter()) } - override - string getSourceType() { result = "web service input" } + override string getSourceType() { result = "web service input" } } /** A data flow source of remote user input (ASP.NET web service). */ @@ -113,8 +109,7 @@ class AspNetServiceRemoteFlowSource extends RemoteFlowSource, DataFlow::Paramete ) } - override - string getSourceType() { result = "ASP.NET web service input" } + override string getSourceType() { result = "ASP.NET web service input" } } /** A data flow source of remote user input (ASP.NET request message). */ @@ -123,8 +118,7 @@ class SystemNetHttpRequestMessageRemoteFlowSource extends RemoteFlowSource, Data getType() instanceof SystemWebHttpRequestMessageClass } - override - string getSourceType() { result = "ASP.NET request message" } + override string getSourceType() { result = "ASP.NET request message" } } /** @@ -136,17 +130,15 @@ class MicrosoftOwinStringFlowSource extends RemoteFlowSource, DataFlow::ExprNode this.getExpr() = any(MicrosoftOwinString owinString).getValueProperty().getGetter().getACall() } - override - string getSourceType() { result = "Microsoft Owin request or query string" } + override string getSourceType() { result = "Microsoft Owin request or query string" } } -/** - * A data flow source of remote user input (`Microsoft Owin IOwinRequest`). - */ +/** A data flow source of remote user input (`Microsoft Owin IOwinRequest`). */ class MicrosoftOwinRequestRemoteFlowSource extends RemoteFlowSource, DataFlow::ExprNode { MicrosoftOwinRequestRemoteFlowSource() { exists(Property p, MicrosoftOwinIOwinRequestClass owinRequest | - this.getExpr() = p.getGetter().getACall() | + this.getExpr() = p.getGetter().getACall() + | p = owinRequest.getAcceptProperty() or p = owinRequest.getBodyProperty() or p = owinRequest.getCacheControlProperty() or @@ -167,23 +159,62 @@ class MicrosoftOwinRequestRemoteFlowSource extends RemoteFlowSource, DataFlow::E ) } - override - string getSourceType() { result = "Microsoft Owin request" } + override string getSourceType() { result = "Microsoft Owin request" } } -/** - * A parameter to an Mvc controller action method, viewed as a source of remote user input. - */ +/** A parameter to an Mvc controller action method, viewed as a source of remote user input. */ class ActionMethodParameter extends RemoteFlowSource, DataFlow::ParameterNode { ActionMethodParameter() { exists(Parameter p | p = this.getParameter() and - p.fromSource() | + p.fromSource() + | p = any(Controller c).getAnActionMethod().getAParameter() or p = any(ApiController c).getAnActionMethod().getAParameter() ) } - override - string getSourceType() { result = "ASP.NET MVC action method parameter" } + override string getSourceType() { result = "ASP.NET MVC action method parameter" } +} + +/** A data flow source of remote user input (ASP.NET Core). */ +abstract class AspNetCoreRemoteFlowSource extends RemoteFlowSource { } + +/** A data flow source of remote user input (ASP.NET query collection). */ +class AspNetCoreQueryRemoteFlowSource extends AspNetCoreRemoteFlowSource, DataFlow::ExprNode { + AspNetCoreQueryRemoteFlowSource() { + exists(ValueOrRefType t | + t instanceof MicrosoftAspNetCoreHttpHttpRequest or + t instanceof MicrosoftAspNetCoreHttpQueryCollection or + t instanceof MicrosoftAspNetCoreHttpQueryString + | + this.getExpr().(Call).getTarget().getDeclaringType() = t or + this.asExpr().(Access).getTarget().getDeclaringType() = t + ) + or + exists(Call c | + c + .getTarget() + .getDeclaringType() + .hasQualifiedName("Microsoft.AspNetCore.Http", "IQueryCollection") and + c.getTarget().getName() = "TryGetValue" and + this.asExpr() = c.getArgumentForName("value") + ) + } + + override string getSourceType() { result = "ASP.NET Core query string" } +} + +/** A parameter to a `Mvc` controller action method, viewed as a source of remote user input. */ +class AspNetCoreActionMethodParameter extends RemoteFlowSource, DataFlow::ParameterNode { + AspNetCoreActionMethodParameter() { + exists(Parameter p | + p = this.getParameter() and + p.fromSource() + | + p = any(MicrosoftAspNetCoreMvcController c).getAnActionMethod().getAParameter() + ) + } + + override string getSourceType() { result = "ASP.NET Core MVC action method parameter" } } diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll b/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll new file mode 100644 index 000000000000..cdcc9c2a37c8 --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll @@ -0,0 +1,295 @@ +/** Provides classes for working with `Microsoft.AspNetCore.Mvc`. */ + +import csharp +import semmle.code.csharp.frameworks.Microsoft + +/** The `Microsoft.AspNetCore` namespace. */ +class MicrosoftAspNetCoreNamespace extends Namespace { + MicrosoftAspNetCoreNamespace() { + getParentNamespace() instanceof MicrosoftNamespace and + hasName("AspNetCore") + } +} + +/** The `Microsoft.AspNetCore.Mvc` namespace. */ +class MicrosoftAspNetCoreMvcNamespace extends Namespace { + MicrosoftAspNetCoreMvcNamespace() { + getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and + hasName("Mvc") + } +} + +/** The 'Microsoft.AspNetCore.Mvc.ViewFeatures' namespace. */ +class MicrosoftAspNetCoreMvcViewFeatures extends Namespace { + MicrosoftAspNetCoreMvcViewFeatures() { + getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and + hasName("ViewFeatures") + } +} + +/** An attribute whose type is in the `Microsoft.AspNetCore.Mvc` namespace. */ +class MicrosoftAspNetCoreMvcAttribute extends Attribute { + MicrosoftAspNetCoreMvcAttribute() { + getType().getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace + } +} + +/** A `Microsoft.AspNetCore.Mvc.HttpPost` attribute. */ +class MicrosoftAspNetCoreMvcHttpPostAttribute extends MicrosoftAspNetCoreMvcAttribute { + MicrosoftAspNetCoreMvcHttpPostAttribute() { getType().hasName("HttpPostAttribute") } +} + +/** A `Microsoft.AspNetCore.Mvc.HttpPut` attribute. */ +class MicrosoftAspNetCoreMvcHttpPutAttribute extends MicrosoftAspNetCoreMvcAttribute { + MicrosoftAspNetCoreMvcHttpPutAttribute() { getType().hasName("HttpPutAttribute") } +} + +/** A `Microsoft.AspNetCore.Mvc.HttpDelete` attribute. */ +class MicrosoftAspNetCoreMvcHttpDeleteAttribute extends MicrosoftAspNetCoreMvcAttribute { + MicrosoftAspNetCoreMvcHttpDeleteAttribute() { getType().hasName("HttpDeleteAttribute") } +} + +/** A `Microsoft.AspNetCore.Mvc.NonAction` attribute. */ +class MicrosoftAspNetCoreMvcNonActionAttribute extends MicrosoftAspNetCoreMvcAttribute { + MicrosoftAspNetCoreMvcNonActionAttribute() { getType().hasName("NonActionAttribute") } +} + +/** The `Microsoft.AspNetCore.Antiforgery` namespace. */ +class MicrosoftAspNetCoreAntiforgeryNamespace extends Namespace { + MicrosoftAspNetCoreAntiforgeryNamespace() { + getParentNamespace() instanceof MicrosoftAspNetCoreNamespace and + hasName("Antiforgery") + } +} + +/** The `Microsoft.AspNetCore.Mvc.Filters` namespace. */ +class MicrosoftAspNetCoreMvcFilters extends Namespace { + MicrosoftAspNetCoreMvcFilters() { + getParentNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and + hasName("Filters") + } +} + +/** The `Microsoft.AspNetCore.Mvc.Filters.IFilterMetadataInterface` interface. */ +class MicrosoftAspNetCoreMvcIFilterMetadataInterface extends Interface { + MicrosoftAspNetCoreMvcIFilterMetadataInterface() { + getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and + hasName("IFilterMetadata") + } +} + +/** The `Microsoft.AspNetCore.IAuthorizationFilter` interface. */ +class MicrosoftAspNetCoreIAuthorizationFilterInterface extends Interface { + MicrosoftAspNetCoreIAuthorizationFilterInterface() { + getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and + hasName("IAsyncAuthorizationFilter") + } + + /** Gets the `OnAuthorizationAsync` method. */ + Method getOnAuthorizationMethod() { result = getAMethod("OnAuthorizationAsync") } +} + +/** The `Microsoft.AspNetCore.IAntiforgery` interface. */ +class MicrosoftAspNetCoreIAntiForgeryInterface extends Interface { + MicrosoftAspNetCoreIAntiForgeryInterface() { + getNamespace() instanceof MicrosoftAspNetCoreAntiforgeryNamespace and + hasName("IAntiforgery") + } + + /** Gets the `ValidateRequestAsync` method. */ + Method getValidateMethod() { result = getAMethod("ValidateRequestAsync") } +} + +/** The `Microsoft.AspNetCore.DefaultAntiForgery` class, or another user-supplied class that implements `IAntiForgery`. */ +class AntiForgeryClass extends Class { + AntiForgeryClass() { getABaseInterface*() instanceof MicrosoftAspNetCoreIAntiForgeryInterface } + + /** Gets the `ValidateRequestAsync` method. */ + Method getValidateMethod() { result = getAMethod("ValidateRequestAsync") } +} + +/** An authorization filter class defined by AspNetCore or the user. */ +class AuthorizationFilterClass extends Class { + AuthorizationFilterClass() { + getABaseInterface*() instanceof MicrosoftAspNetCoreIAuthorizationFilterInterface + } + + /** Gets the `OnAuthorization` method provided by this filter. */ + Method getOnAuthorizationMethod() { result = getAMethod("OnAuthorizationAsync") } +} + +/** An attribute whose type has a name like `[Auto...]Validate[...]Anti[Ff]orgery[...Token]Attribute`. */ +class ValidateAntiForgeryAttribute extends Attribute { + ValidateAntiForgeryAttribute() { getType().getName().matches("%Validate%Anti_orgery%Attribute") } +} + +/** + * A class that has a name like `[Auto...]Validate[...]Anti[Ff]orgery[...Token]` and implements `IFilterMetadata` interface + * This class can be added to a collection of global `MvcOptions.Filters` collection. + */ +class ValidateAntiforgeryTokenAuthorizationFilter extends Class { + ValidateAntiforgeryTokenAuthorizationFilter() { + getABaseInterface*() instanceof MicrosoftAspNetCoreMvcIFilterMetadataInterface and + getName().matches("%Validate%Anti_orgery%") + } +} + +/** The `Microsoft.AspNetCore.Mvc.Filters.FilterCollection` class. */ +class MicrosoftAspNetCoreMvcFilterCollection extends Class { + MicrosoftAspNetCoreMvcFilterCollection() { + getNamespace() instanceof MicrosoftAspNetCoreMvcFilters and + hasName("FilterCollection") + } + + /** Gets an `Add` method. */ + Method getAddMethod() { + result = getAMethod("Add") or + result = getABaseType().getAMethod("Add") + } +} + +/** The `Microsoft.AspNetCore.Mvc.MvcOptions` class. */ +class MicrosoftAspNetCoreMvcOptions extends Class { + MicrosoftAspNetCoreMvcOptions() { + getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and + hasName("MvcOptions") + } + + /** Gets the `Filters` property. */ + Property getFilterCollectionProperty() { result = getProperty("Filters") } +} + +/** The base class for controllers in MVC, i.e. `Microsoft.AspNetCore.Mvc.Controller` or `Microsoft.AspNetCore.Mvc.ControllerBase` class. */ +class MicrosoftAspNetCoreMvcControllerBaseClass extends Class { + MicrosoftAspNetCoreMvcControllerBaseClass() { + getNamespace() instanceof MicrosoftAspNetCoreMvcNamespace and + ( + hasName("Controller") or + hasName("ControllerBase") + ) + } +} + +/** A subtype of `Microsoft.AspNetCore.Mvc.Controller` or `Microsoft.AspNetCore.Mvc.ControllerBase`. */ +class MicrosoftAspNetCoreMvcController extends Class { + MicrosoftAspNetCoreMvcController() { + getABaseType*() instanceof MicrosoftAspNetCoreMvcControllerBaseClass + } + + /** Gets an action method for this controller. */ + Method getAnActionMethod() { + result = getAMethod() and + result.isPublic() and + not result.isStatic() and + not result.getAnAttribute() instanceof MicrosoftAspNetCoreMvcNonActionAttribute + } + + /** Gets a `Redirect*` method. */ + Method getARedirectMethod() { + result = this.getAMethod() and + result.getName().matches("Redirect%") + } +} + +/** The `Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper` class. */ +class MicrosoftAspNetCoreMvcHtmlHelperClass extends Class { + MicrosoftAspNetCoreMvcHtmlHelperClass() { + getNamespace() instanceof MicrosoftAspNetCoreMvcViewFeatures and + hasName("HtmlHelper") + } + + /** Gets the `Raw` method. */ + Method getRawMethod() { result = getAMethod("Raw") } +} + +/** A class deriving from `Microsoft.AspNetCore.Mvc.Razor.RazorPageBase`, implements Razor page in ASPNET Core. */ +class MicrosoftAspNetCoreMvcRazorPageBase extends Class { + MicrosoftAspNetCoreMvcRazorPageBase() { + this.getABaseType*().hasQualifiedName("Microsoft.AspNetCore.Mvc.Razor", "RazorPageBase") + } + + /** Gets the `WriteLiteral` method. */ + Method getWriteLiteralMethod() { result = getAMethod("WriteLiteral") } +} + +/** A class deriving from `Microsoft.AspNetCore.Http.HttpRequest`, implements `HttpRequest` in ASP.NET Core. */ +class MicrosoftAspNetCoreHttpHttpRequest extends Class { + MicrosoftAspNetCoreHttpHttpRequest() { + this.getABaseType*().hasQualifiedName("Microsoft.AspNetCore.Http", "HttpRequest") + } +} + +/** A class deriving from `Microsoft.AspNetCore.Http.HttpResponse`, implements `HttpResponse` in ASP.NET Core. */ +class MicrosoftAspNetCoreHttpHttpResponse extends Class { + MicrosoftAspNetCoreHttpHttpResponse() { + this.getABaseType*().hasQualifiedName("Microsoft.AspNetCore.Http", "HttpResponse") + } + + /** Gets the `Redirect` method. */ + Method getRedirectMethod() { result = this.getAMethod("Redirect") } + + /** Gets the `Headers` property. */ + Property getHeadersProperty() { result = this.getProperty("Headers") } +} + +/** An interface that is a wrapper around the collection of cookies in the response. */ +class MicrosoftAspNetCoreHttpResponseCookies extends Interface { + MicrosoftAspNetCoreHttpResponseCookies() { + this.hasQualifiedName("Microsoft.AspNetCore.Http.IResponseCookies") + } + + /** Gets the `Append` method. */ + Method getAppendMethod() { result = this.getAMethod("Append") } +} + +/** The class `Microsoft.AspNetCore.Http.QueryString`, holds query string in ASP.NET Core. */ +class MicrosoftAspNetCoreHttpQueryString extends Struct { + MicrosoftAspNetCoreHttpQueryString() { + this.hasQualifiedName("Microsoft.AspNetCore.Http", "QueryString") + } +} + +/** A class or interface implementing `IQueryCollection`, holds parsed query string in ASP.NET Core. */ +class MicrosoftAspNetCoreHttpQueryCollection extends RefType { + MicrosoftAspNetCoreHttpQueryCollection() { + this.getABaseInterface().hasQualifiedName("Microsoft.AspNetCore.Http", "IQueryCollection") + } +} + +/** The helper class `ResponseHeaders` for setting headers. */ +class MicrosoftAspNetCoreHttpResponseHeaders extends RefType { + MicrosoftAspNetCoreHttpResponseHeaders() { + this.hasQualifiedName("Microsoft.AspNetCore.Http.Headers", "ResponseHeaders") + } + + /** Gets the `Location` property. */ + Property getLocationProperty() { result = this.getProperty("Location") } +} + +/** The `Microsoft.AspNetCore.Http.HeaderDictionaryExtensions` class. */ +class MicrosoftAspNetCoreHttpHeaderDictionaryExtensions extends RefType { + MicrosoftAspNetCoreHttpHeaderDictionaryExtensions() { + this.hasQualifiedName("Microsoft.AspNetCore.Http", "HeaderDictionaryExtensions") + } + + /** Gets the `Append` extension method. */ + Method getAppendMethod() { result = this.getAMethod("Append") } + + /** Gets the `AppendCommaSeparatedValues` extension method. */ + Method getAppendCommaSeparatedValuesMethod() { + result = this.getAMethod("AppendCommaSeparatedValues") + } + + /** Gets the `SetCommaSeparatedValues` extension method. */ + Method getSetCommaSeparatedValuesMethod() { result = this.getAMethod("SetCommaSeparatedValues") } +} + +/** + * The `Microsoft.AspNetCore.Html.HtmlString` class, supposed to wrap HTML-encoded string in ASP.NET Core + * Untrusted and unsanitized data should never flow there. + */ +class MicrosoftAspNetCoreHttpHtmlString extends Class { + MicrosoftAspNetCoreHttpHtmlString() { + this.hasQualifiedName("Microsoft.AspNetCore.Html", "HtmlString") + } +} diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/system/web/WebPages.qll b/csharp/ql/src/semmle/code/csharp/frameworks/system/web/WebPages.qll new file mode 100644 index 000000000000..04e61696706e --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/frameworks/system/web/WebPages.qll @@ -0,0 +1,37 @@ +/** Definitions related to the namespace `System.Web.WebPages`, ASP.NET */ +import csharp + +private import semmle.code.csharp.frameworks.system.Web + +/** The `System.Web.WebPages` namespace. */ +class SystemWebWebPagesNamespace extends Namespace { + SystemWebWebPagesNamespace() { + getParentNamespace() instanceof SystemWebNamespace and + hasName("WebPages") + } +} + +/** The `System.Web.WebPages.WebPageExecutingBase` class. */ +class SystemWebWebPagesWebPageExecutingBaseClass extends Class { + SystemWebWebPagesWebPageExecutingBaseClass() { + getNamespace() instanceof SystemWebWebPagesNamespace and + hasName("WebPageExecutingBase") + } +} + +/** A class that derives from `System.Web.WebPages.WebPageExecutingBase`. */ +class WebPageClass extends Class { + WebPageClass () { + this.getBaseClass*() instanceof SystemWebWebPagesWebPageExecutingBaseClass + } + + /** Gets the `WriteLiteral` method. */ + Method getWriteLiteralMethod() { + result = getAMethod("WriteLiteral") + } + + /** Gets the `WriteLiteralTo` method. */ + Method getWriteLiteralToMethod() { + result = getAMethod("WriteLiteralTo") + } +} diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/UrlRedirect.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/UrlRedirect.qll index 5ef7a9d7b628..bb8cae46e267 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/UrlRedirect.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/UrlRedirect.qll @@ -9,6 +9,7 @@ module UrlRedirect { import semmle.code.csharp.frameworks.system.Web import semmle.code.csharp.frameworks.system.web.Mvc import semmle.code.csharp.security.Sanitizers + import semmle.code.csharp.frameworks.microsoft.AspNetCore /** * A data flow source for unvalidated URL redirect vulnerabilities. @@ -149,4 +150,65 @@ module UrlRedirect { private class SimpleTypeSanitizer extends Sanitizer, SimpleTypeSanitizedExpr { } private class GuidSanitizer extends Sanitizer, GuidSanitizedExpr { } + + /** + * A URL argument to a call to `HttpResponse.Redirect()` or `Controller.Redirect()`, that is a + * sink for URL redirects. + */ + class AspNetCoreRedirectSink extends Sink { + AspNetCoreRedirectSink() { + exists(MethodCall mc | + mc.getTarget() = any(MicrosoftAspNetCoreHttpHttpResponse response).getRedirectMethod() or + mc.getTarget() = any(MicrosoftAspNetCoreMvcController response).getARedirectMethod() + | + // Response.Redirect uses 'location' parameter + this.getExpr() = mc.getArgumentForName("location") or + // Redirect uses the parameter name 'url' + this.getExpr() = mc.getArgumentForName("url") or + // Controller.RedirectToAction* + this.getExpr() = mc.getArgumentForName("actionName") or + // Controller.RedirectToRoute* + this.getExpr() = mc.getArgumentForName("routeName") or + // Controller.RedirectToPage* + this.getExpr() = mc.getArgumentForName("pageName") + ) + } + } + + /** + * Anything that is setting "location" header in the response headers. + */ + class AspNetCoreLocationHeaderSink extends Sink { + AspNetCoreLocationHeaderSink () { + // ResponseHeaders.Location = + exists(AssignableDefinition def | + def.getTarget() = any(MicrosoftAspNetCoreHttpResponseHeaders headers).getLocationProperty() | + this.asExpr() = def.getSource() + ) + or // HttpResponse.Headers.Append("location", ) + exists(MethodCall mc, MicrosoftAspNetCoreHttpHeaderDictionaryExtensions ext | + mc.getTarget() = ext.getAppendMethod() or + mc.getTarget() = ext.getAppendCommaSeparatedValuesMethod() or + mc.getTarget() = ext.getSetCommaSeparatedValuesMethod() | + mc.getArgumentForName("key").getValue().toLowerCase() = "location" and + this.getExpr() = mc.getArgument(2)) + or // HttpResponse.Headers.Add("location", ) + exists(RefType cl, MicrosoftAspNetCoreHttpHttpResponse resp, PropertyAccess qualifier, MethodCall add | + qualifier.getTarget() = resp.getHeadersProperty() and + add.getTarget() = cl.getAMethod("Add") and + qualifier = add.getQualifier() and + add.getArgument(0).getValue().toLowerCase() = "location" and + this.getExpr() = add.getArgument(1)) + or // HttpResponse.Headers["location"] = + exists(RefType cl, MicrosoftAspNetCoreHttpHttpResponse resp, IndexerAccess ci, Call cs, PropertyAccess qualifier | + qualifier.getTarget() = resp.getHeadersProperty() and + ci.getTarget() = cl.getAnIndexer() and + qualifier = ci.getQualifier() and + cs.getTarget() = cl.getAnIndexer().getSetter() and + cs.getArgument(0).getValue().toLowerCase() = "location" and + this.asExpr() = cs.getArgument(1)) + } + } } + + diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll index 36261409bacc..de16c91a7488 100644 --- a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll +++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll @@ -6,9 +6,11 @@ import csharp module XSS { import semmle.code.csharp.dataflow.flowsources.Remote + import semmle.code.csharp.frameworks.microsoft.AspNetCore import semmle.code.csharp.frameworks.system.Net import semmle.code.csharp.frameworks.system.Web import semmle.code.csharp.frameworks.system.web.Mvc + import semmle.code.csharp.frameworks.system.web.WebPages import semmle.code.csharp.frameworks.system.web.UI import semmle.code.csharp.frameworks.system.web.ui.WebControls import semmle.code.csharp.frameworks.system.windows.Forms @@ -514,6 +516,75 @@ module XSS { this.getExpr() = any(ObjectCreation oc | oc.getTarget().getDeclaringType().hasQualifiedName("System.Net.Http", "StringContent")).getArgumentForName("content") } } + + /** + * An expression that is used as an argument to `Page.WriteLiteral`, typically in + * a `.cshtml` file. + */ + class WebPageWriteLiteralSink extends Sink, HtmlSink { + WebPageWriteLiteralSink() { + this.getExpr() = any(WebPageClass h).getWriteLiteralMethod().getACall().getAnArgument() + } + + override string explanation() { + result = "System.Web.WebPages.WebPage.WriteLiteral() method" + } + } + + /** + * An expression that is used as an argument to `Page.WriteLiteralTo`, typically in + * a `.cshtml` file. + */ + class WebPageWriteLiteralToSink extends Sink, HtmlSink { + WebPageWriteLiteralToSink() { + this.getExpr() = any(WebPageClass h).getWriteLiteralToMethod().getACall().getAnArgument() + } + + override string explanation() { + result = "System.Web.WebPages.WebPage.WriteLiteralTo() method" + } + } + + abstract class AspNetCoreSink extends Sink, HtmlSink { } + + /** + * An expression that is used as an argument to `HtmlHelper.Raw`, typically in + * a `.cshtml` file. + */ + class MicrosoftAspNetCoreMvcHtmlHelperRawSink extends AspNetCoreSink { + MicrosoftAspNetCoreMvcHtmlHelperRawSink() { + this.getExpr() = any(MicrosoftAspNetCoreMvcHtmlHelperClass h).getRawMethod().getACall().getAnArgument() + } + + override string explanation() { + result = "Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.Raw() method" + } + } + + /** + * An expression that is used as an argument to `Page.WriteLiteral` in ASP.NET 6.0 razor page, typically in + * a `.cshtml` file. + */ + class MicrosoftAspNetRazorPageWriteLiteralSink extends AspNetCoreSink { + MicrosoftAspNetRazorPageWriteLiteralSink() { + this.getExpr() = any(MicrosoftAspNetCoreMvcRazorPageBase h).getWriteLiteralMethod().getACall().getAnArgument() + } + + override string explanation() { + result = "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase.WriteLiteral() method" + } + } + + /** + * HtmlString that may be rendered as is need to have sanitized value + */ + class MicrosoftAspNetHtmlStringSink extends AspNetCoreSink { + MicrosoftAspNetHtmlStringSink() { + exists (ObjectCreation c, MicrosoftAspNetCoreHttpHtmlString s | + c.getTarget() = s.getAConstructor() and + this.asExpr() = c.getAnArgument()) + } + } } private Type getMemberType(Member m) { diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/AspInline.expected b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/AspInline.expected similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/AspInline.expected rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/AspInline.expected diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/AspInline.ql b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/AspInline.ql similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/AspInline.ql rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/AspInline.ql diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.cs similarity index 68% rename from csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.cs rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.cs index 7e4e97f40bf0..24c4eb73912d 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.cs @@ -1,4 +1,4 @@ -// semmle-extractor-options: /r:${testdir}/../../../resources/assemblies/System.Data.dll /r:${testdir}/../../../resources/assemblies/System.Web.dll /r:${testdir}/../../../resources/assemblies/System.Web.Mvc.dll /r:System.ComponentModel.Primitives.dll /r:System.Collections.Specialized.dll /r:${testdir}/../../../resources/assemblies/System.Net.Http.dll +// semmle-extractor-options: /r:${testdir}/../../../../resources/assemblies/System.Data.dll /r:${testdir}/../../../../resources/assemblies/System.Web.dll /r:${testdir}/../../../../resources/assemblies/System.Web.Mvc.dll /r:System.ComponentModel.Primitives.dll /r:System.Collections.Specialized.dll /r:${testdir}/../../../../resources/assemblies/System.Net.Http.dll using System; using System.Data.SqlClient; diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.expected b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.expected similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.expected rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.expected diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.qlref b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.qlref similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS.qlref rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/StoredXSS.qlref diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.cs similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/XSS.cs rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.cs diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS.expected b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.expected similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/XSS.expected rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.expected diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS.qlref b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.qlref similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/XSS.qlref rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/XSS.qlref diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/script.aspx b/csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/script.aspx similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-079/script.aspx rename to csharp/ql/test/query-tests/Security Features/CWE-079/StoredXSS/script.aspx diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.expected b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.expected new file mode 100644 index 000000000000..3f231e656004 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.expected @@ -0,0 +1,10 @@ +| XSSAspNet.cs:27:30:27:34 | access to local variable sayHi | $@ flows to here and is written to HTML or JavaScript: System.Web.WebPages.WebPage.WriteLiteral() method. | XSSAspNet.cs:20:25:20:43 | access to property QueryString | User-provided value | +| XSSAspNet.cs:37:40:37:44 | access to local variable sayHi | $@ flows to here and is written to HTML or JavaScript: System.Web.WebPages.WebPage.WriteLiteralTo() method. | XSSAspNet.cs:20:25:20:43 | access to property QueryString | User-provided value | +| XSSAspNet.cs:44:28:44:55 | access to indexer | $@ flows to here and is written to HTML or JavaScript. | XSSAspNet.cs:44:28:44:46 | access to property QueryString | User-provided value | +| XSSAspNetCore.cs:21:52:21:76 | call to operator implicit conversion | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:21:52:21:64 | access to property Query | User-provided value | +| XSSAspNetCore.cs:44:51:44:53 | access to parameter foo | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:40:56:40:58 | foo | User-provided value | +| XSSAspNetCore.cs:51:43:51:67 | access to property Value | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:51:43:51:67 | access to property Value | User-provided value | +| XSSAspNetCore.cs:58:43:58:73 | call to method ToString | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:58:43:58:55 | access to property Query | User-provided value | +| XSSAspNetCore.cs:61:44:61:66 | access to indexer | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:61:44:61:56 | access to property Query | User-provided value | +| XSSAspNetCore.cs:69:43:69:61 | access to property ContentType | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:69:43:69:61 | access to property ContentType | User-provided value | +| XSSAspNetCore.cs:72:51:72:72 | call to operator implicit conversion | $@ flows to here and is written to HTML or JavaScript. | XSSAspNetCore.cs:72:51:72:65 | access to property Headers | User-provided value | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.qlref b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.qlref new file mode 100644 index 000000000000..faad1d6403c1 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSS.qlref @@ -0,0 +1 @@ +Security Features/CWE-079/XSS.ql \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNet.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNet.cs new file mode 100644 index 000000000000..247b681e8272 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNet.cs @@ -0,0 +1,51 @@ +// semmle-extractor-options: /r:System.Dynamic.Runtime.dll /r:System.Runtime.Extensions.dll /r:System.Linq.Expressions.dll +namespace ASP +{ + using System; + using System.IO; + using System.Net; + using System.Web; + using System.Web.WebPages; + + public class _Page_Views_Home_Contact_cshtml : System.Web.Mvc.WebViewPage + { + public _Page_Views_Home_Contact_cshtml() + { + } + + public override void Execute() + { + Layout = "~/_SiteLayout.cshtml"; + Page.Title = "Contact"; + var sayHi = Request.QueryString["sayHi"]; + if (sayHi.IsEmpty()) + { + WriteLiteral(""); // GOOD: hard-coded, not user input + } + else + { + WriteLiteral(sayHi); // BAD: user input flows to HTML unencoded + WriteLiteral(HttpUtility.HtmlEncode(sayHi)); // Good: user input is encoded before it flows to HTML + } + + if (sayHi.IsEmpty()) + { + WriteLiteralTo(Output, ""); // GOOD: hard-coded, not user input + } + else + { + WriteLiteralTo(Output, sayHi); // BAD: user input flows to HTML unencoded + WriteLiteralTo(Output, Html.Encode(sayHi)); // Good: user input is encoded before it flows to HTML + } + + BeginContext("~/Views/Home/Contact.cshtml", 288, 32, false); + + Write(Html.Raw("")); // GOOD: hard-coded, not user input + Write(Html.Raw(Request.QueryString["sayHi"])); // BAD: user input flows to HTML unencoded + Write(Html.Raw(HttpContext.Current.Server.HtmlEncode(Request.QueryString["sayHi"]))); // Good: user input is encoded before it flows to HTML + EndContext("~/Views/Home/Contact.cshtml", 288, 32, false); + } + } +} + +// source-extractor-options: /r:${testdir}/../../../../../packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.dll /r:${testdir}/../../../../../packages/Microsoft.AspNet.Mvc.5.2.3/lib/net45/System.Web.Mvc.dll /r:System.Dynamic.Runtime.dll /r:System.Runtime.Extensions.dll /r:System.Linq.Expressions.dll /r:System.Web.dll /r:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Web.dll /r:System.Collections.Specialized.dll \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNetCore.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNetCore.cs new file mode 100644 index 000000000000..98cda988da87 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/XSSAspNetCore.cs @@ -0,0 +1,79 @@ +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Html; +using Microsoft.Extensions.Primitives; + +namespace Testing.Controllers +{ + public class HomeViewModel + { + public string Message { get; set; } + + public HtmlString Description { get; set; } + } + + public class TestController : Controller + { + public IActionResult Index() + { + // BAD: flow of content type to. + var v = new ViewResult(); + v.ViewData["BadData"] = new HtmlString(Request.Query["Bad data"]); + + StringValues vOut; + Request.Query.TryGetValue("Foo", out vOut); + + // BAD: via Enumerable. (false negative) + v.ViewData["FooFirst"] = new HtmlString(vOut.First()); + + // BAD: via toArray. (false negative) + v.ViewData["FooArray0"] = new HtmlString(vOut.ToArray()[0]); + + // BAD: via implicit conversion operator. (false negative) + v.ViewData["FooImplicit"] = new HtmlString(vOut); + + return v; + } + + [HttpPost("Test")] + [ValidateAntiForgeryToken] + public IActionResult Submit([FromQuery] string foo) + { + var view = new ViewResult(); + //BAD: flow of submitted value to view in HtmlString. + view.ViewData["FOO"] = new HtmlString(foo); + return view; + } + + public IActionResult IndexToModel() + { + //BAD: flow of submitted value to view in HtmlString. + HtmlString v = new HtmlString(Request.QueryString.Value); + return View(new HomeViewModel() { Message = "Message from Index", Description = v }); + } + + public IActionResult About() + { + //BAD: flow of submitted value to view in HtmlString. + HtmlString v = new HtmlString(Request.Query["Foo"].ToString()); + + //BAD: flow of submitted value to view in HtmlString. + HtmlString v1 = new HtmlString(Request.Query["Foo"][0]); + + return View(new HomeViewModel() { Message = "Message from About", Description = v }); + } + + public IActionResult Contact() + { + //BAD: flow of user content type to view in HtmlString. + HtmlString v = new HtmlString(Request.ContentType); + + //BAD: flow of headers to view in HtmlString. + HtmlString v1 = new HtmlString(value: Request.Headers["Foo"]); + + return View(new HomeViewModel() { Message = "Message from Contact", Description = v }); + } + } +} + +// initial-extractor-options: /r:netstandard.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Mvc.1.1.3/lib/net451/Microsoft.AspNetCore.Mvc.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Mvc.Core.1.1.3/lib/net451/Microsoft.AspNetCore.Mvc.Core.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Antiforgery.1.1.2/lib/net451/Microsoft.AspNetCore.Antiforgery.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Mvc.ViewFeatures.1.1.3/lib/net451/Microsoft.AspNetCore.Mvc.ViewFeatures.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Mvc.Abstractions.1.1.3/lib/net451/Microsoft.AspNetCore.Mvc.Abstractions.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Http.Abstractions.1.1.2\lib\net451\Microsoft.AspNetCore.Http.Abstractions.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Html.Abstractions.1.1.2/lib/netstandard1.0/Microsoft.AspNetCore.Html.Abstractions.dll /r:${testdir}/../../../../../packages/Microsoft.AspNetCore.Http.Features.1.1.2\lib\net451\Microsoft.AspNetCore.Http.Features.dll /r:${testdir}/../../../../../packages\Microsoft.Extensions.Primitives.2.1.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll /r:System.Linq.dll /r:System.Linq.Expressions.dll /r:System.Linq.Queryable.dll \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/corestubs.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/corestubs.cs new file mode 100644 index 000000000000..910ef14f5be6 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/corestubs.cs @@ -0,0 +1,236 @@ +namespace Microsoft +{ + namespace AspNetCore + { + namespace Html + { + // Generated from `Microsoft.AspNetCore.Html.HtmlString` in `Microsoft.AspNetCore.Html.Abstractions, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class HtmlString : Microsoft.AspNetCore.Html.IHtmlContent + { + public HtmlString(string value) => throw null; + public override string ToString() => throw null; + } + + // Generated from `Microsoft.AspNetCore.Html.IHtmlContent` in `Microsoft.AspNetCore.Html.Abstractions, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IHtmlContent + { + } + + } + namespace Http + { + // Generated from `Microsoft.AspNetCore.Http.HttpRequest` in `Microsoft.AspNetCore.Http.Abstractions, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + abstract public class HttpRequest + { + public abstract Microsoft.AspNetCore.Http.IHeaderDictionary Headers { get; } + public abstract Microsoft.AspNetCore.Http.IQueryCollection Query { get; set; } + public abstract Microsoft.AspNetCore.Http.QueryString QueryString { get; set; } + public abstract string ContentType { get; set; } + } + + // Generated from `Microsoft.AspNetCore.Http.IHeaderDictionary` in `Microsoft.AspNetCore.Http.Features, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IHeaderDictionary : System.Collections.IEnumerable, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IDictionary, System.Collections.Generic.ICollection> + { + Microsoft.Extensions.Primitives.StringValues this[string key] { get; set; } + } + + // Generated from `Microsoft.AspNetCore.Http.IQueryCollection` in `Microsoft.AspNetCore.Http.Features, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IQueryCollection : System.Collections.IEnumerable, System.Collections.Generic.IEnumerable> + { + Microsoft.Extensions.Primitives.StringValues this[string key] { get; } + bool TryGetValue(string key, out Microsoft.Extensions.Primitives.StringValues value); + } + + // Generated from `Microsoft.AspNetCore.Http.QueryString` in `Microsoft.AspNetCore.Http.Abstractions, Version=1.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public struct QueryString : System.IEquatable + { + public bool Equals(Microsoft.AspNetCore.Http.QueryString other) => throw null; + public override bool Equals(object obj) => throw null; + public override int GetHashCode() => throw null; + public override string ToString() => throw null; + public string Value { get => throw null; } + } + + } + namespace Mvc + { + // Generated from `Microsoft.AspNetCore.Mvc.ActionResult` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + abstract public class ActionResult : Microsoft.AspNetCore.Mvc.IActionResult + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.ControllerBase` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + abstract public class ControllerBase + { + public Microsoft.AspNetCore.Http.HttpRequest Request { get => throw null; } + } + + // Generated from `Microsoft.AspNetCore.Mvc.Controller` in `Microsoft.AspNetCore.Mvc.ViewFeatures, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + abstract public class Controller : Microsoft.AspNetCore.Mvc.ControllerBase, System.IDisposable, Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata, Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter, Microsoft.AspNetCore.Mvc.Filters.IActionFilter + { + public virtual Microsoft.AspNetCore.Mvc.ViewResult View(object model) => throw null; + public void Dispose() => throw null; + } + + // Generated from `Microsoft.AspNetCore.Mvc.FromQueryAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class FromQueryAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider, Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata + { + public FromQueryAttribute() => throw null; + } + + // Generated from `Microsoft.AspNetCore.Mvc.HttpPostAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class HttpPostAttribute : Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute + { + public HttpPostAttribute() => throw null; + public HttpPostAttribute(string template) => throw null; + } + + // Generated from `Microsoft.AspNetCore.Mvc.IActionResult` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IActionResult + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute` in `Microsoft.AspNetCore.Mvc.ViewFeatures, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class ValidateAntiForgeryTokenAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter, Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata, Microsoft.AspNetCore.Mvc.Filters.IFilterFactory + { + public ValidateAntiForgeryTokenAttribute() => throw null; + } + + // Generated from `Microsoft.AspNetCore.Mvc.ViewResult` in `Microsoft.AspNetCore.Mvc.ViewFeatures, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class ViewResult : Microsoft.AspNetCore.Mvc.ActionResult + { + public Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData { get => throw null; set => throw null; } + public ViewResult() => throw null; + } + + namespace Filters + { + // Generated from `Microsoft.AspNetCore.Mvc.Filters.IActionFilter` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IActionFilter : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IAsyncActionFilter : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Filters.IFilterFactory` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IFilterFactory : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IFilterMetadata + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IOrderedFilter : Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata + { + } + + } + namespace ModelBinding + { + // Generated from `Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IBindingSourceMetadata + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IModelNameProvider + { + } + + } + namespace Routing + { + // Generated from `Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + abstract public class HttpMethodAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider, Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IActionHttpMethodProvider + { + } + + // Generated from `Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider` in `Microsoft.AspNetCore.Mvc.Core, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public interface IRouteTemplateProvider + { + } + + } + namespace ViewFeatures + { + // Generated from `Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary` in `Microsoft.AspNetCore.Mvc.ViewFeatures, Version=1.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public class ViewDataDictionary : System.Collections.IEnumerable, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IDictionary, System.Collections.Generic.ICollection> + { + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() => throw null; + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null; + public System.Collections.Generic.ICollection Values { get => throw null; } + public System.Collections.Generic.ICollection Keys { get => throw null; } + public bool Contains(System.Collections.Generic.KeyValuePair item) => throw null; + public bool ContainsKey(string key) => throw null; + public bool IsReadOnly { get => throw null; } + public bool Remove(System.Collections.Generic.KeyValuePair item) => throw null; + public bool Remove(string key) => throw null; + public bool TryGetValue(string key, out object value) => throw null; + public int Count { get => throw null; } + public object this[string index] { get => throw null; set => throw null; } + public void Add(System.Collections.Generic.KeyValuePair item) => throw null; + public void Add(string key, object value) => throw null; + public void Clear() => throw null; + public void CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) => throw null; + } + + } + } + } + namespace Extensions + { + namespace Primitives + { + // Generated from `Microsoft.Extensions.Primitives.StringValues` in `Microsoft.Extensions.Primitives, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` + public struct StringValues : System.IEquatable, System.IEquatable, System.IEquatable, System.Collections.IEnumerable, System.Collections.Generic.IReadOnlyList, System.Collections.Generic.IReadOnlyCollection, + System.Collections.Generic.IList, System.Collections.Generic.IEnumerable, System.Collections.Generic.ICollection + { + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() => throw null; + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null; + bool System.Collections.Generic.ICollection.Contains(string item) => throw null; + bool System.Collections.Generic.ICollection.IsReadOnly { get => throw null; } + bool System.Collections.Generic.ICollection.Remove(string item) => throw null; + int System.Collections.Generic.IList.IndexOf(string item) => throw null; + public bool Equals(Microsoft.Extensions.Primitives.StringValues other) => throw null; + public bool Equals(string other) => throw null; + public bool Equals(string[] other) => throw null; + public int Count { get => throw null; } + public override bool Equals(object obj) => throw null; + public override int GetHashCode() => throw null; + public override string ToString() => throw null; + public static implicit operator string(Microsoft.Extensions.Primitives.StringValues values) => throw null; + public string this[int index] { get => throw null; set => throw null; } + public string[] ToArray() => throw null; + void System.Collections.Generic.ICollection.Add(string item) => throw null; + void System.Collections.Generic.ICollection.Clear() => throw null; + void System.Collections.Generic.ICollection.CopyTo(string[] array, int arrayIndex) => throw null; + void System.Collections.Generic.IList.Insert(int index, string item) => throw null; + void System.Collections.Generic.IList.RemoveAt(int index) => throw null; + } + + } + } +} +namespace System +{ + namespace Linq + { + // Generated from `System.Linq.Enumerable` in `System.Linq, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` + static public class Enumerable + { + public static TSource First(this System.Collections.Generic.IEnumerable source) => throw null; + } + + } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/stubs.cs b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/stubs.cs new file mode 100644 index 000000000000..d58af2a69087 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-079/XSS/stubs.cs @@ -0,0 +1,153 @@ +namespace System +{ +namespace Collections +{ +namespace Specialized +{ +// Generated from `System.Collections.Specialized.NameObjectCollectionBase` in `System.Collections.Specialized, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +abstract public class NameObjectCollectionBase : System.Runtime.Serialization.ISerializable, System.Runtime.Serialization.IDeserializationCallback, System.Collections.IEnumerable, System.Collections.ICollection +{ + bool System.Collections.ICollection.IsSynchronized { get => throw null; } + object System.Collections.ICollection.SyncRoot { get => throw null; } + public virtual System.Collections.IEnumerator GetEnumerator() => throw null; + public virtual int Count { get => throw null; } + public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) => throw null; + public virtual void OnDeserialization(object sender) => throw null; + void System.Collections.ICollection.CopyTo(System.Array array, int index) => throw null; +} + +// Generated from `System.Collections.Specialized.NameValueCollection` in `System.Collections.Specialized, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public class NameValueCollection : System.Collections.Specialized.NameObjectCollectionBase +{ + public string this[string name] { get => throw null; set => throw null; } +} + +} +} +namespace IO +{ +// Generated from `System.IO.TextWriter` in `System.Runtime.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +abstract public class TextWriter : System.MarshalByRefObject, System.IDisposable +{ + public void Dispose() => throw null; +} + +} +namespace Web +{ +// Generated from `System.Web.HttpContext` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public class HttpContext : System.Web.IPrincipalContainer +{ + public System.Web.HttpServerUtility Server { get => throw null; } + public static System.Web.HttpContext Current { get => throw null; set => throw null; } +} + +// Generated from `System.Web.HttpRequestBase` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +abstract public class HttpRequestBase +{ + public virtual System.Collections.Specialized.NameValueCollection QueryString { get => throw null; } +} + +// Generated from `System.Web.HttpServerUtility` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public class HttpServerUtility +{ + public string HtmlEncode(string s) => throw null; +} + +// Generated from `System.Web.HttpUtility` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public class HttpUtility +{ + public static string HtmlEncode(string s) => throw null; +} + +// Generated from `System.Web.IHtmlString` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public interface IHtmlString +{ +} + +// Generated from `System.Web.IPrincipalContainer` in `System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +interface IPrincipalContainer +{ +} + +namespace Mvc +{ +// Generated from `System.Web.Mvc.HtmlHelper<>` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +public class HtmlHelper : System.Web.Mvc.HtmlHelper +{ +} + +// Generated from `System.Web.Mvc.HtmlHelper` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +public class HtmlHelper +{ + public System.Web.IHtmlString Raw(string value) => throw null; + public string Encode(string value) => throw null; +} + +// Generated from `System.Web.Mvc.IViewDataContainer` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +public interface IViewDataContainer +{ +} + +// Generated from `System.Web.Mvc.IViewStartPageChild` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +interface IViewStartPageChild +{ +} + +// Generated from `System.Web.Mvc.WebViewPage<>` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +abstract public class WebViewPage : System.Web.Mvc.WebViewPage +{ + public System.Web.Mvc.HtmlHelper Html { get => throw null; set => throw null; } +} + +// Generated from `System.Web.Mvc.WebViewPage` in `System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +abstract public class WebViewPage : System.Web.WebPages.WebPageBase, System.Web.Mvc.IViewStartPageChild, System.Web.Mvc.IViewDataContainer +{ +} + +} +namespace WebPages +{ +// Generated from `System.Web.WebPages.ITemplateFile` in `System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +public interface ITemplateFile +{ +} + +// Generated from `System.Web.WebPages.StringExtensions` in `System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +static public class StringExtensions +{ + public static bool IsEmpty(this string value) => throw null; +} + +// Generated from `System.Web.WebPages.WebPageBase` in `System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +abstract public class WebPageBase : System.Web.WebPages.WebPageRenderingBase +{ + public System.IO.TextWriter Output { get => throw null; } + public override dynamic Page { get => throw null; } + public override string Layout { get => throw null; set => throw null; } + public override void Write(object value) => throw null; + public override void WriteLiteral(object value) => throw null; +} + +// Generated from `System.Web.WebPages.WebPageExecutingBase` in `System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +abstract public class WebPageExecutingBase +{ + protected void BeginContext(string virtualPath, int startPosition, int length, bool isLiteral) => throw null; + protected void EndContext(string virtualPath, int startPosition, int length, bool isLiteral) => throw null; + public abstract void Execute(); + public abstract void Write(object value); + public abstract void WriteLiteral(object value); + public static void WriteLiteralTo(System.IO.TextWriter writer, object content) => throw null; +} + +// Generated from `System.Web.WebPages.WebPageRenderingBase` in `System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35` +abstract public class WebPageRenderingBase : System.Web.WebPages.WebPageExecutingBase, System.Web.WebPages.ITemplateFile +{ + public abstract dynamic Page { get; } + public abstract string Layout { get; set; } + public virtual System.Web.HttpRequestBase Request { get => throw null; } +} + +} +} +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.expected b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.expected deleted file mode 100644 index ed5ddab84070..000000000000 --- a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.expected +++ /dev/null @@ -1,4 +0,0 @@ -| UrlRedirect.cs:14:31:14:61 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:14:31:14:53 | access to property QueryString | user-provided value | -| UrlRedirect.cs:39:44:39:74 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:39:44:39:66 | access to property QueryString | user-provided value | -| UrlRedirect.cs:40:47:40:77 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:40:47:40:69 | access to property QueryString | user-provided value | -| UrlRedirect.cs:49:29:49:31 | access to local variable url | Untrusted URL redirection due to $@. | UrlRedirect.cs:24:22:24:44 | access to property QueryString | user-provided value | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.cs b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs similarity index 95% rename from csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.cs rename to csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs index 4ced49db6a83..8378e6743d90 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs @@ -1,4 +1,4 @@ -// semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll +// semmle-extractor-options: ${testdir}/../../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll using System; using System.Web; diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected new file mode 100644 index 000000000000..eec21ad90a13 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected @@ -0,0 +1,14 @@ +| UrlRedirect.cs:14:31:14:61 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:14:31:14:53 | access to property QueryString | user-provided value | +| UrlRedirect.cs:39:44:39:74 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:39:44:39:66 | access to property QueryString | user-provided value | +| UrlRedirect.cs:40:47:40:77 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:40:47:40:69 | access to property QueryString | user-provided value | +| UrlRedirect.cs:49:29:49:31 | access to local variable url | Untrusted URL redirection due to $@. | UrlRedirect.cs:24:22:24:44 | access to property QueryString | user-provided value | +| UrlRedirectCore.cs:18:22:18:26 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:21:44:21:48 | call to operator implicit conversion | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:27:46:27:50 | call to operator implicit conversion | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:33:66:33:70 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:36:49:36:53 | call to operator implicit conversion | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:39:69:39:73 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:42:39:42:53 | ... + ... | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:15:44:15:48 | value | user-provided value | +| UrlRedirectCore.cs:50:28:50:32 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:47:51:47:55 | value | user-provided value | +| UrlRedirectCore.cs:55:32:55:45 | object creation of type Uri | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:47:51:47:55 | value | user-provided value | +| UrlRedirectCore.cs:58:31:58:35 | access to parameter value | Untrusted URL redirection due to $@. | UrlRedirectCore.cs:47:51:47:55 | value | user-provided value | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.qlref b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.qlref similarity index 100% rename from csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect.qlref rename to csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.qlref diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirectCore.cs b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirectCore.cs new file mode 100644 index 000000000000..ee530f5f0a43 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirectCore.cs @@ -0,0 +1,67 @@ +// semmle-extractor-options: /r:System.Private.Uri.dll + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Mvc; + +namespace Testing.Controllers +{ + public class SomeController : ControllerBase + { + private static string SomeValue = "HeaderValue"; + + [HttpPost] + public void Post([FromBody] string value) + { + // BAD: straight up controller redirect + Redirect(value); + + // BAD: Setting response headers collection, location = redirect + Response.Headers["location"] = value; + + // GOOD: Setting response header to a constant value + Response.Headers["location"] = SomeValue; + + // BAD: Setting response headers collection, location = redirect via add method + Response.Headers.Add("location", value); + + // GOOD: Setting response header to a constant value + Response.Headers.Add("location", "foo"); + + // BAD: redirect via location + Response.Headers.SetCommaSeparatedValues("location", value); + + // BAD = redirect via setting location value from tainted source + Response.Headers.Append("location", value); + + // BAD: redirect via setting location header from comma-separated values + Response.Headers.AppendCommaSeparatedValues("location", value); + + // BAD: tainted redirect to Action + RedirectToActionPermanent("Error" + value); + } + + // PUT: api/Some/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + + RedirectToPage(value); + + var headers = new ResponseHeaders(Response.Headers); + + // BAD: redirect via header helper class + headers.Location = new Uri(value); + + // BAD: response redirect + Response.Redirect(value); + + // GOOD: whitelisted redirect + if(Url.IsLocalUrl(value)) + Redirect(value); + } + } +} + +// original-extractor-options: /r:netstandard.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Mvc.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Mvc.Core.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Http.Extensions.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Http.Abstractions.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Mvc.Abstractions.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Abstractions.dll /r:${testdir}/../../../../../packages\Microsoft.AspNetCore.Http.Features.2.1.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll /r:${testdir}/../../../../../packages\Microsoft.Extensions.Primitives.2.1.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll /r:System.Private.Uri.dll diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/stubs.cs b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/stubs.cs new file mode 100644 index 000000000000..1dff89716e01 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/stubs.cs @@ -0,0 +1,182 @@ +namespace Microsoft +{ +namespace AspNetCore +{ +namespace Http +{ +// Generated from `Microsoft.AspNetCore.Http.HeaderDictionaryExtensions` in `Microsoft.AspNetCore.Http.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +static public class HeaderDictionaryExtensions +{ + public static void Append(this Microsoft.AspNetCore.Http.IHeaderDictionary headers, string key, Microsoft.Extensions.Primitives.StringValues value) => throw null; + public static void AppendCommaSeparatedValues(this Microsoft.AspNetCore.Http.IHeaderDictionary headers, string key, params string[] values) => throw null; + public static void SetCommaSeparatedValues(this Microsoft.AspNetCore.Http.IHeaderDictionary headers, string key, params string[] values) => throw null; +} + +// Generated from `Microsoft.AspNetCore.Http.HttpResponse` in `Microsoft.AspNetCore.Http.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +abstract public class HttpResponse +{ + public abstract Microsoft.AspNetCore.Http.IHeaderDictionary Headers { get; } + public virtual void Redirect(string location) => throw null; +} + +// Generated from `Microsoft.AspNetCore.Http.IHeaderDictionary` in `Microsoft.AspNetCore.Http.Features, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IHeaderDictionary : System.Collections.IEnumerable, System.Collections.Generic.IEnumerable>, System.Collections.Generic.IDictionary, System.Collections.Generic.ICollection> +{ + Microsoft.Extensions.Primitives.StringValues this[string key] { get; set; } +} + +namespace Headers +{ +// Generated from `Microsoft.AspNetCore.Http.Headers.ResponseHeaders` in `Microsoft.AspNetCore.Http.Extensions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class ResponseHeaders +{ + public ResponseHeaders(Microsoft.AspNetCore.Http.IHeaderDictionary headers) => throw null; + public System.Uri Location { get => throw null; set => throw null; } +} + +} +} +namespace Mvc +{ +// Generated from `Microsoft.AspNetCore.Mvc.ActionResult` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +abstract public class ActionResult : Microsoft.AspNetCore.Mvc.IActionResult +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.ControllerBase` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +abstract public class ControllerBase +{ + public Microsoft.AspNetCore.Http.HttpResponse Response { get => throw null; } + public Microsoft.AspNetCore.Mvc.IUrlHelper Url { get => throw null; set => throw null; } + public virtual Microsoft.AspNetCore.Mvc.RedirectResult Redirect(string url) => throw null; + public virtual Microsoft.AspNetCore.Mvc.RedirectToActionResult RedirectToActionPermanent(string actionName) => throw null; + public virtual Microsoft.AspNetCore.Mvc.RedirectToPageResult RedirectToPage(string pageName) => throw null; +} + +// Generated from `Microsoft.AspNetCore.Mvc.FromBodyAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class FromBodyAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata +{ + public FromBodyAttribute() => throw null; +} + +// Generated from `Microsoft.AspNetCore.Mvc.HttpPostAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class HttpPostAttribute : Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute +{ + public HttpPostAttribute() => throw null; + public HttpPostAttribute(string template) => throw null; +} + +// Generated from `Microsoft.AspNetCore.Mvc.HttpPutAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class HttpPutAttribute : Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute +{ + public HttpPutAttribute() => throw null; + public HttpPutAttribute(string template) => throw null; +} + +// Generated from `Microsoft.AspNetCore.Mvc.IActionResult` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IActionResult +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.IUrlHelper` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IUrlHelper +{ + bool IsLocalUrl(string url); +} + +// Generated from `Microsoft.AspNetCore.Mvc.RedirectResult` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class RedirectResult : Microsoft.AspNetCore.Mvc.ActionResult, Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult, Microsoft.AspNetCore.Mvc.IActionResult +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.RedirectToActionResult` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class RedirectToActionResult : Microsoft.AspNetCore.Mvc.ActionResult, Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult, Microsoft.AspNetCore.Mvc.IActionResult +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.RedirectToPageResult` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public class RedirectToPageResult : Microsoft.AspNetCore.Mvc.ActionResult, Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult, Microsoft.AspNetCore.Mvc.IActionResult +{ +} + +namespace ModelBinding +{ +// Generated from `Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata` in `Microsoft.AspNetCore.Mvc.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IBindingSourceMetadata +{ +} + +} +namespace Routing +{ +// Generated from `Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +abstract public class HttpMethodAttribute : System.Attribute, Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider, Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IActionHttpMethodProvider +{ +} + +// Generated from `Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IRouteTemplateProvider +{ +} + +} +namespace ViewFeatures +{ +// Generated from `Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult` in `Microsoft.AspNetCore.Mvc.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public interface IKeepTempDataResult : Microsoft.AspNetCore.Mvc.IActionResult +{ +} + +} +} +} +namespace Extensions +{ +namespace Primitives +{ +// Generated from `Microsoft.Extensions.Primitives.StringValues` in `Microsoft.Extensions.Primitives, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60` +public struct StringValues : System.IEquatable, System.IEquatable, System.IEquatable, System.Collections.IEnumerable, System.Collections.Generic.IReadOnlyList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IList, System.Collections.Generic.IEnumerable, System.Collections.Generic.ICollection +{ + System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() => throw null; + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null; + bool System.Collections.Generic.ICollection.Contains(string item) => throw null; + bool System.Collections.Generic.ICollection.IsReadOnly { get => throw null; } + bool System.Collections.Generic.ICollection.Remove(string item) => throw null; + int System.Collections.Generic.IList.IndexOf(string item) => throw null; + public bool Equals(Microsoft.Extensions.Primitives.StringValues other) => throw null; + public bool Equals(string other) => throw null; + public bool Equals(string[] other) => throw null; + public int Count { get => throw null; } + public override bool Equals(object obj) => throw null; + public override int GetHashCode() => throw null; + public override string ToString() => throw null; + public static implicit operator Microsoft.Extensions.Primitives.StringValues(string value) => throw null; + void System.Collections.Generic.ICollection.Add(string item) => throw null; + void System.Collections.Generic.ICollection.Clear() => throw null; + void System.Collections.Generic.ICollection.CopyTo(string[] array, int arrayIndex) => throw null; + void System.Collections.Generic.IList.Insert(int index, string item) => throw null; + void System.Collections.Generic.IList.RemoveAt(int index) => throw null; + public string this[int i] { get => throw null; set => throw null; } +} + +} +} +} +namespace System +{ +// Generated from `System.Uri` in `System.Private.Uri, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a` +public class Uri : System.Runtime.Serialization.ISerializable +{ + public Uri(string uriString) => throw null; + public override bool Equals(object comparand) => throw null; + public override int GetHashCode() => throw null; + public override string ToString() => throw null; + void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) => throw null; +} + +}