Skip to content

C#: merge ServiceStack feature branch into main (+added support for remote flow sinks) #5494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
96d11b7
Create ServiceStack.qll
Dec 7, 2020
188dbde
Create SQLInjection.ql
Dec 7, 2020
dbe0170
Add files via upload
Dec 7, 2020
cae6f91
Create ServiceStack.qll
Dec 7, 2020
a261533
Delete ServiceStack.qll
Dec 7, 2020
386eb2d
move ServiceStack out of microsoft
johnlugton Dec 15, 2020
d408ae7
Split ServiceStack into modules and incorporate into main lib
johnlugton Dec 15, 2020
71a08c3
Update servicestack lib
Dec 15, 2020
5c7dedf
Update sinks
Dec 16, 2020
12e8107
Add example
Dec 16, 2020
3c49351
Update file
Dec 16, 2020
ba46eaa
Refactor sink
Dec 16, 2020
4e0f3a3
Update sink based on feedback
Dec 16, 2020
0a7e4b6
Update sink based on feedback
Dec 17, 2020
d4acccb
Update sink
Dec 17, 2020
7d47bff
Tidy up ServiceStack.qll
johnlugton Dec 16, 2020
6d5f903
Minor fixes to XSS:
johnlugton Dec 17, 2020
3f1f83f
remove experimental
johnlugton Dec 18, 2020
563dc62
Improve qldoc for ServiceStack.qll
johnlugton Dec 18, 2020
059d6b0
Fix warning in ServiceStack.qll
johnlugton Dec 18, 2020
402ed04
Merge pull request #4844 from johnlugton/servicestack
yo-h Dec 18, 2020
858c0e6
added support for remote flow sinks in the form of parameters to the …
mr-sherman Mar 22, 2021
3e889c3
updated document formatting
mr-sherman Mar 23, 2021
13997ca
feedback from code review
mr-sherman Mar 26, 2021
bf2d7b3
Added IRestClientAsync methods to external location sink. Removed im…
mr-sherman Mar 29, 2021
ec48d0a
Merge remote-tracking branch 'upstream/main' into service-stack-remot…
mr-sherman May 28, 2021
04940a1
Create 2021-07-14-service-stack-support.md
mr-sherman Jul 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions csharp/change-notes/2021-07-14-service-stack-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lgtm,codescanning
* added support for service stack framework with support for SQL injection, XSS and external API calls
179 changes: 179 additions & 0 deletions csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* General modelling of ServiceStack framework including separate modules for:
* - flow sources
* - SQLi sinks
* - XSS sinks
*/

import csharp

/** A class representing a Service */
class ServiceClass extends Class {
ServiceClass() { this.getBaseClass+().getQualifiedName() = "ServiceStack.Service" }

/** Get a method that handles incoming requests */
Method getARequestMethod() {
result = this.getAMethod(["Post", "Get", "Put", "Delete", "Any", "Option", "Head"])
}
}

/** Top-level Request DTO types */
class RequestDTO extends Class {
RequestDTO() {
this.getABaseInterface().getQualifiedName() =
["ServiceStack.IReturn", "ServieStack.IReturnVoid"]
}
}

/** Top-level Response DTO types */
class ResponseDTO extends Class {
ResponseDTO() {
exists(RequestDTO req, ConstructedGeneric respInterface |
req.getABaseInterface() = respInterface and
respInterface.getUndecoratedName() = "IReturn" and
respInterface.getATypeArgument() = this
)
}
}

/** Flow sources for the ServiceStack framework */
module Sources {
private import semmle.code.csharp.security.dataflow.flowsources.Remote
private import semmle.code.csharp.commons.Collections

/** Types involved in a RequestDTO. Recurse through props and collection types */
private predicate involvedInRequest(RefType c) {
c instanceof RequestDTO
or
exists(RefType parent, RefType propType | involvedInRequest(parent) |
(propType = parent.getAProperty().getType() or propType = parent.getAField().getType()) and
if propType instanceof CollectionType
then
c = propType.(ConstructedGeneric).getATypeArgument() or
c = propType.(ArrayType).getElementType()
else c = propType
)
}

/**
* Remote flow sources for ServiceStack
*
* Assumes all nested fields/properties on request DTOs are tainted, which is
* an overapproximation and may lead to FPs depending on how Service Stack app
* is configured.
*/
class ServiceStackSource extends RemoteFlowSource {
ServiceStackSource() {
// Parameters are sources. In practice only interesting when they are string/primitive typed.
exists(ServiceClass service |
service.getARequestMethod().getAParameter() = this.asParameter()
)
or
// Field/property accesses on RequestDTOs and request involved types
// involved types aren't necessarily only from requests so may lead to FPs...
exists(RefType reqType | involvedInRequest(reqType) |
reqType.getAProperty().getAnAccess() = this.asExpr() or
reqType.getAField().getAnAccess() = this.asExpr()
)
}

override string getSourceType() { result = "ServiceStack request DTO field" }
}
}

