Merge pull request #21015 from aschackmull/go/mad-barriers

Go: Support for MaD barriers and barrier guards.
This commit is contained in:
Owen Mansel-Chan
2026-01-13 09:31:09 +00:00
committed by GitHub
24 changed files with 261 additions and 107 deletions

View File

@@ -50,3 +50,8 @@ extensions:
- ["group:beego", "Controller", True, "GetString", "", "", "ReturnValue[0]", "remote", "manual"]
- ["group:beego", "Controller", True, "GetStrings", "", "", "ReturnValue[0]", "remote", "manual"]
- ["group:beego", "Controller", True, "Input", "", "", "ReturnValue[0]", "remote", "manual"]
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
- ["group:beego", "", True, "Htmlquote", "", "", "ReturnValue", "html-injection", "manual"]

View File

@@ -1,4 +1,21 @@
extensions:
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
# The only way to create a `mime/multipart.FileHeader` is to create a
# `mime/multipart.Form`, which creates the `Filename` field of each
# `mime/multipart.FileHeader` by calling `Part.FileName`, which calls
# `path/filepath.Base` on its return value. In general `path/filepath.Base`
# is not a sanitizer for path traversal, but in this specific case where the
# output is going to be used as a filename rather than a directory name, it
# is adequate.
- ["mime/multipart", "FileHeader", False, "Filename", "", "", "", "path-injection", "manual"]
# `Part.FileName` calls `path/filepath.Base` on its return value. In
# general `path/filepath.Base` is not a sanitizer for path traversal, but in
# this specific case where the output is going to be used as a filename
# rather than a directory name, it is adequate.
- ["mime/multipart", "Part", False, "FileName", "", "", "ReturnValue", "path-injection", "manual"]
- addsTo:
pack: codeql/go-all
extensible: summaryModel

View File

@@ -1,4 +1,9 @@
extensions:
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
- ["path/filepath", "", False, "Rel", "", "", "ReturnValue", "path-injection", "manual"]
- addsTo:
pack: codeql/go-all
extensible: summaryModel

View File

