-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Changes from all commits
96d11b7
188dbde
dbe0170
cae6f91
a261533
386eb2d
d408ae7
71a08c3
5c7dedf
12e8107
3c49351
ba46eaa
4e0f3a3
0a7e4b6
d4acccb
7d47bff
6d5f903
3f1f83f
563dc62
059d6b0
402ed04
858c0e6
3e889c3
13997ca
bf2d7b3
ec48d0a
04940a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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 { | ||
mr-sherman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
mr-sherman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||
) | ||
} | ||
} | ||
} | ||
|
||
/** 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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", | ||
mr-sherman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"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() | ||
) | ||
mr-sherman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
or | ||
exists(ObjectCreation oc | | ||
oc.getType().hasQualifiedName("ServiceStack.HttpResult") and | ||
this.asExpr() = oc.getArgument(0) | ||
) | ||
Comment on lines
+173
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be converted to the CSV approach. |
||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.