/** Flow Sinks for the ServiceStack framework */
module Sinks {
private import semmle.code.csharp.security.dataflow.flowsinks.ExternalLocationSink

/** RemoteFlow sinks for service stack */
class ServiceStackRemoteRequestParameter extends ExternalLocationSink {
ServiceStackRemoteRequestParameter() {
exists(MethodCall mc |
mc.getTarget().getQualifiedName() in [
"ServiceStack.IRestClient.Get", "ServiceStack.IRestClient.Put",
"ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Delete",
"ServiceStack.IRestClient.Patch", "ServiceStack.IRestClient.Send",
"ServiceStack.IRestClientAsync.GetAsync","ServiceStack.IRestClientAsync.DeleteAsync",
"ServiceStack.IRestClientAsync.PutAsync","ServiceStack.IRestClientAsync.PostAsync",
"ServiceStack.IRestClientAsync.PatchAsync","ServiceStack.IRestClientAsync.CustomMethodAsync"
] and
this.asExpr() = mc.getAnArgument()
Comment on lines +89 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably convert these to the new data-driven approach, lines would look something like:
ServiceStack;IRestClient;true;Get<>;;;Argument[0];external

)
}
}
}

/** SQLi support for the ServiceStack framework */
module SQL {
private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection

/** SQLi sinks for ServiceStack */
class ServiceStackSink extends Sink {
ServiceStackSink() {
exists(MethodCall mc, Method m, int p |
(mc.getTarget() = m.getAnOverrider*() or mc.getTarget() = m.getAnImplementor*()) and
sqlSinkParam(m, p) and
mc.getArgument(p) = this.asExpr()
)
}
Comment on lines +111 to +118
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could cover all the below with CSV lines.

}

private predicate sqlSinkParam(Method m, int p) {
exists(RefType cls | cls = m.getDeclaringType() |
// if using the typed query builder api, only need to worry about Unsafe variants
cls.getQualifiedName() =
["ServiceStack.OrmLite.SqlExpression", "ServiceStack.OrmLite.IUntypedSqlExpression"] and
m.getName().matches("Unsafe%") and
p = 0
or
// Read api - all string typed 1st params are potential sql sinks. They should be templates, not directly user controlled.
cls.getQualifiedName() =
[
"ServiceStack.OrmLite.OrmLiteReadApi", "ServiceStack.OrmLite.OrmLiteReadExpressionsApi",
"ServiceStack.OrmLite.OrmLiteReadApiAsync",
"ServiceStack.OrmLite.OrmLiteReadExpressionsApiAsync"
] and
m.getParameter(p).getType() instanceof StringType and
p = 1
or
// Write API - only 2 methods that take string
cls.getQualifiedName() =
["ServiceStack.OrmLite.OrmLiteWriteApi", "ServiceStack.OrmLite.OrmLiteWriteApiAsync"] and
m.getName() = ["ExecuteSql", "ExecuteSqlAsync"] and
p = 1
or
// NoSQL sinks in redis client. TODO should these be separate query?
cls.getQualifiedName() = "ServiceStack.Redis.IRedisClient" and
(
m.getName() = ["Custom", "LoadLuaScript"]
or
m.getName().matches("%Lua%") and not m.getName().matches("%Sha%")
) and
p = 0
// TODO
// ServiceStack.OrmLite.OrmLiteUtils.SqlColumn - what about other similar classes?
// couldn't find CustomSelect
// need to handle "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable"
)
}
}

/** XSS support for ServiceStack framework */
module XSS {
private import semmle.code.csharp.security.dataflow.XSS::XSS

/** XSS sinks for ServiceStack */
class XssSink extends Sink {
XssSink() {
exists(ServiceClass service, ReturnStmt r |
this.asExpr() = r.getExpr() and
r.getEnclosingCallable() = service.getARequestMethod()
)
or
exists(ObjectCreation oc |
oc.getType().hasQualifiedName("ServiceStack.HttpResult") and
this.asExpr() = oc.getArgument(0)
)
Comment on lines +173 to +176
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be converted to the CSV approach.

}
}
}
1 change: 1 addition & 0 deletions csharp/ql/src/semmle/code/csharp/frameworks/Sql.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ private import semmle.code.csharp.frameworks.system.Data
private import semmle.code.csharp.frameworks.system.data.SqlClient
private import semmle.code.csharp.frameworks.EntityFramework
private import semmle.code.csharp.frameworks.NHibernate
private import semmle.code.csharp.frameworks.ServiceStack::SQL
private import semmle.code.csharp.frameworks.Dapper
private import semmle.code.csharp.dataflow.DataFlow4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module XSS {
import semmle.code.csharp.frameworks.system.Net
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.frameworks.system.web.UI
import semmle.code.csharp.frameworks.ServiceStack::XSS
import semmle.code.csharp.security.Sanitizers
import semmle.code.csharp.security.dataflow.flowsinks.Html
import semmle.code.csharp.security.dataflow.flowsinks.Remote
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import csharp
private import Remote
private import semmle.code.csharp.commons.Loggers
private import semmle.code.csharp.frameworks.system.Web

private import semmle.code.csharp.frameworks.ServiceStack::Sinks
/**
* An external location sink.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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
import semmle.code.csharp.frameworks.ServiceStack::Sources

/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
Expand Down