@@ -116,10 +116,10 @@ module FileSystemAccess {
}
}
private class DefaultFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
private class ExternalFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
DataFlow::ArgumentNode pathArgument;
DefaultFileSystemAccess() {
ExternalFileSystemAccess() {
sinkNode(pathArgument, "path-injection") and
this = pathArgument.getCall()
}
@@ -394,10 +394,10 @@ module LoggerCall {
}
}
private class DefaultLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
private class ExternalLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
DataFlow::ArgumentNode messageComponent;
DefaultLoggerCall() {
ExternalLoggerCall() {
sinkNode(messageComponent, "log-injection") and
this = messageComponent.getCall()
}

View File

@@ -320,11 +320,11 @@ module Http {
)
}
private class DefaultHttpRedirect extends Range, DataFlow::CallNode {
private class ExternalHttpRedirect extends Range, DataFlow::CallNode {
DataFlow::ArgumentNode url;
int rw;
DefaultHttpRedirect() {
ExternalHttpRedirect() {
this = url.getCall() and
exists(string kind |
sinkKindInfo(kind, rw) and

View File

@@ -129,7 +129,9 @@ module ModelValidation {
summaryModel(_, _, _, _, _, _, path, _, _, _, _) or
summaryModel(_, _, _, _, _, _, _, path, _, _, _) or
sinkModel(_, _, _, _, _, _, path, _, _, _) or
sourceModel(_, _, _, _, _, _, path, _, _, _)
sourceModel(_, _, _, _, _, _, path, _, _, _) or
barrierModel(_, _, _, _, _, _, path, _, _, _) or
barrierGuardModel(_, _, _, _, _, _, path, _, _, _, _)
}
private module MkAccessPath = AccessPathSyntax::AccessPath<getRelevantAccessPath/1>;
@@ -142,6 +144,8 @@ module ModelValidation {
exists(string pred, AccessPath input, AccessPathToken part |
sinkModel(_, _, _, _, _, _, input, _, _, _) and pred = "sink"
or
barrierGuardModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "barrier guard"
or
summaryModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "summary"
|
(
@@ -164,6 +168,8 @@ module ModelValidation {
exists(string pred, AccessPath output, AccessPathToken part |
sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source"
or
barrierModel(_, _, _, _, _, _, output, _, _, _) and pred = "barrier"
or
summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary"
|
(
@@ -181,7 +187,13 @@ module ModelValidation {
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, _, _, _, _, kind, _, _) }
predicate sinkKind(string kind) { sinkModel(_, _, _, _, _, _, _, kind, _, _) }
predicate sinkKind(string kind) {
sinkModel(_, _, _, _, _, _, _, kind, _, _)
or
barrierModel(_, _, _, _, _, _, _, kind, _, _)
or
barrierGuardModel(_, _, _, _, _, _, _, _, kind, _, _)
}
predicate sourceKind(string kind) { sourceModel(_, _, _, _, _, _, _, kind, _, _) }
@@ -199,6 +211,11 @@ module ModelValidation {
or
sinkModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "sink"
or
barrierModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "barrier"
or
barrierGuardModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
pred = "barrier guard"
or
summaryModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
pred = "summary"
or
@@ -224,6 +241,14 @@ module ModelValidation {
invalidProvenance(provenance) and
result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
)
or
exists(string acceptingvalue |
barrierGuardModel(_, _, _, _, _, _, _, acceptingvalue, _, _, _) and
invalidAcceptingValue(acceptingvalue) and
result =
"Unrecognized accepting value description \"" + acceptingvalue +
"\" in barrier guard model."
)
}
private string getInvalidPackageGroup() {
@@ -232,6 +257,11 @@ module ModelValidation {
or
FlowExtensions::sinkModel(package, _, _, _, _, _, _, _, _, _) and pred = "sink"
or
FlowExtensions::barrierModel(package, _, _, _, _, _, _, _, _, _) and pred = "barrier"
or
FlowExtensions::barrierGuardModel(package, _, _, _, _, _, _, _, _, _, _) and
pred = "barrier guard"
or
FlowExtensions::summaryModel(package, _, _, _, _, _, _, _, _, _, _) and
pred = "summary"
or
@@ -262,6 +292,10 @@ private predicate elementSpec(
or
sinkModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or
barrierModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or
barrierGuardModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or
summaryModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or
neutralModel(package, type, name, signature, _, _) and ext = "" and subtypes = false
@@ -397,6 +431,54 @@ private module Cached {
isSinkNode(n, kind, model) and n.asNode() = node
)
}
private newtype TKindModelPair =
TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) }
private boolean convertAcceptingValue(Public::AcceptingValue av) {
av.isTrue() and result = true
or
av.isFalse() and result = false
// Remaining cases are not supported yet, they depend on the shared Guards library.
// or
// av.isNoException() and result.getDualValue().isThrowsException()
// or
// av.isZero() and result.asIntValue() = 0
// or
// av.isNotZero() and result.getDualValue().asIntValue() = 0
// or
// av.isNull() and result.isNullValue()
// or
// av.isNotNull() and result.isNonNullValue()
}
private predicate barrierGuardChecks(DataFlow::Node g, Expr e, boolean gv, TKindModelPair kmp) {
exists(
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingvalue,
string kind, string model
|
isBarrierGuardNode(n, acceptingvalue, kind, model) and
n.asNode().asExpr() = e and
kmp = TMkPair(kind, model) and
gv = convertAcceptingValue(acceptingvalue)
|
g.asExpr().(CallExpr).getAnArgument() = e // TODO: qualifier?
)
}
/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
cached
predicate barrierNode(DataFlow::Node node, string kind, string model) {
exists(SourceSinkInterpretationInput::InterpretNode n |
isBarrierNode(n, kind, model) and n.asNode() = node
)
or
DataFlow::ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
model)) = node
}
}
import Cached
@@ -413,6 +495,12 @@ predicate sourceNode(DataFlow::Node node, string kind) { sourceNode(node, kind,
*/
predicate sinkNode(DataFlow::Node node, string kind) { sinkNode(node, kind, _) }
/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
predicate barrierNode(DataFlow::Node node, string kind) { barrierNode(node, kind, _) }
// adapter class for converting Mad summaries to `SummarizedCallable`s
private class SummarizedCallableAdapter extends Public::SummarizedCallable {
SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _) }

View File

@@ -339,6 +339,20 @@ class ContentSet instanceof TContentSet {
*/
signature predicate guardChecksSig(Node g, Expr e, boolean branch);
bindingset[this]
private signature class ParamSig;
private module WithParam<ParamSig P> {
/**
* Holds if the guard `g` validates the expression `e` upon evaluating to `branch`.
*
* The expression `e` is expected to be a syntactic part of the guard `g`.
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
* the argument `x`.
*/
signature predicate guardChecksSig(Node g, Expr e, boolean branch, P param);
}
/**
* Provides a set of barrier nodes for a guard that validates an expression.
*
@@ -346,12 +360,36 @@ signature predicate guardChecksSig(Node g, Expr e, boolean branch);
* in data flow and taint tracking.
*/
module BarrierGuard<guardChecksSig/3 guardChecks> {
private predicate guardChecks(Node g, Expr e, boolean branch, Unit param) {
guardChecks(g, e, branch) and exists(param)
}
private module B = ParameterizedBarrierGuard<Unit, guardChecks/4>;
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode() {
Node getABarrierNode() { result = B::getABarrierNode(_) }
/**
* Gets a node that is safely guarded by the given guard check.
*/
Node getABarrierNodeForGuard(Node guardCheck) {
result = B::getABarrierNodeForGuard(guardCheck, _)
}
}
/**
* Provides a set of barrier nodes for a guard that validates an expression.
*
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
* in data flow and taint tracking.
*/
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode(P param) {
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var |
result = pragma[only_bind_out](var).getAUse()
|
guards(_, guard, _, var) and
guards(_, guard, _, var, param) and
pragma[only_bind_out](guard).dominates(result.getBasicBlock())
)
}
@@ -359,9 +397,9 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
/**
* Gets a node that is safely guarded by the given guard check.
*/
Node getABarrierNodeForGuard(Node guardCheck) {
Node getABarrierNodeForGuard(Node guardCheck, P param) {
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var | result = var.getAUse() |
guards(guardCheck, guard, _, var) and
guards(guardCheck, guard, _, var, param) and
guard.dominates(result.getBasicBlock())
)
}
@@ -373,22 +411,24 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
* This predicate exists to enforce a good join order in `getAGuardedNode`.
*/
pragma[noinline]
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap) {
guards(g, guard, nd) and nd = ap.getAUse()
private predicate guards(
Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap, P param
) {
guards(g, guard, nd, param) and nd = ap.getAUse()
}
/**
* Holds if `guard` marks a point in the control-flow graph where `g`
* is known to validate `nd`.
*/
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd) {
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, P param) {
exists(boolean branch |
guardChecks(g, nd.asExpr(), branch) and
guardChecks(g, nd.asExpr(), branch, param) and
guard.ensures(g, branch)
)
or
exists(DataFlow::Property p, Node resNode, Node check, boolean outcome |
guardingCall(g, _, _, _, p, _, nd, resNode) and
guardingCall(g, _, _, _, p, _, nd, resNode, param) and
p.checkOn(check, outcome, resNode) and
guard.ensures(pragma[only_bind_into](check), outcome)
)
@@ -405,9 +445,9 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
pragma[noinline]
private predicate guardingCall(
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, CallNode c,
Node nd, Node resNode
Node nd, Node resNode, P param
) {
guardingFunction(g, f, inp, outp, p) and
guardingFunction(g, f, inp, outp, p, param) and
c = f.getACall() and
nd = getInputNode(inp, c) and
localFlow(getOutputNode(outp, c), resNode)
@@ -438,7 +478,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
* `false`, `nil` or a non-`nil` value.)
*/
private predicate guardingFunction(
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, P param
) {
exists(FuncDecl fd, Node arg, Node ret |
fd.getFunction() = f and
@@ -446,7 +486,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
(
// Case: a function like "if someBarrierGuard(arg) { return true } else { return false }"
exists(ControlFlow::ConditionGuardNode guard |
guards(g, pragma[only_bind_out](guard), arg) and
guards(g, pragma[only_bind_out](guard), arg, param) and
guard.dominates(pragma[only_bind_out](ret).getBasicBlock())
|
onlyPossibleReturnSatisfyingProperty(fd, outp, ret, p)
@@ -456,7 +496,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
// or "return !someBarrierGuard(arg) && otherCond(...)"
exists(boolean outcome |
ret = getUniqueOutputNode(fd, outp) and
guardChecks(g, arg.asExpr(), outcome) and
guardChecks(g, arg.asExpr(), outcome, param) and
// This predicate's contract is (p holds of ret ==> arg is checked),
// (and we have (this has outcome ==> arg is checked))
// but p.checkOn(ret, outcome, this) gives us (ret has outcome ==> p holds of this),
@@ -471,7 +511,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
DataFlow::Property outpProp
|
ret = getUniqueOutputNode(fd, outp) and
guardingFunction(g, f2, inp2, outp2, outpProp) and
guardingFunction(g, f2, inp2, outp2, outpProp, param) and
c = f2.getACall() and
arg = inp2.getNode(c) and
(

View File

@@ -160,16 +160,27 @@ module SourceSinkInterpretationInput implements
}
predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
Element e, string output, string kind, Public::Provenance provenance, string model
) {
none()
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
barrierModel(package, type, subtypes, name, signature, ext, output, kind, provenance, model) and
e = interpretElement(package, type, subtypes, name, signature, ext)
)
}
predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Element e, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingvalue, kind,
provenance, model) and
e = interpretElement(package, type, subtypes, name, signature, ext)
)
}
// Note that due to embedding, which is currently implemented via some

View File

@@ -165,14 +165,6 @@ module Beego {
override string getAContentType() { none() }
}
private class HtmlQuoteSanitizer extends SharedXss::Sanitizer {
HtmlQuoteSanitizer() {
exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName(packagePath(), "Htmlquote") |
this = c.getArgument(0)
)
}
}
private class UtilsTaintPropagators extends TaintTracking::FunctionModel {
UtilsTaintPropagators() { this.hasQualifiedName(utilsPackagePath(), "GetDisplayString") }

View File

@@ -24,8 +24,8 @@ module NoSql {
*/
abstract class Range extends DataFlow::Node { }
private class DefaultQueryString extends Range {
DefaultQueryString() {
private class ExternalQueryString extends Range {
ExternalQueryString() {
exists(DataFlow::ArgumentNode arg | sinkNode(arg, "nosql-injection") |
this = arg.getACorrespondingSyntacticArgument()
)

View File

@@ -67,8 +67,8 @@ module SQL {
*/
abstract class Range extends DataFlow::Node { }
private class DefaultQueryString extends Range {
DefaultQueryString() {
private class ExternalQueryString extends Range {
ExternalQueryString() {
exists(DataFlow::ArgumentNode arg | sinkNode(arg, "sql-injection") |
not arg instanceof DataFlow::ImplicitVarargsSlice and
this = arg

View File

@@ -5,12 +5,12 @@
import go
private class DefaultSystemCommandExecution extends SystemCommandExecution::Range,
private class ExternalSystemCommandExecution extends SystemCommandExecution::Range,
DataFlow::CallNode
{
DataFlow::ArgumentNode commandName;
DefaultSystemCommandExecution() {
ExternalSystemCommandExecution() {
sinkNode(commandName, "command-injection") and
this = commandName.getCall()
}

View File

@@ -25,10 +25,17 @@ module XPath {
*/
abstract class Range extends DataFlow::Node { }
private class DefaultXPathExpressionString extends Range {
DefaultXPathExpressionString() { sinkNode(this, "xpath-injection") }
private class ExternalXPathExpressionString extends Range {
ExternalXPathExpressionString() { sinkNode(this, "xpath-injection") }
}
}
/** A sanitizer for XPath injection. */
abstract class Sanitizer extends DataFlow::Node { }
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, "xpath-injection") }
}
}
/**

View File

@@ -39,10 +39,10 @@ module Regexp {
)
}
private class DefaultRegexpPattern extends RegexpPattern::Range, DataFlow::ArgumentNode {
private class ExternalRegexpPattern extends RegexpPattern::Range, DataFlow::ArgumentNode {
int strArg;
DefaultRegexpPattern() {
ExternalRegexpPattern() {
exists(string kind |
regexSinkKindInfo(kind, strArg) and
sinkNode(this, kind)
@@ -61,12 +61,12 @@ module Regexp {
}
}
private class DefaultRegexpMatchFunction extends RegexpMatchFunction::Range, Function {
private class ExternalRegexpMatchFunction extends RegexpMatchFunction::Range, Function {
int patArg;
int strArg;
DefaultRegexpMatchFunction() {
exists(DefaultRegexpPattern drp, string kind |
ExternalRegexpMatchFunction() {
exists(ExternalRegexpPattern drp, string kind |
drp.getCall() = this.getACall() and
sinkNode(drp, kind)
|

View File

@@ -47,6 +47,10 @@ module CommandInjection {
override predicate doubleDashIsSanitizing() { exec.doubleDashIsSanitizing() }
}
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, "command-injection") }
}
/**
* A call to a regexp match function, considered as a barrier guard for command injection.
*/

View File

@@ -43,8 +43,15 @@ module HardcodedCredentials {
}
/** A use of a credential. */
private class CredentialsSink extends Sink {
CredentialsSink() { exists(string s | s.matches("credentials-%") | sinkNode(this, s)) }
private class ExternalCredentialsSink extends Sink {
ExternalCredentialsSink() { exists(string s | s.matches("credentials-%") | sinkNode(this, s)) }
}
/** A use of a credential. */
private class ExternalCredentialsSanitizer extends Sanitizer {
ExternalCredentialsSanitizer() {
exists(string s | s.matches("credentials-%") | barrierNode(this, s))
}
}
/**

View File

@@ -20,6 +20,8 @@ module MissingJwtSignatureCheck {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
any(AdditionalFlowStep s).step(nodeFrom, nodeTo)
}

View File

@@ -51,7 +51,11 @@ module MissingJwtSignatureCheck {
private class DefaultSource extends Source instanceof ActiveThreatModelSource { }
private class DefaultSink extends Sink {
DefaultSink() { sinkNode(this, "jwt") }
private class ExternalSink extends Sink {
ExternalSink() { sinkNode(this, "jwt") }
}
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, "jwt") }
}
}

View File

@@ -75,6 +75,10 @@ module OpenUrlRedirect {
}
}
private class ExternalBarrier extends Barrier {
ExternalBarrier() { barrierNode(this, "url-redirection") }
}
/**
* An assignment of a safe value to the field `Path`, considered as a barrier for sanitizing
* untrusted URLs.

View File

@@ -44,10 +44,10 @@ module RequestForgery {
*/
private class ThreatModelFlowAsSource extends Source instanceof ActiveThreatModelSource { }
private class DefaultRequestForgerySink extends Sink {
private class ExternalRequestForgerySink extends Sink {
string kind;
DefaultRequestForgerySink() {
ExternalRequestForgerySink() {
exists(string modelKind | sinkNode(this, modelKind) |
modelKind = "request-forgery" and kind = "URL"
or
@@ -94,6 +94,10 @@ module RequestForgery {
HostnameSanitizer() { hostnameSanitizingPrefixEdge(this, _) }
}
private class ExternalRequestForgerySanitizer extends Sanitizer {
ExternalRequestForgerySanitizer() { barrierNode(this, "request-forgery") }
}
/**
* A call to a function called `isLocalUrl`, `isValidRedirect`, or similar, which is
* considered a barrier guard.

View File

@@ -43,6 +43,10 @@ module SqlInjection {
/** DEPRECATED: Use `SimpleTypeSanitizer` from semmle.go.security.Sanitizers instead. */
deprecated class NumericOrBooleanSanitizer = SimpleTypeSanitizer;
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, ["nosql-injection", "sql-injection"]) }
}
/**
* A numeric- or boolean-typed node, considered a sanitizer for sql injection.
*/

View File

@@ -57,6 +57,10 @@ module TaintedPath {
PathAsSink() { this = any(FileSystemAccess fsa).getAPathArgument() }
}
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, "path-injection") }
}
/**
* A numeric- or boolean-typed node, considered a sanitizer for path traversal.
*/
@@ -66,19 +70,6 @@ module TaintedPath {
}
}
/**
* A call to `filepath.Rel`, considered as a sanitizer for path traversal.
*/
class FilepathRelSanitizer extends Sanitizer {
FilepathRelSanitizer() {
exists(Function f, FunctionOutput outp |
f.hasQualifiedName("path/filepath", "Rel") and
outp.isResult(0) and
this = outp.getNode(f.getACall())
)
}
}
/**
* A call to `filepath.Clean("/" + e)`, considered to sanitize `e` against path traversal.
*/
@@ -112,44 +103,6 @@ module TaintedPath {
}
}
/**
* A read from the field `Filename` of the type `mime/multipart.FileHeader`,
* considered as a sanitizer for path traversal.
*
* The only way to create a `mime/multipart.FileHeader` is to create a
* `mime/multipart.Form`, which creates the `Filename` field of each
* `mime/multipart.FileHeader` by calling `Part.FileName`, which calls
* `path/filepath.Base` on its return value. In general `path/filepath.Base`
* is not a sanitizer for path traversal, but in this specific case where the
* output is going to be used as a filename rather than a directory name, it
* is adequate.
*/
class MimeMultipartFileHeaderFilenameSanitizer extends Sanitizer {
MimeMultipartFileHeaderFilenameSanitizer() {
this.(DataFlow::FieldReadNode)
.getField()
.hasQualifiedName("mime/multipart", "FileHeader", "Filename")
}
}
/**
* A call to `mime/multipart.Part.FileName`, considered as a sanitizer
* against path traversal.
*
* `Part.FileName` calls `path/filepath.Base` on its return value. In
* general `path/filepath.Base` is not a sanitizer for path traversal, but in
* this specific case where the output is going to be used as a filename
* rather than a directory name, it is adequate.
*/
class MimeMultipartPartFileNameSanitizer extends Sanitizer {
MimeMultipartPartFileNameSanitizer() {
this =
any(Method m | m.hasQualifiedName("mime/multipart", "Part", "FileName"))
.getACall()
.getResult()
}
}
/**
* A check of the form `!strings.Contains(nd, "..")`, considered as a sanitizer guard for
* path traversal.

View File

@@ -34,4 +34,7 @@ module XPathInjection {
/** An XPath expression string, considered as a taint sink for XPath injection. */
class XPathExpressionStringAsSink extends Sink instanceof XPath::XPathExpressionString { }
/** A sanitizer for XPath injection. */
class XPathSanitizer extends Sanitizer instanceof XPath::Sanitizer { }
}

View File

@@ -49,8 +49,8 @@ module SharedXss {
override Locatable getAssociatedLoc() { result = this.getRead().getEnclosingTextNode() }
}
private class DefaultSink extends Sink {
DefaultSink() { sinkNode(this, ["html-injection", "js-injection"]) }
private class ExternalSink extends Sink {
ExternalSink() { sinkNode(this, ["html-injection", "js-injection"]) }
}
/**
@@ -88,6 +88,10 @@ module SharedXss {
body.getAContentType().regexpMatch("(?i).*html.*")
}
private class ExternalSanitizer extends Sanitizer {
ExternalSanitizer() { barrierNode(this, ["html-injection", "js-injection"]) }
}
/**
* A JSON marshaler, acting to sanitize a possible XSS vulnerability because the
* marshaled value is very unlikely to be returned as an HTML content-type.