mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Merge branch 'main' into py-mad-xss
This commit is contained in:
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
lgtm,codescanning
|
||||||
|
* Support for [gqlgen](https://github.com/99designs/gqlgen) has been added.
|
||||||
@@ -42,6 +42,7 @@ import semmle.go.frameworks.Gin
|
|||||||
import semmle.go.frameworks.Glog
|
import semmle.go.frameworks.Glog
|
||||||
import semmle.go.frameworks.GoMicro
|
import semmle.go.frameworks.GoMicro
|
||||||
import semmle.go.frameworks.GoRestfulHttp
|
import semmle.go.frameworks.GoRestfulHttp
|
||||||
|
import semmle.go.frameworks.Gqlgen
|
||||||
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
|
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
|
||||||
import semmle.go.frameworks.K8sIoApiCoreV1
|
import semmle.go.frameworks.K8sIoApiCoreV1
|
||||||
import semmle.go.frameworks.K8sIoClientGo
|
import semmle.go.frameworks.K8sIoClientGo
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
47
go/ql/lib/semmle/go/frameworks/Gqlgen.qll
Normal file
47
go/ql/lib/semmle/go/frameworks/Gqlgen.qll
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/** Provides models of commonly used functions and types in the gqlgen packages. */
|
||||||
|
|
||||||
|
import go
|
||||||
|
|
||||||
|
/** Provides models of commonly used functions and types in the gqlgen packages. */
|
||||||
|
module Gqlgen {
|
||||||
|
/** An autogenerated file containing gqlgen code. */
|
||||||
|
private class GqlgenGeneratedFile extends File {
|
||||||
|
GqlgenGeneratedFile() {
|
||||||
|
exists(DataFlow::CallNode call |
|
||||||
|
call.getReceiver().getType().hasQualifiedName("github.com/99designs/gqlgen/graphql", _) and
|
||||||
|
call.getFile() = this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A resolver interface. */
|
||||||
|
private class ResolverInterface extends Type {
|
||||||
|
ResolverInterface() {
|
||||||
|
this.getQualifiedName().matches("%Resolver") and
|
||||||
|
this.getEntity().getDeclaration().getFile() instanceof GqlgenGeneratedFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A resolver implementation. */
|
||||||
|
private class ResolverInterfaceMethod extends Method {
|
||||||
|
ResolverInterfaceMethod() { this.getReceiver().getType() instanceof ResolverInterface }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A resolver method which is exposed as a Graphql endpoint */
|
||||||
|
private class ResolverImplementationMethod extends Method {
|
||||||
|
ResolverImplementationMethod() { this.implements(any(ResolverInterfaceMethod r)) }
|
||||||
|
|
||||||
|
Parameter getAnUntrustedParameter() {
|
||||||
|
result.getFunction() = this.getFuncDecl() and
|
||||||
|
not result.getType().hasQualifiedName("context", "Context") and
|
||||||
|
result.getIndex() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A parameter of a resolver method which receives untrusted input. */
|
||||||
|
class ResolverParameter extends UntrustedFlowSource::Range instanceof DataFlow::ParameterNode {
|
||||||
|
ResolverParameter() {
|
||||||
|
this.asParameter() = any(ResolverImplementationMethod h).getAnUntrustedParameter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/go.mod
Normal file
29
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/go.mod
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module pwntester/gqlgen-todos
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/99designs/gqlgen v0.17.3
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.4
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/matryer/moq v0.2.3 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.6.0 // indirect
|
||||||
|
github.com/urfave/cli/v2 v2.25.5 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
golang.org/x/mod v0.6.0-dev // indirect
|
||||||
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
|
golang.org/x/tools v0.1.9 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
failures
|
||||||
|
testFailures
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import go
|
||||||
|
import TestUtilities.InlineExpectationsTest
|
||||||
|
|
||||||
|
module ResolveParameterTest implements TestSig {
|
||||||
|
string getARelevantTag() { result = "resolverParameter" }
|
||||||
|
|
||||||
|
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||||
|
tag = "resolverParameter" and
|
||||||
|
exists(Gqlgen::ResolverParameter p |
|
||||||
|
element = p.toString() and
|
||||||
|
value = "\"" + p.toString() + "\"" and
|
||||||
|
p.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(),
|
||||||
|
location.getStartColumn(), location.getEndLine(), location.getEndColumn())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import MakeTest<ResolveParameterTest>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||||
|
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"pwntester/gqlgen-todos/graph/model"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResolverRoot interface {
|
||||||
|
Mutation() MutationResolver
|
||||||
|
Query() QueryResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutationResolver interface {
|
||||||
|
CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error)
|
||||||
|
}
|
||||||
|
type QueryResolver interface {
|
||||||
|
Todos(ctx context.Context) ([]*model.Todo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stub(dg graphql.CollectedField) {
|
||||||
|
dg.GetPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion ***************************** type.gotpl *****************************
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
type NewTodo struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Todo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Done bool `json:"done"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
// This file will not be regenerated automatically.
|
||||||
|
//
|
||||||
|
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||||
|
|
||||||
|
type Resolver struct{}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# GraphQL schema example
|
||||||
|
#
|
||||||
|
# https://gqlgen.com/getting-started/
|
||||||
|
|
||||||
|
type Todo {
|
||||||
|
id: ID!
|
||||||
|
text: String!
|
||||||
|
done: Boolean!
|
||||||
|
user: User!
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
todos: [Todo!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input NewTodo {
|
||||||
|
text: String!
|
||||||
|
userId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createTodo(input: NewTodo!): Todo!
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.34
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"pwntester/gqlgen-todos/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateTodo is the resolver for the createTodo field.
|
||||||
|
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { // $ resolverParameter="definition of input"
|
||||||
|
panic(fmt.Errorf("not implemented: CreateTodo - createTodo %v", input))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todos is the resolver for the todos field.
|
||||||
|
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
|
||||||
|
panic(fmt.Errorf("not implemented: Todos - todos"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns MutationResolver implementation.
|
||||||
|
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
// Query returns QueryResolver implementation.
|
||||||
|
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
type mutationResolver struct{ *Resolver }
|
||||||
|
type queryResolver struct{ *Resolver }
|
||||||
25
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/github.com/99designs/gqlgen/graphql/stub.go
generated
vendored
Normal file
25
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/github.com/99designs/gqlgen/graphql/stub.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by depstubber. DO NOT EDIT.
|
||||||
|
// This is a simple stub for github.com/99designs/gqlgen/graphql, strictly for use in testing.
|
||||||
|
|
||||||
|
// See the LICENSE file for information about the licensing of the original library.
|
||||||
|
// Source: github.com/99designs/gqlgen/graphql (exports: CollectedField; functions: )
|
||||||
|
|
||||||
|
// Package graphql is a stub of github.com/99designs/gqlgen/graphql, generated by depstubber.
|
||||||
|
package graphql
|
||||||
|
|
||||||
|
type CollectedField struct {
|
||||||
|
Field interface{}
|
||||||
|
Selections interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ CollectedField) ArgumentMap(_ map[string]interface{}) map[string]interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ CollectedField) GetPosition() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ CollectedField) UnmarshalJSON(_ []byte) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
60
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/modules.txt
vendored
Normal file
60
go/ql/test/library-tests/semmle/go/frameworks/gqlgen/vendor/modules.txt
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# github.com/99designs/gqlgen v0.17.3
|
||||||
|
## explicit
|
||||||
|
github.com/99designs/gqlgen
|
||||||
|
# github.com/vektah/gqlparser/v2 v2.5.4
|
||||||
|
## explicit
|
||||||
|
github.com/vektah/gqlparser/v2
|
||||||
|
# github.com/agnivade/levenshtein v1.1.1
|
||||||
|
## explicit
|
||||||
|
github.com/agnivade/levenshtein
|
||||||
|
# github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||||
|
## explicit
|
||||||
|
github.com/cpuguy83/go-md2man/v2
|
||||||
|
# github.com/gorilla/websocket v1.5.0
|
||||||
|
## explicit
|
||||||
|
github.com/gorilla/websocket
|
||||||
|
# github.com/hashicorp/golang-lru v0.5.0
|
||||||
|
## explicit
|
||||||
|
github.com/hashicorp/golang-lru
|
||||||
|
# github.com/kr/text v0.2.0
|
||||||
|
## explicit
|
||||||
|
github.com/kr/text
|
||||||
|
# github.com/matryer/moq v0.2.3
|
||||||
|
## explicit
|
||||||
|
github.com/matryer/moq
|
||||||
|
# github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
## explicit
|
||||||
|
github.com/mitchellh/mapstructure
|
||||||
|
# github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
|
||||||
|
## explicit
|
||||||
|
github.com/niemeyer/pretty
|
||||||
|
# github.com/russross/blackfriday/v2 v2.1.0
|
||||||
|
## explicit
|
||||||
|
github.com/russross/blackfriday/v2
|
||||||
|
# github.com/stretchr/testify v1.6.0
|
||||||
|
## explicit
|
||||||
|
github.com/stretchr/testify
|
||||||
|
# github.com/urfave/cli/v2 v2.25.5
|
||||||
|
## explicit
|
||||||
|
github.com/urfave/cli/v2
|
||||||
|
# github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
|
||||||
|
## explicit
|
||||||
|
github.com/xrash/smetrics
|
||||||
|
# golang.org/x/mod v0.6.0-dev
|
||||||
|
## explicit
|
||||||
|
golang.org/x/mod
|
||||||
|
# golang.org/x/sys v0.8.0
|
||||||
|
## explicit
|
||||||
|
golang.org/x/sys
|
||||||
|
# golang.org/x/tools v0.1.9
|
||||||
|
## explicit
|
||||||
|
golang.org/x/tools
|
||||||
|
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||||
|
## explicit
|
||||||
|
golang.org/x/xerrors
|
||||||
|
# gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
|
||||||
|
## explicit
|
||||||
|
gopkg.in/check.v1
|
||||||
|
# gopkg.in/yaml.v2 v2.4.0
|
||||||
|
## explicit
|
||||||
|
gopkg.in/yaml.v2
|
||||||
@@ -55,12 +55,12 @@ jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,,
|
|||||||
jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,94,55
|
jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,94,55
|
||||||
java.awt,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
|
java.awt,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
|
||||||
java.beans,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
java.beans,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||||
java.io,49,,45,,,22,,,,,,,,,,,,,,27,,,,,,,,,,,,,,,,,,,43,2
|
java.io,50,,45,,,22,,,,,,,,,,,,,,28,,,,,,,,,,,,,,,,,,,43,2
|
||||||
java.lang,31,,92,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,56,36
|
java.lang,31,,93,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,57,36
|
||||||
java.net,13,3,21,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,21,
|
java.net,13,3,23,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,23,
|
||||||
java.nio,47,,35,,,3,,,,,,,,,,,,,,44,,,,,,,,,,,,,,,,,,,35,
|
java.nio,53,,36,,,5,,,,,,,,,,,,,,47,,,,,,,,,1,,,,,,,,,,36,
|
||||||
java.sql,13,,2,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,2,
|
java.sql,13,,2,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,2,
|
||||||
java.util,44,,484,,,,,,,,,,,,,,34,,,,,,,5,2,,1,2,,,,,,,,,,,44,440
|
java.util,45,,485,,,1,,,,,,,,,,,34,,,,,,,5,2,,1,2,,,,,,,,,,,45,440
|
||||||
javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
||||||
javax.faces.context,2,7,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,
|
javax.faces.context,2,7,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,
|
||||||
javax.imageio.stream,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
javax.imageio.stream,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||||
@@ -84,7 +84,7 @@ kotlin,16,,1849,,,,,,,,,,,,,,,,,14,,,,,,,,,2,,,,,,,,,,1836,13
|
|||||||
net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,321,17
|
net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,321,17
|
||||||
net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,
|
net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,
|
||||||
ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
||||||
okhttp3,4,,48,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,25
|
okhttp3,4,,50,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,27
|
||||||
org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49,
|
org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49,
|
||||||
org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
||||||
org.apache.commons.codec,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,
|
org.apache.commons.codec,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,
|
||||||
@@ -98,7 +98,7 @@ org.apache.commons.jelly,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,
|
|||||||
org.apache.commons.jexl2,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
org.apache.commons.jexl2,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||||
org.apache.commons.jexl3,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
org.apache.commons.jexl3,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||||
org.apache.commons.lang,,,765,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,594,171
|
org.apache.commons.lang,,,765,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,594,171
|
||||||
org.apache.commons.lang3,6,,424,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,293,131
|
org.apache.commons.lang3,6,,425,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,294,131
|
||||||
org.apache.commons.logging,6,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,
|
org.apache.commons.logging,6,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,
|
||||||
org.apache.commons.net,9,12,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,12,,
|
org.apache.commons.net,9,12,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,12,,
|
||||||
org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
||||||
@@ -131,6 +131,7 @@ org.dom4j,20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,20,,,,,,
|
|||||||
org.eclipse.jetty.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
org.eclipse.jetty.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
||||||
org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
||||||
org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,
|
org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,
|
||||||
|
org.gradle.api.file,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,
|
||||||
org.hibernate,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,,
|
org.hibernate,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,,
|
||||||
org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
||||||
org.jboss.logging,324,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,,
|
org.jboss.logging,324,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,,
|
||||||
@@ -180,4 +181,4 @@ ratpack.func,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35
|
|||||||
ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,
|
ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,
|
||||||
ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10,
|
ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10,
|
||||||
ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35
|
ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35
|
||||||
retrofit2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
retrofit2,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,
|
||||||
|
|||||||
|
@@ -11,17 +11,17 @@ Java framework & library support
|
|||||||
Android extensions,``androidx.*``,5,183,19,,,,,,
|
Android extensions,``androidx.*``,5,183,19,,,,,,
|
||||||
`Apache Commons Collections <https://commons.apache.org/proper/commons-collections/>`_,"``org.apache.commons.collections``, ``org.apache.commons.collections4``",,1600,,,,,,,
|
`Apache Commons Collections <https://commons.apache.org/proper/commons-collections/>`_,"``org.apache.commons.collections``, ``org.apache.commons.collections4``",,1600,,,,,,,
|
||||||
`Apache Commons IO <https://commons.apache.org/proper/commons-io/>`_,``org.apache.commons.io``,,560,111,94,,,,,15
|
`Apache Commons IO <https://commons.apache.org/proper/commons-io/>`_,``org.apache.commons.io``,,560,111,94,,,,,15
|
||||||
`Apache Commons Lang <https://commons.apache.org/proper/commons-lang/>`_,``org.apache.commons.lang3``,,424,6,,,,,,
|
`Apache Commons Lang <https://commons.apache.org/proper/commons-lang/>`_,``org.apache.commons.lang3``,,425,6,,,,,,
|
||||||
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,
|
`Apache Commons Text <https://commons.apache.org/proper/commons-text/>`_,``org.apache.commons.text``,,272,,,,,,,
|
||||||
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,182,122,,3,,,,119
|
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,182,122,,3,,,,119
|
||||||
`Apache Log4j 2 <https://logging.apache.org/log4j/2.0/>`_,``org.apache.logging.log4j``,,8,359,,,,,,
|
`Apache Log4j 2 <https://logging.apache.org/log4j/2.0/>`_,``org.apache.logging.log4j``,,8,359,,,,,,
|
||||||
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,730,41,7,,,,,
|
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,730,41,7,,,,,
|
||||||
JBoss Logging,``org.jboss.logging``,,,324,,,,,,
|
JBoss Logging,``org.jboss.logging``,,,324,,,,,,
|
||||||
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,
|
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,
|
||||||
Java Standard Library,``java.*``,3,683,197,76,,9,,,17
|
Java Standard Library,``java.*``,3,688,205,80,,9,,,18
|
||||||
Java extensions,"``javax.*``, ``jakarta.*``",63,672,34,2,4,,1,1,2
|
Java extensions,"``javax.*``, ``jakarta.*``",63,672,34,2,4,,1,1,2
|
||||||
Kotlin Standard Library,``kotlin*``,,1849,16,14,,,,,2
|
Kotlin Standard Library,``kotlin*``,,1849,16,14,,,,,2
|
||||||
`Spring <https://spring.io/>`_,``org.springframework.*``,29,483,115,4,,28,14,,35
|
`Spring <https://spring.io/>`_,``org.springframework.*``,29,483,115,4,,28,14,,35
|
||||||
Others,"``antlr``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.thoughtworks.xstream``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.hibernate``, ``org.influxdb``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.kohsuke.stapler``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",126,5232,577,89,6,18,18,,200
|
Others,"``antlr``, ``cn.hutool.core.codec``, ``com.alibaba.druid.sql``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.google.gson``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.thoughtworks.xstream``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``freemarker.cache``, ``freemarker.template``, ``groovy.lang``, ``groovy.text``, ``groovy.util``, ``hudson``, ``io.jsonwebtoken``, ``io.netty.bootstrap``, ``io.netty.buffer``, ``io.netty.channel``, ``io.netty.handler.codec``, ``io.netty.handler.ssl``, ``io.netty.handler.stream``, ``io.netty.resolver``, ``io.netty.util``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.apache.velocity.app``, ``org.apache.velocity.runtime``, ``org.codehaus.cargo.container.installer``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.eclipse.jetty.client``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.hibernate``, ``org.influxdb``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.jooq``, ``org.kohsuke.stapler``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.scijava.log``, ``org.slf4j``, ``org.thymeleaf``, ``org.xml.sax``, ``org.xmlpull.v1``, ``org.yaml.snakeyaml``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",126,5237,577,89,6,18,18,,200
|
||||||
Totals,,283,13595,2059,286,16,122,33,1,390
|
Totals,,283,13606,2067,290,16,122,33,1,391
|
||||||
|
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,9 +12,44 @@
|
|||||||
* @tags internal extract automodel application-mode candidates
|
* @tags internal extract automodel application-mode candidates
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java
|
||||||
private import AutomodelApplicationModeCharacteristics
|
private import AutomodelApplicationModeCharacteristics
|
||||||
private import AutomodelJavaUtil
|
private import AutomodelJavaUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a sample of endpoints (of at most `limit` samples) with the given method signature.
|
||||||
|
*
|
||||||
|
* The main purpose of this helper predicate is to avoid selecting too many candidates, as this may
|
||||||
|
* cause the SARIF file to exceed the maximum size limit.
|
||||||
|
*/
|
||||||
|
bindingset[limit]
|
||||||
|
private Endpoint getSampleForSignature(
|
||||||
|
int limit, string package, string type, string subtypes, string name, string signature,
|
||||||
|
string input
|
||||||
|
) {
|
||||||
|
exists(int n, int num_endpoints, ApplicationModeMetadataExtractor meta |
|
||||||
|
num_endpoints =
|
||||||
|
count(Endpoint e | meta.hasMetadata(e, package, type, subtypes, name, signature, input))
|
||||||
|
|
|
||||||
|
result =
|
||||||
|
rank[n](Endpoint e, Location loc |
|
||||||
|
loc = e.getLocation() and
|
||||||
|
meta.hasMetadata(e, package, type, subtypes, name, signature, input)
|
||||||
|
|
|
||||||
|
e
|
||||||
|
order by
|
||||||
|
loc.getFile().getAbsolutePath(), loc.getStartLine(), loc.getStartColumn(),
|
||||||
|
loc.getEndLine(), loc.getEndColumn()
|
||||||
|
) and
|
||||||
|
// To avoid selecting samples that are too close together (as the ranking above goes by file
|
||||||
|
// path first), we select `limit` evenly spaced samples from the ranked list of endpoints. By
|
||||||
|
// default this would always include the first sample, so we add a random-chosen prime offset
|
||||||
|
// to the first sample index, and reduce modulo the number of endpoints.
|
||||||
|
// Finally, we add 1 to the result, as ranking results in a 1-indexed relation.
|
||||||
|
n = 1 + (([0 .. limit - 1] * (num_endpoints / limit).floor() + 46337) % num_endpoints)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
from
|
from
|
||||||
Endpoint endpoint, string message, ApplicationModeMetadataExtractor meta, DollarAtString package,
|
Endpoint endpoint, string message, ApplicationModeMetadataExtractor meta, DollarAtString package,
|
||||||
DollarAtString type, DollarAtString subtypes, DollarAtString name, DollarAtString signature,
|
DollarAtString type, DollarAtString subtypes, DollarAtString name, DollarAtString signature,
|
||||||
@@ -23,6 +58,7 @@ where
|
|||||||
not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u |
|
not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u |
|
||||||
u.appliesToEndpoint(endpoint)
|
u.appliesToEndpoint(endpoint)
|
||||||
) and
|
) and
|
||||||
|
endpoint = getSampleForSignature(9, package, type, subtypes, name, signature, input) and
|
||||||
// If a node is already a known sink for any of our existing ATM queries and is already modeled as a MaD sink, we
|
// If a node is already a known sink for any of our existing ATM queries and is already modeled as a MaD sink, we
|
||||||
// don't include it as a candidate. Otherwise, we might include it as a candidate for query A, but the model will
|
// don't include it as a candidate. Otherwise, we might include it as a candidate for query A, but the model will
|
||||||
// label it as a sink for one of the sink types of query B, for which it's already a known sink. This would result in
|
// label it as a sink for one of the sink types of query B, for which it's already a known sink. This would result in
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class DomBasedXssAtmConfig extends AtmConfig {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) {
|
override predicate isSanitizer(DataFlow::Node node) {
|
||||||
super.isSanitizer(node) or
|
super.isSanitizer(node) or
|
||||||
node instanceof DomBasedXss::Sanitizer
|
node instanceof DomBasedXss::Sanitizer or
|
||||||
|
DomBasedXss::isOptionallySanitizedNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
@@ -31,10 +32,6 @@ class DomBasedXssAtmConfig extends AtmConfig {
|
|||||||
guard instanceof QuoteGuard or
|
guard instanceof QuoteGuard or
|
||||||
guard instanceof ContainsHtmlGuard
|
guard instanceof ContainsHtmlGuard
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private import semmle.javascript.security.dataflow.Xss::Shared as Shared
|
private import semmle.javascript.security.dataflow.Xss::Shared as Shared
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class XssThroughDomAtmConfig extends AtmConfig {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) {
|
override predicate isSanitizer(DataFlow::Node node) {
|
||||||
super.isSanitizer(node) or
|
super.isSanitizer(node) or
|
||||||
node instanceof DomBasedXss::Sanitizer
|
node instanceof DomBasedXss::Sanitizer or
|
||||||
|
DomBasedXss::isOptionallySanitizedNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
@@ -34,10 +35,6 @@ class XssThroughDomAtmConfig extends AtmConfig {
|
|||||||
guard instanceof QuoteGuard or
|
guard instanceof QuoteGuard or
|
||||||
guard instanceof ContainsHtmlGuard
|
guard instanceof ContainsHtmlGuard
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -166,6 +166,26 @@ abstract class Configuration extends string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow into `node` is prohibited.
|
||||||
|
*/
|
||||||
|
predicate isBarrierIn(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow out `node` is prohibited.
|
||||||
|
*/
|
||||||
|
predicate isBarrierOut(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow into `node` is prohibited for the flow label `lbl`.
|
||||||
|
*/
|
||||||
|
predicate isBarrierIn(DataFlow::Node node, FlowLabel lbl) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow out `node` is prohibited for the flow label `lbl`.
|
||||||
|
*/
|
||||||
|
predicate isBarrierOut(DataFlow::Node node, FlowLabel lbl) { none() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if flow from `pred` to `succ` is prohibited.
|
* Holds if flow from `pred` to `succ` is prohibited.
|
||||||
*/
|
*/
|
||||||
@@ -494,7 +514,7 @@ private BasicBlock getADominatedBasicBlock(BarrierGuardNode guard, ConditionGuar
|
|||||||
*
|
*
|
||||||
* Only holds for barriers that should apply to all flow labels.
|
* Only holds for barriers that should apply to all flow labels.
|
||||||
*/
|
*/
|
||||||
private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) {
|
private predicate isBarrierEdgeRaw(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
cfg.isBarrierEdge(pred, succ)
|
cfg.isBarrierEdge(pred, succ)
|
||||||
or
|
or
|
||||||
exists(DataFlow::BarrierGuardNode guard |
|
exists(DataFlow::BarrierGuardNode guard |
|
||||||
@@ -503,11 +523,26 @@ private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if there is a barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
|
||||||
|
* or one implied by a barrier guard, or by an out/in barrier for `pred` or `succ`, respectively.
|
||||||
|
*
|
||||||
|
* Only holds for barriers that should apply to all flow labels.
|
||||||
|
*/
|
||||||
|
pragma[inline]
|
||||||
|
private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
isBarrierEdgeRaw(cfg, pred, succ)
|
||||||
|
or
|
||||||
|
cfg.isBarrierOut(pred)
|
||||||
|
or
|
||||||
|
cfg.isBarrierIn(succ)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
|
* Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
|
||||||
* or one implied by a barrier guard.
|
* or one implied by a barrier guard.
|
||||||
*/
|
*/
|
||||||
private predicate isLabeledBarrierEdge(
|
private predicate isLabeledBarrierEdgeRaw(
|
||||||
Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label
|
Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label
|
||||||
) {
|
) {
|
||||||
cfg.isBarrierEdge(pred, succ, label)
|
cfg.isBarrierEdge(pred, succ, label)
|
||||||
@@ -518,6 +553,21 @@ private predicate isLabeledBarrierEdge(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
|
||||||
|
* or one implied by a barrier guard, or by an out/in barrier for `pred` or `succ`, respectively.
|
||||||
|
*/
|
||||||
|
pragma[inline]
|
||||||
|
private predicate isLabeledBarrierEdge(
|
||||||
|
Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label
|
||||||
|
) {
|
||||||
|
isLabeledBarrierEdgeRaw(cfg, pred, succ, label)
|
||||||
|
or
|
||||||
|
cfg.isBarrierOut(pred, label)
|
||||||
|
or
|
||||||
|
cfg.isBarrierIn(succ, label)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A guard node that only blocks specific labels.
|
* A guard node that only blocks specific labels.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -62,6 +62,26 @@ module TaintTracking {
|
|||||||
*/
|
*/
|
||||||
predicate isSanitizer(DataFlow::Node node) { none() }
|
predicate isSanitizer(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow into `node` is prohibited.
|
||||||
|
*/
|
||||||
|
predicate isSanitizerIn(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow out `node` is prohibited.
|
||||||
|
*/
|
||||||
|
predicate isSanitizerOut(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow into `node` is prohibited for the flow label `lbl`.
|
||||||
|
*/
|
||||||
|
predicate isSanitizerIn(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if flow out `node` is prohibited for the flow label `lbl`.
|
||||||
|
*/
|
||||||
|
predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) { none() }
|
||||||
|
|
||||||
/** Holds if the edge from `pred` to `succ` is a taint sanitizer. */
|
/** Holds if the edge from `pred` to `succ` is a taint sanitizer. */
|
||||||
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||||
|
|
||||||
@@ -108,6 +128,22 @@ module TaintTracking {
|
|||||||
this.isSanitizerEdge(source, sink) and lbl.isTaint()
|
this.isSanitizerEdge(source, sink) and lbl.isTaint()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final override predicate isBarrierIn(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
final override predicate isBarrierOut(DataFlow::Node node) { none() }
|
||||||
|
|
||||||
|
final override predicate isBarrierIn(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||||
|
this.isSanitizerIn(node, lbl)
|
||||||
|
or
|
||||||
|
this.isSanitizerIn(node) and lbl.isTaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
final override predicate isBarrierOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||||
|
this.isSanitizerOut(node, lbl)
|
||||||
|
or
|
||||||
|
this.isSanitizerOut(node) and lbl.isTaint()
|
||||||
|
}
|
||||||
|
|
||||||
final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
||||||
super.isBarrierGuard(guard) or
|
super.isBarrierGuard(guard) or
|
||||||
guard.(AdditionalSanitizerGuardNode).appliesTo(this) or
|
guard.(AdditionalSanitizerGuardNode).appliesTo(this) or
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof CleartextLogging::Barrier }
|
override predicate isSanitizer(DataFlow::Node node) { node instanceof CleartextLogging::Barrier }
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
CleartextLogging::isSanitizerEdge(pred, succ)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||||
CleartextLogging::isAdditionalTaintStep(src, trg)
|
CleartextLogging::isAdditionalTaintStep(src, trg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,12 +175,24 @@ module CleartextLogging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED. Use `Barrier` instead, sanitized have been replaced by sanitized nodes.
|
||||||
|
*
|
||||||
* Holds if the edge `pred` -> `succ` should be sanitized for clear-text logging of sensitive information.
|
* Holds if the edge `pred` -> `succ` should be sanitized for clear-text logging of sensitive information.
|
||||||
*/
|
*/
|
||||||
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
deprecated predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
succ.(DataFlow::PropRead).getBase() = pred
|
succ.(DataFlow::PropRead).getBase() = pred
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PropReadAsBarrier extends Barrier {
|
||||||
|
PropReadAsBarrier() {
|
||||||
|
this = any(DataFlow::PropRead read).getBase() and
|
||||||
|
// the 'foo' in 'foo.bar()' may have flow, we only want to suppress plain property reads
|
||||||
|
not this = any(DataFlow::MethodCallNode call).getReceiver() and
|
||||||
|
// do not block custom taint steps from this node
|
||||||
|
not isAdditionalTaintStep(this, _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if the edge `src` -> `trg` is an additional taint-step for clear-text logging of sensitive information.
|
* Holds if the edge `src` -> `trg` is an additional taint-step for clear-text logging of sensitive information.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
|
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
CleartextLogging::isSanitizerEdge(pred, succ)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||||
CleartextLogging::isAdditionalTaintStep(src, trg)
|
CleartextLogging::isAdditionalTaintStep(src, trg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
node instanceof Sanitizer
|
node instanceof Sanitizer
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) }
|
||||||
sanitizingPrefixEdge(source, sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
isAdditionalRequestForgeryStep(pred, succ)
|
isAdditionalRequestForgeryStep(pred, succ)
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
node instanceof Sanitizer
|
node instanceof Sanitizer
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
|
||||||
hostnameSanitizingPrefixEdge(source, sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalFlowStep(
|
override predicate isAdditionalFlowStep(
|
||||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel f, DataFlow::FlowLabel g
|
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel f, DataFlow::FlowLabel g
|
||||||
|
|||||||
@@ -290,9 +290,13 @@ module DomBasedXss {
|
|||||||
private class HtmlSanitizerAsSanitizer extends Sanitizer instanceof HtmlSanitizerCall { }
|
private class HtmlSanitizerAsSanitizer extends Sanitizer instanceof HtmlSanitizerCall { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* DEPRECATED. Use `isOptionallySanitizedNode` instead.
|
||||||
|
*
|
||||||
* Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`.
|
* Holds if there exists two dataflow edges to `succ`, where one edges is sanitized, and the other edge starts with `pred`.
|
||||||
*/
|
*/
|
||||||
predicate isOptionallySanitizedEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
deprecated predicate isOptionallySanitizedEdge = isOptionallySanitizedEdgeInternal/2;
|
||||||
|
|
||||||
|
private predicate isOptionallySanitizedEdgeInternal(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
exists(HtmlSanitizerCall sanitizer |
|
exists(HtmlSanitizerCall sanitizer |
|
||||||
// sanitized = sanitize ? sanitizer(source) : source;
|
// sanitized = sanitize ? sanitizer(source) : source;
|
||||||
exists(ConditionalExpr branch, Variable var, VarAccess access |
|
exists(ConditionalExpr branch, Variable var, VarAccess access |
|
||||||
@@ -319,6 +323,17 @@ module DomBasedXss {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `node` should be considered optionally sanitized as it occurs in a branch
|
||||||
|
* that controls whether sanitization is enabled.
|
||||||
|
*
|
||||||
|
* For example, in `sanitized = sanitize ? sanitizer(source) : source`, the right-hand `source` expression
|
||||||
|
* is considered an optionally sanitized node.
|
||||||
|
*/
|
||||||
|
predicate isOptionallySanitizedNode(DataFlow::Node node) {
|
||||||
|
isOptionallySanitizedEdgeInternal(_, node)
|
||||||
|
}
|
||||||
|
|
||||||
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
|
/** A source of remote user input, considered as a flow source for DOM-based XSS. */
|
||||||
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { }
|
||||||
|
|
||||||
|
|||||||
@@ -86,13 +86,9 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
// we assume that `.join()` calls have a prefix, and thus block the prefix label.
|
// we assume that `.join()` calls have a prefix, and thus block the prefix label.
|
||||||
node = any(DataFlow::MethodCallNode call | call.getMethodName() = "join") and
|
node = any(DataFlow::MethodCallNode call | call.getMethodName() = "join") and
|
||||||
lbl = prefixLabel()
|
lbl = prefixLabel()
|
||||||
}
|
or
|
||||||
|
isOptionallySanitizedNode(node) and
|
||||||
override predicate isSanitizerEdge(
|
lbl = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()]
|
||||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label
|
|
||||||
) {
|
|
||||||
isOptionallySanitizedEdge(pred, succ) and
|
|
||||||
label = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isAdditionalFlowStep(
|
override predicate isAdditionalFlowStep(
|
||||||
|
|||||||
@@ -46,15 +46,11 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isSanitizerIn(DataFlow::Node node) {
|
||||||
// Block flow from the location to its properties, as the relevant properties (hash and search) are taint sources of their own.
|
// Block flow from the location to its properties, as the relevant properties (hash and search) are taint sources of their own.
|
||||||
// The location source is only used for propagating through API calls like `new URL(location)` and into external APIs where
|
// The location source is only used for propagating through API calls like `new URL(location)` and into external APIs where
|
||||||
// the whole location object escapes.
|
// the whole location object escapes.
|
||||||
exists(DataFlow::PropRead read |
|
node = DOM::locationRef().getAPropertyRead()
|
||||||
read = DOM::locationRef().getAPropertyRead() and
|
|
||||||
pred = read.getBase() and
|
|
||||||
succ = read
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,9 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
node instanceof Sanitizer
|
node instanceof Sanitizer
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isSanitizerOut(DataFlow::Node node) {
|
||||||
// stop propagation at the sinks to avoid double reporting
|
// stop propagation at the sinks to avoid double reporting
|
||||||
pred instanceof Sink and
|
this.isSink(node)
|
||||||
// constrain succ
|
|
||||||
pred = succ.getAPredecessor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
|
|||||||
@@ -55,20 +55,11 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(
|
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl
|
|
||||||
) {
|
|
||||||
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
|
// Suppress the value-preserving step src -> dst in `extend(dst, src)`. This is modeled as a value-preserving
|
||||||
// step because it preserves all properties, but the destination is not actually Object.prototype.
|
// step because it preserves all properties, but the destination is not actually Object.prototype.
|
||||||
exists(ExtendCall call |
|
node = any(ExtendCall call).getASourceOperand() and
|
||||||
pred = call.getASourceOperand() and
|
lbl instanceof ObjectPrototype
|
||||||
(
|
|
||||||
succ = call.getDestinationOperand().getALocalSource()
|
|
||||||
or
|
|
||||||
succ = call
|
|
||||||
) and
|
|
||||||
lbl instanceof ObjectPrototype
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isAdditionalFlowStep(
|
override predicate isAdditionalFlowStep(
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
node instanceof Sanitizer
|
node instanceof Sanitizer
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) }
|
||||||
sanitizingPrefixEdge(source, sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
isAdditionalRequestForgeryStep(pred, succ)
|
isAdditionalRequestForgeryStep(pred, succ)
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) {
|
override predicate isSanitizer(DataFlow::Node node) {
|
||||||
super.isSanitizer(node) or
|
super.isSanitizer(node) or
|
||||||
node instanceof Sanitizer
|
node instanceof Sanitizer or
|
||||||
|
node = any(DataFlow::PropRead read | read.getPropertyName() = "length")
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) {
|
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node dst) {
|
||||||
@@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
guard instanceof UpperBoundsCheckSanitizerGuard
|
guard instanceof UpperBoundsCheckSanitizerGuard
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
succ.(DataFlow::PropRead).accesses(pred, "length")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Holds if data is converted to a number from `src` to `dst`. */
|
/** Holds if data is converted to a number from `src` to `dst`. */
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
node instanceof Sanitizer
|
node instanceof Sanitizer
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
|
||||||
hostnameSanitizingPrefixEdge(source, sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
guard instanceof LocalUrlSanitizingGuard or
|
guard instanceof LocalUrlSanitizingGuard or
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ class Configration extends TaintTracking::Configuration {
|
|||||||
node instanceof DomBasedXss::Sanitizer
|
node instanceof DomBasedXss::Sanitizer
|
||||||
or
|
or
|
||||||
node instanceof UnsafeJQueryPlugin::Sanitizer
|
node instanceof UnsafeJQueryPlugin::Sanitizer
|
||||||
}
|
or
|
||||||
|
DomBasedXss::isOptionallySanitizedNode(node)
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// override to require that there is a path without unmatched return steps
|
// override to require that there is a path without unmatched return steps
|
||||||
|
|||||||
@@ -31,16 +31,13 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
aliasPropertyPresenceStep(src, sink)
|
aliasPropertyPresenceStep(src, sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isSanitizerOut(DataFlow::Node node) {
|
||||||
// prefixing prevents forced html/css confusion:
|
// prefixing prevents forced html/css confusion:
|
||||||
// prefixing through concatenation:
|
// prefixing through concatenation:
|
||||||
StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1))
|
StringConcatenation::taintStep(node, _, _, any(int i | i >= 1))
|
||||||
or
|
or
|
||||||
// prefixing through a poor-mans templating system:
|
// prefixing through a poor-mans templating system:
|
||||||
exists(StringReplaceCall replace |
|
node = any(StringReplaceCall call).getRawReplacement()
|
||||||
replace = succ and
|
|
||||||
pred = replace.getRawReplacement()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
|
||||||
|
|||||||
@@ -34,11 +34,24 @@ module UnvalidatedDynamicMethodCall {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A sanitizer for unvalidated dynamic method calls.
|
* A sanitizer for unvalidated dynamic method calls.
|
||||||
* Override the `sanitizes` predicate to specify an edge that should be sanitized.
|
|
||||||
* The `this` value is not seen as a sanitizer.
|
|
||||||
*/
|
*/
|
||||||
abstract class Sanitizer extends DataFlow::Node {
|
abstract class Sanitizer extends DataFlow::Node {
|
||||||
abstract predicate sanitizes(DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl);
|
/**
|
||||||
|
* Gets the flow label blocked by this sanitizer.
|
||||||
|
*/
|
||||||
|
DataFlow::FlowLabel getFlowLabel() { result.isTaint() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEPRECATED. Use sanitizer nodes instead.
|
||||||
|
*
|
||||||
|
* This predicate no longer has any effect. The `this` value of `Sanitizer` is instead
|
||||||
|
* treated as a sanitizing node, that is, flow in and out of that node is prohibited.
|
||||||
|
*/
|
||||||
|
deprecated predicate sanitizes(
|
||||||
|
DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl
|
||||||
|
) {
|
||||||
|
none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
sink.(Sink).getFlowLabel() = label
|
sink.(Sink).getFlowLabel() = label
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(
|
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel label) {
|
||||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl
|
super.isLabeledBarrier(node, label)
|
||||||
) {
|
or
|
||||||
any(Sanitizer s).sanitizes(pred, succ, lbl)
|
node.(Sanitizer).getFlowLabel() = label
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) {
|
override predicate isSanitizer(DataFlow::Node node) {
|
||||||
super.isSanitizer(node) or
|
super.isSanitizer(node) or
|
||||||
node instanceof DomBasedXss::Sanitizer
|
node instanceof DomBasedXss::Sanitizer or
|
||||||
|
DomBasedXss::isOptionallySanitizedNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||||
@@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
guard instanceof ContainsHtmlGuard
|
guard instanceof ContainsHtmlGuard
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
|
||||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
|
||||||
}
|
|
||||||
|
|
||||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||||
succ = DataFlow::globalVarRef("URL").getAMemberCall("createObjectURL") and
|
succ = DataFlow::globalVarRef("URL").getAMemberCall("createObjectURL") and
|
||||||
pred = succ.(DataFlow::InvokeNode).getArgument(0)
|
pred = succ.(DataFlow::InvokeNode).getArgument(0)
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class Configuration extends TaintTracking::Configuration {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
override predicate isSanitizerOut(DataFlow::Node node) {
|
||||||
this.strictSanitizingPrefixEdge(source, sink)
|
this.strictSanitizingPrefixEdge(node, _)
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ class TestDataFlowConfiguration extends DataFlow::Configuration {
|
|||||||
f.getName().matches("%noReturnTracking%") and
|
f.getName().matches("%noReturnTracking%") and
|
||||||
node = f.getAReturnedExpr().flow()
|
node = f.getAReturnedExpr().flow()
|
||||||
)
|
)
|
||||||
}
|
or
|
||||||
|
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||||
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) {
|
|
||||||
src = src and
|
|
||||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,8 @@ class TestTaintTrackingConfiguration extends TaintTracking::Configuration {
|
|||||||
f.getName().matches("%noReturnTracking%") and
|
f.getName().matches("%noReturnTracking%") and
|
||||||
node = f.getAReturnedExpr().flow()
|
node = f.getAReturnedExpr().flow()
|
||||||
)
|
)
|
||||||
}
|
or
|
||||||
|
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||||
override predicate isSanitizerEdge(DataFlow::Node src, DataFlow::Node snk) {
|
|
||||||
src = src and
|
|
||||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +96,8 @@ class GermanFlowConfig extends DataFlow::Configuration {
|
|||||||
f.getName().matches("%noReturnTracking%") and
|
f.getName().matches("%noReturnTracking%") and
|
||||||
node = f.getAReturnedExpr().flow()
|
node = f.getAReturnedExpr().flow()
|
||||||
)
|
)
|
||||||
}
|
or
|
||||||
|
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||||
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) {
|
|
||||||
src = src and
|
|
||||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import javascript
|
import javascript
|
||||||
|
|
||||||
|
DataFlow::Node sourceVariable() { result.asExpr().(VarRef).getName() = "sourceVariable" }
|
||||||
|
|
||||||
|
StringOps::ConcatenationRoot sinkConcatenation() {
|
||||||
|
result.getConstantStringParts().matches("<sink>%</sink>")
|
||||||
|
}
|
||||||
|
|
||||||
class ExampleConfiguration extends TaintTracking::Configuration {
|
class ExampleConfiguration extends TaintTracking::Configuration {
|
||||||
ExampleConfiguration() { this = "ExampleConfiguration" }
|
ExampleConfiguration() { this = "ExampleConfiguration" }
|
||||||
|
|
||||||
override predicate isSource(DataFlow::Node source) {
|
override predicate isSource(DataFlow::Node source) {
|
||||||
source.asExpr().(CallExpr).getCalleeName() = "SOURCE"
|
source.asExpr().(CallExpr).getCalleeName() = "SOURCE"
|
||||||
|
or
|
||||||
|
source = sourceVariable()
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSink(DataFlow::Node sink) {
|
override predicate isSink(DataFlow::Node sink) {
|
||||||
@@ -12,8 +20,14 @@ class ExampleConfiguration extends TaintTracking::Configuration {
|
|||||||
callExpr.getCalleeName() = "SINK" and
|
callExpr.getCalleeName() = "SINK" and
|
||||||
DataFlow::valueNode(callExpr.getArgument(0)) = sink
|
DataFlow::valueNode(callExpr.getArgument(0)) = sink
|
||||||
)
|
)
|
||||||
|
or
|
||||||
|
sink = sinkConcatenation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override predicate isSanitizerIn(DataFlow::Node node) { node = sourceVariable() }
|
||||||
|
|
||||||
|
override predicate isSanitizerOut(DataFlow::Node node) { node = sinkConcatenation() }
|
||||||
|
|
||||||
override predicate isSanitizer(DataFlow::Node node) {
|
override predicate isSanitizer(DataFlow::Node node) {
|
||||||
exists(CallExpr callExpr |
|
exists(CallExpr callExpr |
|
||||||
callExpr.getCalleeName() = "SANITIZE" and
|
callExpr.getCalleeName() = "SANITIZE" and
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'dummy';
|
||||||
|
|
||||||
|
function barrierIn() {
|
||||||
|
var sourceVariable = 123;
|
||||||
|
SINK(sourceVariable); // NOT OK
|
||||||
|
|
||||||
|
flowWithSourceParam(sourceVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function barrierInParameter(sourceVariable) {
|
||||||
|
SINK(sourceVariable); // NOT OK, but only report the parameter as the source
|
||||||
|
}
|
||||||
|
|
||||||
|
function barrierOut() {
|
||||||
|
let taint = SOURCE();
|
||||||
|
taint = "<sink>" + taint + "</sink>"; // NOT OK
|
||||||
|
taint = "<sink>" + taint + "</sink>"; // OK - only report first instance
|
||||||
|
}
|
||||||
@@ -133,6 +133,9 @@ sanitizingGuard
|
|||||||
| tst.js:399:16:399:41 | o.hasOw ... "p.q"]) | tst.js:399:33:399:40 | v["p.q"] | true |
|
| tst.js:399:16:399:41 | o.hasOw ... "p.q"]) | tst.js:399:33:399:40 | v["p.q"] | true |
|
||||||
| tst.js:401:16:401:34 | Object.hasOwn(o, v) | tst.js:401:33:401:33 | v | true |
|
| tst.js:401:16:401:34 | Object.hasOwn(o, v) | tst.js:401:33:401:33 | v | true |
|
||||||
taintedSink
|
taintedSink
|
||||||
|
| sanitizer-in-out.js:5:10:5:23 | sourceVariable | sanitizer-in-out.js:5:10:5:23 | sourceVariable |
|
||||||
|
| sanitizer-in-out.js:11:10:11:23 | sourceVariable | sanitizer-in-out.js:11:10:11:23 | sourceVariable |
|
||||||
|
| sanitizer-in-out.js:15:17:15:24 | SOURCE() | sanitizer-in-out.js:16:13:16:40 | "<sink> ... /sink>" |
|
||||||
| tst.js:2:13:2:20 | SOURCE() | tst.js:3:10:3:10 | v |
|
| tst.js:2:13:2:20 | SOURCE() | tst.js:3:10:3:10 | v |
|
||||||
| tst.js:2:13:2:20 | SOURCE() | tst.js:8:14:8:14 | v |
|
| tst.js:2:13:2:20 | SOURCE() | tst.js:8:14:8:14 | v |
|
||||||
| tst.js:2:13:2:20 | SOURCE() | tst.js:12:14:12:14 | v |
|
| tst.js:2:13:2:20 | SOURCE() | tst.js:12:14:12:14 | v |
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ nodes
|
|||||||
| build-leaks.js:14:18:14:20 | env |
|
| build-leaks.js:14:18:14:20 | env |
|
||||||
| build-leaks.js:15:24:15:34 | process.env |
|
| build-leaks.js:15:24:15:34 | process.env |
|
||||||
| build-leaks.js:15:24:15:34 | process.env |
|
| build-leaks.js:15:24:15:34 | process.env |
|
||||||
|
| build-leaks.js:15:24:15:39 | process.env[key] |
|
||||||
| build-leaks.js:16:20:16:22 | env |
|
| build-leaks.js:16:20:16:22 | env |
|
||||||
| build-leaks.js:21:11:26:5 | stringifed |
|
| build-leaks.js:21:11:26:5 | stringifed |
|
||||||
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
|
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
|
||||||
| build-leaks.js:22:24:25:14 | Object. ... }, {}) |
|
| build-leaks.js:22:24:25:14 | Object. ... }, {}) |
|
||||||
| build-leaks.js:22:49:22:51 | env |
|
| build-leaks.js:22:49:22:51 | env |
|
||||||
|
| build-leaks.js:23:24:23:47 | JSON.st ... w[key]) |
|
||||||
| build-leaks.js:23:39:23:41 | raw |
|
| build-leaks.js:23:39:23:41 | raw |
|
||||||
|
| build-leaks.js:23:39:23:46 | raw[key] |
|
||||||
| build-leaks.js:24:20:24:22 | env |
|
| build-leaks.js:24:20:24:22 | env |
|
||||||
| build-leaks.js:30:22:30:31 | stringifed |
|
| build-leaks.js:30:22:30:31 | stringifed |
|
||||||
| build-leaks.js:34:26:34:57 | getEnv( ... ngified |
|
| build-leaks.js:34:26:34:57 | getEnv( ... ngified |
|
||||||
@@ -36,13 +39,19 @@ edges
|
|||||||
| build-leaks.js:14:18:14:20 | env | build-leaks.js:16:20:16:22 | env |
|
| build-leaks.js:14:18:14:20 | env | build-leaks.js:16:20:16:22 | env |
|
||||||
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
|
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
|
||||||
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
|
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:14:18:14:20 | env |
|
||||||
|
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:15:24:15:39 | process.env[key] |
|
||||||
|
| build-leaks.js:15:24:15:34 | process.env | build-leaks.js:15:24:15:39 | process.env[key] |
|
||||||
|
| build-leaks.js:15:24:15:39 | process.env[key] | build-leaks.js:14:18:14:20 | env |
|
||||||
| build-leaks.js:16:20:16:22 | env | build-leaks.js:13:17:19:10 | Object. ... }) |
|
| build-leaks.js:16:20:16:22 | env | build-leaks.js:13:17:19:10 | Object. ... }) |
|
||||||
| build-leaks.js:16:20:16:22 | env | build-leaks.js:14:18:14:20 | env |
|
| build-leaks.js:16:20:16:22 | env | build-leaks.js:14:18:14:20 | env |
|
||||||
| build-leaks.js:21:11:26:5 | stringifed | build-leaks.js:30:22:30:31 | stringifed |
|
| build-leaks.js:21:11:26:5 | stringifed | build-leaks.js:30:22:30:31 | stringifed |
|
||||||
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } | build-leaks.js:21:11:26:5 | stringifed |
|
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } | build-leaks.js:21:11:26:5 | stringifed |
|
||||||
| build-leaks.js:22:24:25:14 | Object. ... }, {}) | build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
|
| build-leaks.js:22:24:25:14 | Object. ... }, {}) | build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
|
||||||
| build-leaks.js:22:49:22:51 | env | build-leaks.js:24:20:24:22 | env |
|
| build-leaks.js:22:49:22:51 | env | build-leaks.js:24:20:24:22 | env |
|
||||||
|
| build-leaks.js:23:24:23:47 | JSON.st ... w[key]) | build-leaks.js:22:49:22:51 | env |
|
||||||
| build-leaks.js:23:39:23:41 | raw | build-leaks.js:22:49:22:51 | env |
|
| build-leaks.js:23:39:23:41 | raw | build-leaks.js:22:49:22:51 | env |
|
||||||
|
| build-leaks.js:23:39:23:41 | raw | build-leaks.js:23:39:23:46 | raw[key] |
|
||||||
|
| build-leaks.js:23:39:23:46 | raw[key] | build-leaks.js:23:24:23:47 | JSON.st ... w[key]) |
|
||||||
| build-leaks.js:24:20:24:22 | env | build-leaks.js:22:24:25:14 | Object. ... }, {}) |
|
| build-leaks.js:24:20:24:22 | env | build-leaks.js:22:24:25:14 | Object. ... }, {}) |
|
||||||
| build-leaks.js:24:20:24:22 | env | build-leaks.js:22:49:22:51 | env |
|
| build-leaks.js:24:20:24:22 | env | build-leaks.js:22:49:22:51 | env |
|
||||||
| build-leaks.js:30:22:30:31 | stringifed | build-leaks.js:34:26:34:57 | getEnv( ... ngified |
|
| build-leaks.js:30:22:30:31 | stringifed | build-leaks.js:34:26:34:57 | getEnv( ... ngified |
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -978,6 +978,12 @@ module API {
|
|||||||
pred = Impl::MkModuleInstanceUp(mod) and
|
pred = Impl::MkModuleInstanceUp(mod) and
|
||||||
succ = getBackwardEndNode(mod.getOwnInstanceMethod("call"))
|
succ = getBackwardEndNode(mod.getOwnInstanceMethod("call"))
|
||||||
)
|
)
|
||||||
|
or
|
||||||
|
// Step through callable wrappers like `proc` and `lambda` calls.
|
||||||
|
exists(DataFlow::Node node |
|
||||||
|
pred = getBackwardEndNode(node) and
|
||||||
|
succ = getBackwardStartNode(node.asCallable())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pragma[nomagic]
|
pragma[nomagic]
|
||||||
|
|||||||
@@ -843,6 +843,58 @@ module XmlParserCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-flow node that constructs an XPath expression.
|
||||||
|
*
|
||||||
|
* If it is important that the XPath expression is indeed executed, then use `XPathExecution`.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `XPathConstruction::Range` instead.
|
||||||
|
*/
|
||||||
|
class XPathConstruction extends DataFlow::Node instanceof XPathConstruction::Range {
|
||||||
|
/** Gets the argument that specifies the XPath expressions to be constructed. */
|
||||||
|
DataFlow::Node getXPath() { result = super.getXPath() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides a class for modeling new XPath construction APIs. */
|
||||||
|
module XPathConstruction {
|
||||||
|
/**
|
||||||
|
* A data-flow node that constructs an XPath expression.
|
||||||
|
*
|
||||||
|
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||||
|
* extend `XPathConstruction` instead.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/** Gets the argument that specifies the XPath expressions to be constructed. */
|
||||||
|
abstract DataFlow::Node getXPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-flow node that executes an XPath expression.
|
||||||
|
*
|
||||||
|
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||||
|
* extend `XPathExecution::Range` instead.
|
||||||
|
*/
|
||||||
|
class XPathExecution extends DataFlow::Node instanceof XPathExecution::Range {
|
||||||
|
/** Gets the argument that specifies the XPath expressions to be executed. */
|
||||||
|
DataFlow::Node getXPath() { result = super.getXPath() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Provides a class for modeling new XPath execution APIs. */
|
||||||
|
module XPathExecution {
|
||||||
|
/**
|
||||||
|
* A data-flow node that executes an XPath expression.
|
||||||
|
*
|
||||||
|
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||||
|
* extend `XPathExecution` instead.
|
||||||
|
*/
|
||||||
|
abstract class Range extends DataFlow::Node {
|
||||||
|
/** Gets the argument that specifies the XPath expressions to be executed. */
|
||||||
|
abstract DataFlow::Node getXPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data-flow node that may represent a database object in an ORM system.
|
* A data-flow node that may represent a database object in an ORM system.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1333,11 +1333,20 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
|||||||
creation.asExpr() =
|
creation.asExpr() =
|
||||||
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
||||||
c.asCallable() = mc.getBlock().getExpr() and
|
c.asCallable() = mc.getBlock().getExpr() and
|
||||||
mc.getExpr().getMethodName() = ["lambda", "proc"]
|
isProcCreationCall(mc.getExpr())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if `call` is a call to `lambda`, `proc`, or `Proc.new` */
|
||||||
|
pragma[nomagic]
|
||||||
|
private predicate isProcCreationCall(MethodCall call) {
|
||||||
|
call.getMethodName() = ["proc", "lambda"]
|
||||||
|
or
|
||||||
|
call.getMethodName() = "new" and
|
||||||
|
call.getReceiver().(ConstantReadAccess).getAQualifiedName() = "Proc"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
|
* Holds if `call` is a from-source lambda call of kind `kind` where `receiver`
|
||||||
* is the lambda expression.
|
* is the lambda expression.
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::Call
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Execution of a XPath statement. */
|
||||||
|
private class NokogiriXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||||
|
NokogiriXPathExecution() {
|
||||||
|
exists(NokogiriXmlParserCall parserCall |
|
||||||
|
this = parserCall.getAMethodCall(["xpath", "at_xpath", "search", "at"])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getXPath() { result = this.getArgument(0) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds if `assign` enables the `default_substitute_entities` option in
|
* Holds if `assign` enables the `default_substitute_entities` option in
|
||||||
* libxml-ruby.
|
* libxml-ruby.
|
||||||
@@ -123,6 +134,40 @@ private predicate xmlMiniEntitySubstitutionEnabled() {
|
|||||||
enablesLibXmlDefaultEntitySubstitution(_)
|
enablesLibXmlDefaultEntitySubstitution(_)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Execution of a XPath statement. */
|
||||||
|
private class LibXmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||||
|
LibXmlXPathExecution() {
|
||||||
|
exists(LibXmlRubyXmlParserCall parserCall |
|
||||||
|
this = parserCall.getAMethodCall(["find", "find_first"])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getXPath() { result = this.getArgument(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A call to `REXML::Document.new`, considered as a XML parsing. */
|
||||||
|
private class RexmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
|
||||||
|
RexmlParserCall() {
|
||||||
|
this = API::getTopLevelMember("REXML").getMember("Document").getAnInstantiation()
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||||
|
|
||||||
|
/** No option for parsing */
|
||||||
|
override predicate externalEntitiesEnabled() { none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Execution of a XPath statement. */
|
||||||
|
private class RexmlXPathExecution extends XPathExecution::Range, DataFlow::CallNode {
|
||||||
|
RexmlXPathExecution() {
|
||||||
|
this =
|
||||||
|
[API::getTopLevelMember("REXML").getMember("XPath"), API::getTopLevelMember("XPath")]
|
||||||
|
.getAMethodCall(["each", "first", "match"])
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getXPath() { result = this.getArgument(1) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A call to `ActiveSupport::XmlMini.parse` considered as an `XmlParserCall`.
|
* A call to `ActiveSupport::XmlMini.parse` considered as an `XmlParserCall`.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,16 +18,7 @@ private class PotentialRequestHandler extends DataFlow::CallableNode {
|
|||||||
(
|
(
|
||||||
this.(DataFlow::MethodNode).getMethodName() = "call"
|
this.(DataFlow::MethodNode).getMethodName() = "call"
|
||||||
or
|
or
|
||||||
not this instanceof DataFlow::MethodNode and
|
this = API::getTopLevelCall("run").getArgument(0).asCallable()
|
||||||
exists(DataFlow::CallNode cn | cn.getMethodName() = "run" |
|
|
||||||
this.(DataFlow::LocalSourceNode).flowsTo(cn.getArgument(0))
|
|
||||||
or
|
|
||||||
// TODO: `Proc.new` should automatically propagate flow from its block argument
|
|
||||||
any(DataFlow::CallNode proc |
|
|
||||||
proc = API::getTopLevelMember("Proc").getAnInstantiation() and
|
|
||||||
proc.getBlock() = this
|
|
||||||
).(DataFlow::LocalSourceNode).flowsTo(cn.getArgument(0))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Provides class and predicates to track external data that
|
||||||
|
* may represent malicious xpath query objects.
|
||||||
|
*
|
||||||
|
* This module is intended to be imported into a taint-tracking query.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import codeql.ruby.Concepts
|
||||||
|
private import codeql.ruby.DataFlow
|
||||||
|
private import codeql.ruby.dataflow.BarrierGuards
|
||||||
|
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||||
|
|
||||||
|
/** Models Xpath Injection related classes and functions */
|
||||||
|
module XpathInjection {
|
||||||
|
/** A data flow source for "XPath injection" vulnerabilities. */
|
||||||
|
abstract class Source extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/** A data flow sink for "XPath injection" vulnerabilities */
|
||||||
|
abstract class Sink extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/** A sanitizer for "XPath injection" vulnerabilities. */
|
||||||
|
abstract class Sanitizer extends DataFlow::Node { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A source of remote user input, considered as a flow source.
|
||||||
|
*/
|
||||||
|
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An execution of an XPath expression, considered as a sink.
|
||||||
|
*/
|
||||||
|
private class XPathExecutionAsSink extends Sink {
|
||||||
|
XPathExecutionAsSink() { this = any(XPathExecution e).getXPath() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A construction of an XPath expression, considered as a sink.
|
||||||
|
*/
|
||||||
|
private class XPathConstructionAsSink extends Sink {
|
||||||
|
XPathConstructionAsSink() { this = any(XPathConstruction c).getXPath() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||||
|
*/
|
||||||
|
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An inclusion check against an array of constant strings, considered as a
|
||||||
|
* sanitizer-guard.
|
||||||
|
*/
|
||||||
|
private class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||||
|
StringConstArrayInclusionCallBarrier
|
||||||
|
{ }
|
||||||
|
}
|
||||||
27
ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll
Normal file
27
ruby/ql/lib/codeql/ruby/security/XpathInjectionQuery.qll
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||||
|
*
|
||||||
|
* Note, for performance reasons: only import this file if
|
||||||
|
* `XpathInjection::Configuration` is needed, otherwise
|
||||||
|
* `XpathInjectionCustomizations` should be imported instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private import codeql.ruby.DataFlow
|
||||||
|
private import codeql.ruby.TaintTracking
|
||||||
|
import XpathInjectionCustomizations::XpathInjection
|
||||||
|
|
||||||
|
/** Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities. */
|
||||||
|
module XpathInjection {
|
||||||
|
/**
|
||||||
|
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||||
|
*/
|
||||||
|
private module Config implements DataFlow::ConfigSig {
|
||||||
|
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||||
|
|
||||||
|
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||||
|
|
||||||
|
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||||
|
}
|
||||||
|
|
||||||
|
import TaintTracking::Global<Config>
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: newQuery
|
||||||
|
---
|
||||||
|
* Added a new experimental query, `rb/xpath-injection`, to detect cases where XPath statements are constructed from user input in an unsafe manner.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: minorAnalysis
|
||||||
|
---
|
||||||
|
* Improved resolution of calls performed on an object created with `Proc.new`.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
|
||||||
|
<overview>
|
||||||
|
<p>
|
||||||
|
If an XPath expression is built using string concatenation, and the components of the concatenation
|
||||||
|
include user input, it makes it very easy for a user to create a malicious XPath expression.
|
||||||
|
</p>
|
||||||
|
</overview>
|
||||||
|
|
||||||
|
<recommendation>
|
||||||
|
<p>
|
||||||
|
If user input must be included in an XPath expression, either sanitize the data or use variable
|
||||||
|
references to safely embed it without altering the structure of the expression.
|
||||||
|
</p>
|
||||||
|
</recommendation>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
<p>
|
||||||
|
The following example uses the <code>nokogiri</code>, <code>rexml</code> and <code>libxml</code> XML parsers to parse a string <code>xml</code>.
|
||||||
|
Then the xpath query is controlled by the user and hence leads to a vulnerability.
|
||||||
|
</p>
|
||||||
|
<sample src="examples/XPathBad.rb"/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To guard against XPath Injection attacks, the user input should be sanitized.
|
||||||
|
</p>
|
||||||
|
<sample src="examples/XPathGood.rb"/>
|
||||||
|
</example>
|
||||||
|
|
||||||
|
<references>
|
||||||
|
<li>
|
||||||
|
OWASP:
|
||||||
|
<a href="https://owasp.org/www-community/attacks/XPATH_Injection">XPath injection</a>.
|
||||||
|
</li>
|
||||||
|
</references>
|
||||||
|
</qhelp>
|
||||||
21
ruby/ql/src/experimental/xpath-injection/XpathInjection.ql
Normal file
21
ruby/ql/src/experimental/xpath-injection/XpathInjection.ql
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @name XPath query built from user-controlled sources
|
||||||
|
* @description Building a XPath query from user-controlled sources is vulnerable to insertion of
|
||||||
|
* malicious XPath code by the user.
|
||||||
|
* @kind path-problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @security-severity 9.8
|
||||||
|
* @precision high
|
||||||
|
* @id rb/xpath-injection
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-643
|
||||||
|
*/
|
||||||
|
|
||||||
|
import codeql.ruby.DataFlow
|
||||||
|
import codeql.ruby.security.XpathInjectionQuery
|
||||||
|
import XpathInjection::PathGraph
|
||||||
|
|
||||||
|
from XpathInjection::PathNode source, XpathInjection::PathNode sink
|
||||||
|
where XpathInjection::flowPath(source, sink)
|
||||||
|
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
|
||||||
|
"user-provided value"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
require 'nokogiri'
|
||||||
|
require 'rexml'
|
||||||
|
require 'libxml'
|
||||||
|
|
||||||
|
class BadNokogiriController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = Nokogiri::XML.parse(xml)
|
||||||
|
results = doc.xpath("//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BadRexmlController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
results = REXML::XPath.first(doc, "//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BadLibxmlController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = LibXML::XML::Document.string(xml)
|
||||||
|
results = doc.find_first("//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
require 'nokogiri'
|
||||||
|
require 'rexml'
|
||||||
|
require 'libxml'
|
||||||
|
|
||||||
|
class BadNokogiriController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = Nokogiri::XML.parse(xml)
|
||||||
|
name = if ["foo", "foo2"].include? name
|
||||||
|
name
|
||||||
|
else
|
||||||
|
name = "foo"
|
||||||
|
end
|
||||||
|
results = doc.xpath("//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BadRexmlController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
name = if ["foo", "foo2"].include? name
|
||||||
|
name
|
||||||
|
else
|
||||||
|
name = "foo"
|
||||||
|
end
|
||||||
|
results = REXML::XPath.first(doc, "//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BadLibxmlController < ActionController::Base
|
||||||
|
def some_request_handler
|
||||||
|
name = params["name"]
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
doc = LibXML::XML::Document.string(xml)
|
||||||
|
name = if ["foo", "foo2"].include? name
|
||||||
|
name
|
||||||
|
else
|
||||||
|
name = "foo"
|
||||||
|
end
|
||||||
|
results = doc.find_first("//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Foo.bar proc { |x|
|
||||||
|
x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
Foo.bar lambda { |x|
|
||||||
|
x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
Foo.bar Proc.new { |x|
|
||||||
|
x # $ reachableFromSource=Member[Foo].Method[bar].Argument[0].Parameter[0]
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
require 'libxml'
|
||||||
|
|
||||||
|
class FooController < ActionController::Base
|
||||||
|
def libxml_handler(event:, context:)
|
||||||
|
name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = LibXML::XML::Document.string(xml)
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results1 = doc.find_first('//foo')
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results2 = doc.find_first("//#{name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results3 = doc.find('//foo')
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results4 = doc.find("//#{name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BarController < ActionController::Base
|
||||||
|
def libxml_safe_handler(event:, context:)
|
||||||
|
safe_name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
|
||||||
|
# GOOD: barrier guard prevents taint flow
|
||||||
|
safe_name = if ["foo", "foo2"].include? safe_name
|
||||||
|
safe_name
|
||||||
|
else
|
||||||
|
safe_name = "foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results5 = doc.find_first("//#{safe_name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results6 = doc.find("//#{safe_name}")
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
require 'nokogiri'
|
||||||
|
|
||||||
|
class FooController < ActionController::Base
|
||||||
|
def nokogiri_handler(event:, context:)
|
||||||
|
name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = Nokogiri::XML.parse(xml)
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results1 = doc.at('//foo')
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results2 = doc.at("//#{name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results3 = doc.xpath('//foo')
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results4 = doc.xpath("//#{name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results5 = doc.at_xpath('//foo')
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results6 = doc.at_xpath("//#{name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
doc.xpath('//foo').each do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
|
||||||
|
# BAD: XPath query constructed from user input
|
||||||
|
doc.xpath("//#{name}").each do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
doc.search('//foo').each do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
|
||||||
|
# BAD: XPath query constructed from user input
|
||||||
|
doc.search("//#{name}").each do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BarController < ActionController::Base
|
||||||
|
def nokogiri_safe_handler(event:, context:)
|
||||||
|
safe_name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = Nokogiri::XML.parse(xml)
|
||||||
|
|
||||||
|
# GOOD: barrier guard prevents taint flow
|
||||||
|
safe_name = if ["foo", "foo2"].include? safe_name
|
||||||
|
safe_name
|
||||||
|
else
|
||||||
|
safe_name = "foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results7 = doc.at("//#{safe_name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results8 = doc.xpath("//#{safe_name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results9 = doc.at_xpath("//#{safe_name}")
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
require 'rexml'
|
||||||
|
|
||||||
|
class FooController < ActionController::Base
|
||||||
|
def rexml_handler(event:, context:)
|
||||||
|
name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results1 = REXML::XPath.first(doc, "//foo")
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results2 = REXML::XPath.first(doc, "//#{name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
results3 = REXML::XPath.match(doc, "//foo", nil)
|
||||||
|
|
||||||
|
# BAD: XPath query is constructed from user input
|
||||||
|
results4 = REXML::XPath.match(doc, "//#{name}", nil)
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from user input
|
||||||
|
REXML::XPath.each(doc, "//foo") do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
|
||||||
|
# BAD: XPath query constructed from user input
|
||||||
|
REXML::XPath.each(doc, "//#{name}") do |element|
|
||||||
|
puts element.text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BarController < ActionController::Base
|
||||||
|
def rexml_safe_handler(event:, context:)
|
||||||
|
safe_name = params[:user_name]
|
||||||
|
|
||||||
|
xml = <<-XML
|
||||||
|
<root>
|
||||||
|
<foo>bar</foo>
|
||||||
|
<password>THIS IS SECRET</password>
|
||||||
|
</root>
|
||||||
|
XML
|
||||||
|
|
||||||
|
# Parse the XML
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
|
||||||
|
# GOOD: barrier guard prevents taint flow
|
||||||
|
safe_name = if ["foo", "foo2"].include? safe_name
|
||||||
|
safe_name
|
||||||
|
else
|
||||||
|
safe_name = "foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results5 = REXML::XPath.first(doc, "//#{safe_name}")
|
||||||
|
|
||||||
|
# GOOD: XPath query is not constructed from unsanitized user input
|
||||||
|
results6 = REXML::XPath.match(doc, "//#{safe_name}", nil)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
edges
|
||||||
|
| LibxmlInjection.rb:5:5:5:8 | name | LibxmlInjection.rb:21:31:21:41 | "//#{...}" |
|
||||||
|
| LibxmlInjection.rb:5:5:5:8 | name | LibxmlInjection.rb:27:25:27:35 | "//#{...}" |
|
||||||
|
| LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:5:12:5:29 | ...[...] |
|
||||||
|
| LibxmlInjection.rb:5:12:5:29 | ...[...] | LibxmlInjection.rb:5:5:5:8 | name |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:21:23:21:33 | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:27:26:27:36 | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:33:29:33:39 | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:41:15:41:25 | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | NokogiriInjection.rb:51:16:51:26 | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:5:12:5:29 | ...[...] |
|
||||||
|
| NokogiriInjection.rb:5:12:5:29 | ...[...] | NokogiriInjection.rb:5:5:5:8 | name |
|
||||||
|
| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:21:40:21:50 | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:27:40:27:50 | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:5:5:5:8 | name | RexmlInjection.rb:35:28:35:38 | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:5:12:5:29 | ...[...] |
|
||||||
|
| RexmlInjection.rb:5:12:5:29 | ...[...] | RexmlInjection.rb:5:5:5:8 | name |
|
||||||
|
nodes
|
||||||
|
| LibxmlInjection.rb:5:5:5:8 | name | semmle.label | name |
|
||||||
|
| LibxmlInjection.rb:5:12:5:17 | call to params | semmle.label | call to params |
|
||||||
|
| LibxmlInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] |
|
||||||
|
| LibxmlInjection.rb:21:31:21:41 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| LibxmlInjection.rb:27:25:27:35 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:5:5:5:8 | name | semmle.label | name |
|
||||||
|
| NokogiriInjection.rb:5:12:5:17 | call to params | semmle.label | call to params |
|
||||||
|
| NokogiriInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] |
|
||||||
|
| NokogiriInjection.rb:21:23:21:33 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:27:26:27:36 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:33:29:33:39 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:41:15:41:25 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| NokogiriInjection.rb:51:16:51:26 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:5:5:5:8 | name | semmle.label | name |
|
||||||
|
| RexmlInjection.rb:5:12:5:17 | call to params | semmle.label | call to params |
|
||||||
|
| RexmlInjection.rb:5:12:5:29 | ...[...] | semmle.label | ...[...] |
|
||||||
|
| RexmlInjection.rb:21:40:21:50 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:27:40:27:50 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
| RexmlInjection.rb:35:28:35:38 | "//#{...}" | semmle.label | "//#{...}" |
|
||||||
|
subpaths
|
||||||
|
#select
|
||||||
|
| LibxmlInjection.rb:21:31:21:41 | "//#{...}" | LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:21:31:21:41 | "//#{...}" | XPath expression depends on a $@. | LibxmlInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| LibxmlInjection.rb:27:25:27:35 | "//#{...}" | LibxmlInjection.rb:5:12:5:17 | call to params | LibxmlInjection.rb:27:25:27:35 | "//#{...}" | XPath expression depends on a $@. | LibxmlInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| NokogiriInjection.rb:21:23:21:33 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:21:23:21:33 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| NokogiriInjection.rb:27:26:27:36 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:27:26:27:36 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| NokogiriInjection.rb:33:29:33:39 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:33:29:33:39 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| NokogiriInjection.rb:41:15:41:25 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:41:15:41:25 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| NokogiriInjection.rb:51:16:51:26 | "//#{...}" | NokogiriInjection.rb:5:12:5:17 | call to params | NokogiriInjection.rb:51:16:51:26 | "//#{...}" | XPath expression depends on a $@. | NokogiriInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| RexmlInjection.rb:21:40:21:50 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:21:40:21:50 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| RexmlInjection.rb:27:40:27:50 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:27:40:27:50 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
|
| RexmlInjection.rb:35:28:35:38 | "//#{...}" | RexmlInjection.rb:5:12:5:17 | call to params | RexmlInjection.rb:35:28:35:38 | "//#{...}" | XPath expression depends on a $@. | RexmlInjection.rb:5:12:5:17 | call to params | user-provided value |
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
experimental/xpath-injection/XpathInjection.ql
|
||||||
4
swift/ql/lib/change-notes/2023-05-30-sensitive-exprs.md
Normal file
4
swift/ql/lib/change-notes/2023-05-30-sensitive-exprs.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: minorAnalysis
|
||||||
|
---
|
||||||
|
* Added new heuristics to `SensitiveExprs.qll`, enhancing detection from the library.
|
||||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
|
|
||||||
ParamNodeEx getParamNode() { result = p }
|
ParamNodeEx getParamNode() { result = p }
|
||||||
|
|
||||||
override string toString() { result = p + ": " + ap }
|
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||||
|
|
||||||
predicate hasLocationInfo(
|
predicate hasLocationInfo(
|
||||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||||
@@ -2758,12 +2758,21 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private predicate forceUnfold(AccessPathApprox apa) {
|
||||||
|
forceHighPrecision(apa.getHead())
|
||||||
|
or
|
||||||
|
exists(Content c2 |
|
||||||
|
apa = TConsCons(_, _, c2, _) and
|
||||||
|
forceHighPrecision(c2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
|
||||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||||
*/
|
*/
|
||||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||||
if forceHighPrecision(apa.getHead())
|
if forceUnfold(apa)
|
||||||
then unfold = true
|
then unfold = true
|
||||||
else
|
else
|
||||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
result = " <" + this.(PathNodeMid).getCallContext().toString() + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ppSummaryCtx() {
|
||||||
|
this instanceof PathNodeSink and result = ""
|
||||||
|
or
|
||||||
|
result = " <" + this.(PathNodeMid).getSummaryCtx().toString() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets a textual representation of this element. */
|
/** Gets a textual representation of this element. */
|
||||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||||
|
|
||||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
|||||||
* representation of the call context.
|
* representation of the call context.
|
||||||
*/
|
*/
|
||||||
string toStringWithContext() {
|
string toStringWithContext() {
|
||||||
result = this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx()
|
result =
|
||||||
|
this.getNodeEx().toString() + this.ppType() + this.ppAp() + this.ppCtx() +
|
||||||
|
this.ppSummaryCtx()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class SensitiveCredential extends SensitiveDataType, TCredential {
|
|||||||
result = HeuristicNames::maybeSensitiveRegexp(classification)
|
result = HeuristicNames::maybeSensitiveRegexp(classification)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
result = "(?is).*(account|accnt|license).?(id|key).*"
|
result = "(?is).*((account|accnt|licen(se|ce)).?(id|key)|one.?time.?code|pass.?phrase).*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,21 +50,26 @@ class SensitivePrivateInfo extends SensitiveDataType, TPrivateInfo {
|
|||||||
"(?is).*(" +
|
"(?is).*(" +
|
||||||
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
|
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
|
||||||
// Government identifiers, such as Social Security Numbers
|
// Government identifiers, such as Social Security Numbers
|
||||||
"social.?security|national.?insurance|" +
|
"social.?security|employer.?identification|national.?insurance|resident.?id|" +
|
||||||
|
"passport.?(num|no)|" +
|
||||||
// Contact information, such as home addresses
|
// Contact information, such as home addresses
|
||||||
"post.?code|zip.?code|home.?address|" +
|
"post.?code|zip.?code|home.?addr|" +
|
||||||
// and telephone numbers
|
// and telephone numbers
|
||||||
"(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax).?(num|no)|telephone|" +
|
"(mob(ile)?|home).?(num|no|tel|phone)|(tel|fax).?(num|no|phone)|" + "emergency.?contact|" +
|
||||||
// Geographic location - where the user is (or was)
|
// Geographic location - where the user is (or was)
|
||||||
"latitude|longitude|" +
|
"l(atitude|ongitude)|nationality|" +
|
||||||
// Financial data - such as credit card numbers, salary, bank accounts, and debts
|
// Financial data - such as credit card numbers, salary, bank accounts, and debts
|
||||||
"credit.?card|debit.?card|salary|bank.?account|acc(ou)?nt.?(no|num)|" +
|
"(credit|debit|bank|visa).?(card|num|no|acc(ou?)nt)|acc(ou)?nt.?(no|num|credit)|" +
|
||||||
|
"salary|billing|credit.?(rating|score)|" +
|
||||||
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
|
// Communications - e-mail addresses, private e-mail messages, SMS text messages, chat logs, etc.
|
||||||
"email|" +
|
"e(mail|_mail)|" +
|
||||||
// Health - medical conditions, insurance status, prescription records
|
// Health - medical conditions, insurance status, prescription records
|
||||||
"birthday|birth.?date|date.?of.?birth|medical|" +
|
"birth.?da(te|y)|da(te|y).?(of.?)?birth|" +
|
||||||
|
"medical|(health|care).?plan|healthkit|appointment|prescription|" +
|
||||||
|
"blood.?(type|alcohol|glucose|pressure)|heart.?(rate|rhythm)|body.?(mass|fat)|" +
|
||||||
|
"menstrua|pregnan|insulin|inhaler|" +
|
||||||
// Relationships - work and family
|
// Relationships - work and family
|
||||||
"employer|spouse" +
|
"employ(er|ee)|spouse|maiden.?name" +
|
||||||
// ---
|
// ---
|
||||||
").*"
|
").*"
|
||||||
}
|
}
|
||||||
@@ -80,13 +85,32 @@ private string regexpProbablySafe() {
|
|||||||
result = "(?is).*(file|path|url|invalid).*"
|
result = "(?is).*(file|path|url|invalid).*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a string that is to be tested for sensitivity.
|
||||||
|
*/
|
||||||
|
private string sensitiveCandidateStrings() {
|
||||||
|
result = any(VarDecl v).getName()
|
||||||
|
or
|
||||||
|
result = any(Function f).getShortName()
|
||||||
|
or
|
||||||
|
result = any(Argument a).getLabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a string from the candidates that is sensitive.
|
||||||
|
*/
|
||||||
|
private string sensitiveStrings(SensitiveDataType sensitiveType) {
|
||||||
|
result = sensitiveCandidateStrings() and
|
||||||
|
result.regexpMatch(sensitiveType.getRegexp())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `VarDecl` that might be used to contain sensitive data.
|
* A `VarDecl` that might be used to contain sensitive data.
|
||||||
*/
|
*/
|
||||||
private class SensitiveVarDecl extends VarDecl {
|
private class SensitiveVarDecl extends VarDecl {
|
||||||
SensitiveDataType sensitiveType;
|
SensitiveDataType sensitiveType;
|
||||||
|
|
||||||
SensitiveVarDecl() { this.getName().regexpMatch(sensitiveType.getRegexp()) }
|
SensitiveVarDecl() { this.getName() = sensitiveStrings(sensitiveType) }
|
||||||
|
|
||||||
predicate hasInfo(string label, SensitiveDataType type) {
|
predicate hasInfo(string label, SensitiveDataType type) {
|
||||||
label = this.getName() and
|
label = this.getName() and
|
||||||
@@ -99,15 +123,11 @@ private class SensitiveVarDecl extends VarDecl {
|
|||||||
*/
|
*/
|
||||||
private class SensitiveFunction extends Function {
|
private class SensitiveFunction extends Function {
|
||||||
SensitiveDataType sensitiveType;
|
SensitiveDataType sensitiveType;
|
||||||
string name; // name of the function, not including the argument list.
|
|
||||||
|
|
||||||
SensitiveFunction() {
|
SensitiveFunction() { this.getShortName() = sensitiveStrings(sensitiveType) }
|
||||||
name = this.getShortName() and
|
|
||||||
name.regexpMatch(sensitiveType.getRegexp())
|
|
||||||
}
|
|
||||||
|
|
||||||
predicate hasInfo(string label, SensitiveDataType type) {
|
predicate hasInfo(string label, SensitiveDataType type) {
|
||||||
label = name and
|
label = this.getShortName() and
|
||||||
sensitiveType = type
|
sensitiveType = type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +138,7 @@ private class SensitiveFunction extends Function {
|
|||||||
private class SensitiveArgument extends Argument {
|
private class SensitiveArgument extends Argument {
|
||||||
SensitiveDataType sensitiveType;
|
SensitiveDataType sensitiveType;
|
||||||
|
|
||||||
SensitiveArgument() { this.getLabel().regexpMatch(sensitiveType.getRegexp()) }
|
SensitiveArgument() { this.getLabel() = sensitiveStrings(sensitiveType) }
|
||||||
|
|
||||||
predicate hasInfo(string label, SensitiveDataType type) {
|
predicate hasInfo(string label, SensitiveDataType type) {
|
||||||
label = this.getLabel() and
|
label = this.getLabel() and
|
||||||
@@ -169,6 +189,7 @@ class SensitiveExpr extends Expr {
|
|||||||
* A function that is likely used to encrypt or hash data.
|
* A function that is likely used to encrypt or hash data.
|
||||||
*/
|
*/
|
||||||
private class EncryptionFunction extends Function {
|
private class EncryptionFunction extends Function {
|
||||||
|
cached
|
||||||
EncryptionFunction() { this.getName().regexpMatch("(?is).*(crypt|hash|encode|protect).*") }
|
EncryptionFunction() { this.getName().regexpMatch("(?is).*(crypt|hash|encode|protect).*") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
swift/ql/src/change-notes/2023-06-23-redos-query.md
Normal file
4
swift/ql/src/change-notes/2023-06-23-redos-query.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
category: newQuery
|
||||||
|
---
|
||||||
|
* Added new query "Inefficient regular expression" (`swift/redos`). This query finds regular expressions that require exponential time to match certain inputs and may make an application vulnerable to denial-of-service attacks.
|
||||||
26
swift/ql/src/queries/Security/CWE-1333/ReDoS.qhelp
Normal file
26
swift/ql/src/queries/Security/CWE-1333/ReDoS.qhelp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
<include src="ReDoSIntroduction.inc.qhelp" />
|
||||||
|
<example>
|
||||||
|
<p>Consider the following regular expression:</p>
|
||||||
|
<sample language="swift">
|
||||||
|
/^_(__|.)+_$/</sample>
|
||||||
|
<p>
|
||||||
|
Its sub-expression <code>"(__|.)+"</code> can match the string
|
||||||
|
<code>"__"</code> either by the first alternative <code>"__"</code> to the
|
||||||
|
left of the <code>"|"</code> operator, or by two repetitions of the second
|
||||||
|
alternative <code>"."</code> to the right. Therefore, a string consisting of an
|
||||||
|
odd number of underscores followed by some other character will cause the
|
||||||
|
regular expression engine to run for an exponential amount of time before
|
||||||
|
rejecting the input.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This problem can be avoided by rewriting the regular expression to remove
|
||||||
|
the ambiguity between the two branches of the alternative inside the
|
||||||
|
repetition:
|
||||||
|
</p>
|
||||||
|
<sample language="swift">
|
||||||
|
/^_(__|[^_])+_$/</sample>
|
||||||
|
</example>
|
||||||
|
<include src="ReDoSReferences.inc.qhelp"/>
|
||||||
|
</qhelp>
|
||||||
25
swift/ql/src/queries/Security/CWE-1333/ReDoS.ql
Normal file
25
swift/ql/src/queries/Security/CWE-1333/ReDoS.ql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* @name Inefficient regular expression
|
||||||
|
* @description A regular expression that requires exponential time to match certain inputs
|
||||||
|
* can be a performance bottleneck, and may be vulnerable to denial-of-service
|
||||||
|
* attacks.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @security-severity 7.5
|
||||||
|
* @precision high
|
||||||
|
* @id swift/redos
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-1333
|
||||||
|
* external/cwe/cwe-730
|
||||||
|
* external/cwe/cwe-400
|
||||||
|
*/
|
||||||
|
|
||||||
|
import codeql.swift.regex.Regex
|
||||||
|
private import codeql.swift.regex.RegexTreeView::RegexTreeView as TreeView
|
||||||
|
import codeql.regex.nfa.ExponentialBackTracking::Make<TreeView>
|
||||||
|
|
||||||
|
from TreeView::RegExpTerm t, string pump, State s, string prefixMsg
|
||||||
|
where hasReDoSResult(t, pump, s, prefixMsg)
|
||||||
|
select t,
|
||||||
|
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
|
||||||
|
"containing many repetitions of '" + pump + "'."
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
<overview>
|
||||||
|
<p>
|
||||||
|
Some regular expressions take a long time to match certain input strings
|
||||||
|
to the point where the time it takes to match a string of length <i>n</i>
|
||||||
|
is proportional to <i>n<sup>k</sup></i> or even <i>2<sup>n</sup></i>.
|
||||||
|
Such regular expressions can negatively affect performance, and potentially allow
|
||||||
|
a malicious user to perform a Denial of Service ("DoS") attack by crafting
|
||||||
|
an expensive input string for the regular expression to match.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The regular expression engine used by Swift uses
|
||||||
|
backtracking non-deterministic finite automata to implement regular
|
||||||
|
expression matching. While this approach is space-efficient and allows
|
||||||
|
supporting advanced features like capture groups, it is not time-efficient
|
||||||
|
in general. The worst-case time complexity of such an automaton can be
|
||||||
|
polynomial or exponential, meaning that for strings of a certain
|
||||||
|
shape, increasing the input length by ten characters may make the
|
||||||
|
automaton about 1000 times slower.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Typically, a regular expression is affected by this problem if it contains
|
||||||
|
a repetition of the form <code>r*</code> or <code>r+</code> where the
|
||||||
|
sub-expression <code>r</code> is ambiguous in the sense that it can match
|
||||||
|
some string in multiple ways. More information about the precise
|
||||||
|
circumstances can be found in the references.
|
||||||
|
</p>
|
||||||
|
</overview>
|
||||||
|
<recommendation>
|
||||||
|
<p>
|
||||||
|
Modify the regular expression to remove the ambiguity, or ensure that the
|
||||||
|
strings matched with the regular expression are short enough that the
|
||||||
|
time complexity does not matter.
|
||||||
|
</p>
|
||||||
|
</recommendation>
|
||||||
|
</qhelp>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
<references>
|
||||||
|
<li> OWASP:
|
||||||
|
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
|
||||||
|
</li>
|
||||||
|
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
|
||||||
|
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
|
||||||
|
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
|
||||||
|
<a href="https://arxiv.org/abs/1301.0849">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
|
||||||
|
</li>
|
||||||
|
</references>
|
||||||
|
</qhelp>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
| ReDoS.swift:65:22:65:22 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
|
||||||
|
| ReDoS.swift:66:22:66:22 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
|
||||||
|
| ReDoS.swift:69:18:69:18 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
|
||||||
|
| ReDoS.swift:77:57:77:57 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
|
||||||
|
| ReDoS.swift:80:57:80:57 | a* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
|
||||||
1
swift/ql/test/query-tests/Security/CWE-1333/ReDoS.qlref
Normal file
1
swift/ql/test/query-tests/Security/CWE-1333/ReDoS.qlref
Normal file
@@ -0,0 +1 @@
|
|||||||
|
queries/Security/CWE-1333/ReDoS.ql
|
||||||
85
swift/ql/test/query-tests/Security/CWE-1333/ReDoS.swift
Normal file
85
swift/ql/test/query-tests/Security/CWE-1333/ReDoS.swift
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
// --- stubs ---
|
||||||
|
|
||||||
|
struct URL {
|
||||||
|
init?(string: String) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnyRegexOutput {
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol RegexComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Regex<Output> : RegexComponent {
|
||||||
|
struct Match {
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ pattern: String) throws where Output == AnyRegexOutput { }
|
||||||
|
|
||||||
|
func firstMatch(in string: String) throws -> Regex<Output>.Match? { return nil}
|
||||||
|
|
||||||
|
typealias RegexOutput = Output
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
init(contentsOf: URL) {
|
||||||
|
let data = ""
|
||||||
|
self.init(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NSObject {
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _NSRange {
|
||||||
|
init(location: Int, length: Int) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias NSRange = _NSRange
|
||||||
|
|
||||||
|
class NSRegularExpression : NSObject {
|
||||||
|
struct Options : OptionSet {
|
||||||
|
var rawValue: UInt
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MatchingOptions : OptionSet {
|
||||||
|
var rawValue: UInt
|
||||||
|
}
|
||||||
|
|
||||||
|
init(pattern: String, options: NSRegularExpression.Options = []) throws { }
|
||||||
|
|
||||||
|
func stringByReplacingMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange, withTemplate templ: String) -> String { return "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- tests ---
|
||||||
|
|
||||||
|
func myRegexpTests(myUrl: URL) throws {
|
||||||
|
let tainted = String(contentsOf: myUrl) // tainted
|
||||||
|
let untainted = "abcdef"
|
||||||
|
|
||||||
|
// Regex
|
||||||
|
|
||||||
|
_ = "((a*)*b)" // GOOD (never used)
|
||||||
|
_ = try Regex("((a*)*b)") // DUBIOUS (never used)
|
||||||
|
_ = try Regex("((a*)*b)").firstMatch(in: untainted) // DUBIOUS (never used on tainted input) [FLAGGED]
|
||||||
|
_ = try Regex("((a*)*b)").firstMatch(in: tainted) // BAD
|
||||||
|
_ = try Regex(".*").firstMatch(in: tainted) // GOOD (safe regex)
|
||||||
|
|
||||||
|
let str = "((a*)*b)" // BAD
|
||||||
|
let regex = try Regex(str)
|
||||||
|
_ = try regex.firstMatch(in: tainted)
|
||||||
|
|
||||||
|
// NSRegularExpression
|
||||||
|
|
||||||
|
_ = try? NSRegularExpression(pattern: "((a*)*b)") // DUBIOUS (never used)
|
||||||
|
|
||||||
|
let nsregex1 = try? NSRegularExpression(pattern: "((a*)*b)") // DUBIOUS (never used on tainted input) [FLAGGED]
|
||||||
|
_ = nsregex1?.stringByReplacingMatches(in: untainted, range: NSRange(location: 0, length: untainted.utf16.count), withTemplate: "")
|
||||||
|
|
||||||
|
let nsregex2 = try? NSRegularExpression(pattern: "((a*)*b)") // BAD
|
||||||
|
_ = nsregex2?.stringByReplacingMatches(in: tainted, range: NSRange(location: 0, length: tainted.utf16.count), withTemplate: "")
|
||||||
|
|
||||||
|
let nsregex3 = try? NSRegularExpression(pattern: ".*") // GOOD (safe regex)
|
||||||
|
_ = nsregex3?.stringByReplacingMatches(in: tainted, range: NSRange(location: 0, length: tainted.utf16.count), withTemplate: "")
|
||||||
|
}
|
||||||
@@ -5,14 +5,17 @@ edges
|
|||||||
| testSend.swift:33:14:33:32 | call to Data.init(_:) | testSend.swift:37:19:37:19 | data2 |
|
| testSend.swift:33:14:33:32 | call to Data.init(_:) | testSend.swift:37:19:37:19 | data2 |
|
||||||
| testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:33:14:33:32 | call to Data.init(_:) |
|
| testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:33:14:33:32 | call to Data.init(_:) |
|
||||||
| testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data |
|
| testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data |
|
||||||
| testSend.swift:52:13:52:13 | password | testSend.swift:59:27:59:27 | str1 |
|
| testSend.swift:58:13:58:13 | password | testSend.swift:65:27:65:27 | str1 |
|
||||||
| testSend.swift:53:13:53:13 | password | testSend.swift:60:27:60:27 | str2 |
|
| testSend.swift:59:13:59:13 | password | testSend.swift:66:27:66:27 | str2 |
|
||||||
| testSend.swift:54:13:54:25 | call to pad(_:) | testSend.swift:61:27:61:27 | str3 |
|
| testSend.swift:60:13:60:25 | call to pad(_:) | testSend.swift:67:27:67:27 | str3 |
|
||||||
| testSend.swift:54:17:54:17 | password | testSend.swift:41:10:41:18 | data |
|
| testSend.swift:60:17:60:17 | password | testSend.swift:41:10:41:18 | data |
|
||||||
| testSend.swift:54:17:54:17 | password | testSend.swift:54:13:54:25 | call to pad(_:) |
|
| testSend.swift:60:17:60:17 | password | testSend.swift:60:13:60:25 | call to pad(_:) |
|
||||||
| testURL.swift:13:54:13:54 | passwd | testURL.swift:13:22:13:54 | ... .+(_:_:) ... |
|
| testURL.swift:17:54:17:54 | passwd | testURL.swift:17:22:17:54 | ... .+(_:_:) ... |
|
||||||
| testURL.swift:15:55:15:55 | account_no | testURL.swift:15:22:15:55 | ... .+(_:_:) ... |
|
| testURL.swift:19:55:19:55 | account_no | testURL.swift:19:22:19:55 | ... .+(_:_:) ... |
|
||||||
| testURL.swift:16:55:16:55 | credit_card_no | testURL.swift:16:22:16:55 | ... .+(_:_:) ... |
|
| testURL.swift:20:55:20:55 | credit_card_no | testURL.swift:20:22:20:55 | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:28:55:28:55 | e_mail | testURL.swift:28:22:28:55 | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:30:57:30:57 | a_homeaddr_z | testURL.swift:30:22:30:57 | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:32:55:32:55 | resident_ID | testURL.swift:32:22:32:55 | ... .+(_:_:) ... |
|
||||||
nodes
|
nodes
|
||||||
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
| testAlamofire.swift:150:45:150:45 | password | semmle.label | password |
|
| testAlamofire.swift:150:45:150:45 | password | semmle.label | password |
|
||||||
@@ -26,36 +29,55 @@ nodes
|
|||||||
| testSend.swift:37:19:37:19 | data2 | semmle.label | data2 |
|
| testSend.swift:37:19:37:19 | data2 | semmle.label | data2 |
|
||||||
| testSend.swift:41:10:41:18 | data | semmle.label | data |
|
| testSend.swift:41:10:41:18 | data | semmle.label | data |
|
||||||
| testSend.swift:41:45:41:45 | data | semmle.label | data |
|
| testSend.swift:41:45:41:45 | data | semmle.label | data |
|
||||||
| testSend.swift:52:13:52:13 | password | semmle.label | password |
|
| testSend.swift:58:13:58:13 | password | semmle.label | password |
|
||||||
| testSend.swift:53:13:53:13 | password | semmle.label | password |
|
| testSend.swift:59:13:59:13 | password | semmle.label | password |
|
||||||
| testSend.swift:54:13:54:25 | call to pad(_:) | semmle.label | call to pad(_:) |
|
| testSend.swift:60:13:60:25 | call to pad(_:) | semmle.label | call to pad(_:) |
|
||||||
| testSend.swift:54:17:54:17 | password | semmle.label | password |
|
| testSend.swift:60:17:60:17 | password | semmle.label | password |
|
||||||
| testSend.swift:59:27:59:27 | str1 | semmle.label | str1 |
|
| testSend.swift:65:27:65:27 | str1 | semmle.label | str1 |
|
||||||
| testSend.swift:60:27:60:27 | str2 | semmle.label | str2 |
|
| testSend.swift:66:27:66:27 | str2 | semmle.label | str2 |
|
||||||
| testSend.swift:61:27:61:27 | str3 | semmle.label | str3 |
|
| testSend.swift:67:27:67:27 | str3 | semmle.label | str3 |
|
||||||
| testSend.swift:65:27:65:27 | license_key | semmle.label | license_key |
|
| testSend.swift:71:27:71:27 | license_key | semmle.label | license_key |
|
||||||
| testSend.swift:66:27:66:30 | .mobileNumber | semmle.label | .mobileNumber |
|
| testSend.swift:72:27:72:30 | .mobileNumber | semmle.label | .mobileNumber |
|
||||||
| testURL.swift:13:22:13:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
| testSend.swift:76:27:76:30 | .Telephone | semmle.label | .Telephone |
|
||||||
| testURL.swift:13:54:13:54 | passwd | semmle.label | passwd |
|
| testSend.swift:77:27:77:30 | .birth_day | semmle.label | .birth_day |
|
||||||
| testURL.swift:15:22:15:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
| testSend.swift:78:27:78:30 | .CarePlanID | semmle.label | .CarePlanID |
|
||||||
| testURL.swift:15:55:15:55 | account_no | semmle.label | account_no |
|
| testSend.swift:79:27:79:30 | .BankCardNo | semmle.label | .BankCardNo |
|
||||||
| testURL.swift:16:22:16:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
| testSend.swift:80:27:80:30 | .MyCreditRating | semmle.label | .MyCreditRating |
|
||||||
| testURL.swift:16:55:16:55 | credit_card_no | semmle.label | credit_card_no |
|
| testURL.swift:17:22:17:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
| testURL.swift:20:22:20:22 | passwd | semmle.label | passwd |
|
| testURL.swift:17:54:17:54 | passwd | semmle.label | passwd |
|
||||||
|
| testURL.swift:19:22:19:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:19:55:19:55 | account_no | semmle.label | account_no |
|
||||||
|
| testURL.swift:20:22:20:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:20:55:20:55 | credit_card_no | semmle.label | credit_card_no |
|
||||||
|
| testURL.swift:24:22:24:22 | passwd | semmle.label | passwd |
|
||||||
|
| testURL.swift:28:22:28:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:28:55:28:55 | e_mail | semmle.label | e_mail |
|
||||||
|
| testURL.swift:30:22:30:57 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:30:57:30:57 | a_homeaddr_z | semmle.label | a_homeaddr_z |
|
||||||
|
| testURL.swift:32:22:32:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||||
|
| testURL.swift:32:55:32:55 | resident_ID | semmle.label | resident_ID |
|
||||||
subpaths
|
subpaths
|
||||||
| testSend.swift:54:17:54:17 | password | testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data | testSend.swift:54:13:54:25 | call to pad(_:) |
|
| testSend.swift:60:17:60:17 | password | testSend.swift:41:10:41:18 | data | testSend.swift:41:45:41:45 | data | testSend.swift:60:13:60:25 | call to pad(_:) |
|
||||||
#select
|
#select
|
||||||
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | testAlamofire.swift:150:45:150:45 | password | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:150:45:150:45 | password | password |
|
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | testAlamofire.swift:150:45:150:45 | password | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:150:45:150:45 | password | password |
|
||||||
| testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | testAlamofire.swift:152:51:152:51 | password | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:152:51:152:51 | password | password |
|
| testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | testAlamofire.swift:152:51:152:51 | password | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:152:51:152:51 | password | password |
|
||||||
| testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | testAlamofire.swift:154:38:154:38 | email | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:154:38:154:38 | email | email |
|
| testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | testAlamofire.swift:154:38:154:38 | email | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:154:38:154:38 | email | email |
|
||||||
| testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | This operation transmits 'passwordPlain', which may contain unencrypted sensitive data from $@. | testSend.swift:29:19:29:19 | passwordPlain | passwordPlain |
|
| testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | This operation transmits 'passwordPlain', which may contain unencrypted sensitive data from $@. | testSend.swift:29:19:29:19 | passwordPlain | passwordPlain |
|
||||||
| testSend.swift:37:19:37:19 | data2 | testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:37:19:37:19 | data2 | This operation transmits 'data2', which may contain unencrypted sensitive data from $@. | testSend.swift:33:19:33:19 | passwordPlain | passwordPlain |
|
| testSend.swift:37:19:37:19 | data2 | testSend.swift:33:19:33:19 | passwordPlain | testSend.swift:37:19:37:19 | data2 | This operation transmits 'data2', which may contain unencrypted sensitive data from $@. | testSend.swift:33:19:33:19 | passwordPlain | passwordPlain |
|
||||||
| testSend.swift:59:27:59:27 | str1 | testSend.swift:52:13:52:13 | password | testSend.swift:59:27:59:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:52:13:52:13 | password | password |
|
| testSend.swift:65:27:65:27 | str1 | testSend.swift:58:13:58:13 | password | testSend.swift:65:27:65:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:58:13:58:13 | password | password |
|
||||||
| testSend.swift:60:27:60:27 | str2 | testSend.swift:53:13:53:13 | password | testSend.swift:60:27:60:27 | str2 | This operation transmits 'str2', which may contain unencrypted sensitive data from $@. | testSend.swift:53:13:53:13 | password | password |
|
| testSend.swift:66:27:66:27 | str2 | testSend.swift:59:13:59:13 | password | testSend.swift:66:27:66:27 | str2 | This operation transmits 'str2', which may contain unencrypted sensitive data from $@. | testSend.swift:59:13:59:13 | password | password |
|
||||||
| testSend.swift:61:27:61:27 | str3 | testSend.swift:54:17:54:17 | password | testSend.swift:61:27:61:27 | str3 | This operation transmits 'str3', which may contain unencrypted sensitive data from $@. | testSend.swift:54:17:54:17 | password | password |
|
| testSend.swift:67:27:67:27 | str3 | testSend.swift:60:17:60:17 | password | testSend.swift:67:27:67:27 | str3 | This operation transmits 'str3', which may contain unencrypted sensitive data from $@. | testSend.swift:60:17:60:17 | password | password |
|
||||||
| testSend.swift:65:27:65:27 | license_key | testSend.swift:65:27:65:27 | license_key | testSend.swift:65:27:65:27 | license_key | This operation transmits 'license_key', which may contain unencrypted sensitive data from $@. | testSend.swift:65:27:65:27 | license_key | license_key |
|
| testSend.swift:71:27:71:27 | license_key | testSend.swift:71:27:71:27 | license_key | testSend.swift:71:27:71:27 | license_key | This operation transmits 'license_key', which may contain unencrypted sensitive data from $@. | testSend.swift:71:27:71:27 | license_key | license_key |
|
||||||
| testSend.swift:66:27:66:30 | .mobileNumber | testSend.swift:66:27:66:30 | .mobileNumber | testSend.swift:66:27:66:30 | .mobileNumber | This operation transmits '.mobileNumber', which may contain unencrypted sensitive data from $@. | testSend.swift:66:27:66:30 | .mobileNumber | .mobileNumber |
|
| testSend.swift:72:27:72:30 | .mobileNumber | testSend.swift:72:27:72:30 | .mobileNumber | testSend.swift:72:27:72:30 | .mobileNumber | This operation transmits '.mobileNumber', which may contain unencrypted sensitive data from $@. | testSend.swift:72:27:72:30 | .mobileNumber | .mobileNumber |
|
||||||
| testURL.swift:13:22:13:54 | ... .+(_:_:) ... | testURL.swift:13:54:13:54 | passwd | testURL.swift:13:22:13:54 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:13:54:13:54 | passwd | passwd |
|
| testSend.swift:76:27:76:30 | .Telephone | testSend.swift:76:27:76:30 | .Telephone | testSend.swift:76:27:76:30 | .Telephone | This operation transmits '.Telephone', which may contain unencrypted sensitive data from $@. | testSend.swift:76:27:76:30 | .Telephone | .Telephone |
|
||||||
| testURL.swift:15:22:15:55 | ... .+(_:_:) ... | testURL.swift:15:55:15:55 | account_no | testURL.swift:15:22:15:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:15:55:15:55 | account_no | account_no |
|
| testSend.swift:77:27:77:30 | .birth_day | testSend.swift:77:27:77:30 | .birth_day | testSend.swift:77:27:77:30 | .birth_day | This operation transmits '.birth_day', which may contain unencrypted sensitive data from $@. | testSend.swift:77:27:77:30 | .birth_day | .birth_day |
|
||||||
| testURL.swift:16:22:16:55 | ... .+(_:_:) ... | testURL.swift:16:55:16:55 | credit_card_no | testURL.swift:16:22:16:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:16:55:16:55 | credit_card_no | credit_card_no |
|
| testSend.swift:78:27:78:30 | .CarePlanID | testSend.swift:78:27:78:30 | .CarePlanID | testSend.swift:78:27:78:30 | .CarePlanID | This operation transmits '.CarePlanID', which may contain unencrypted sensitive data from $@. | testSend.swift:78:27:78:30 | .CarePlanID | .CarePlanID |
|
||||||
| testURL.swift:20:22:20:22 | passwd | testURL.swift:20:22:20:22 | passwd | testURL.swift:20:22:20:22 | passwd | This operation transmits 'passwd', which may contain unencrypted sensitive data from $@. | testURL.swift:20:22:20:22 | passwd | passwd |
|
| testSend.swift:79:27:79:30 | .BankCardNo | testSend.swift:79:27:79:30 | .BankCardNo | testSend.swift:79:27:79:30 | .BankCardNo | This operation transmits '.BankCardNo', which may contain unencrypted sensitive data from $@. | testSend.swift:79:27:79:30 | .BankCardNo | .BankCardNo |
|
||||||
|
| testSend.swift:80:27:80:30 | .MyCreditRating | testSend.swift:80:27:80:30 | .MyCreditRating | testSend.swift:80:27:80:30 | .MyCreditRating | This operation transmits '.MyCreditRating', which may contain unencrypted sensitive data from $@. | testSend.swift:80:27:80:30 | .MyCreditRating | .MyCreditRating |
|
||||||
|
| testURL.swift:17:22:17:54 | ... .+(_:_:) ... | testURL.swift:17:54:17:54 | passwd | testURL.swift:17:22:17:54 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:17:54:17:54 | passwd | passwd |
|
||||||
|
| testURL.swift:19:22:19:55 | ... .+(_:_:) ... | testURL.swift:19:55:19:55 | account_no | testURL.swift:19:22:19:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:19:55:19:55 | account_no | account_no |
|
||||||
|
| testURL.swift:20:22:20:55 | ... .+(_:_:) ... | testURL.swift:20:55:20:55 | credit_card_no | testURL.swift:20:22:20:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:20:55:20:55 | credit_card_no | credit_card_no |
|
||||||
|
| testURL.swift:24:22:24:22 | passwd | testURL.swift:24:22:24:22 | passwd | testURL.swift:24:22:24:22 | passwd | This operation transmits 'passwd', which may contain unencrypted sensitive data from $@. | testURL.swift:24:22:24:22 | passwd | passwd |
|
||||||
|
| testURL.swift:28:22:28:55 | ... .+(_:_:) ... | testURL.swift:28:55:28:55 | e_mail | testURL.swift:28:22:28:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:28:55:28:55 | e_mail | e_mail |
|
||||||
|
| testURL.swift:30:22:30:57 | ... .+(_:_:) ... | testURL.swift:30:57:30:57 | a_homeaddr_z | testURL.swift:30:22:30:57 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:30:57:30:57 | a_homeaddr_z | a_homeaddr_z |
|
||||||
|
| testURL.swift:32:22:32:55 | ... .+(_:_:) ... | testURL.swift:32:55:32:55 | resident_ID | testURL.swift:32:22:32:55 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testURL.swift:32:55:32:55 | resident_ID | resident_ID |
|
||||||
|
|||||||
@@ -119,16 +119,24 @@
|
|||||||
| testRealm.swift:73:15:73:15 | myPassword | label:myPassword, type:credential |
|
| testRealm.swift:73:15:73:15 | myPassword | label:myPassword, type:credential |
|
||||||
| testSend.swift:29:19:29:19 | passwordPlain | label:passwordPlain, type:credential |
|
| testSend.swift:29:19:29:19 | passwordPlain | label:passwordPlain, type:credential |
|
||||||
| testSend.swift:33:19:33:19 | passwordPlain | label:passwordPlain, type:credential |
|
| testSend.swift:33:19:33:19 | passwordPlain | label:passwordPlain, type:credential |
|
||||||
| testSend.swift:52:13:52:13 | password | label:password, type:credential |
|
| testSend.swift:58:13:58:13 | password | label:password, type:credential |
|
||||||
| testSend.swift:53:13:53:13 | password | label:password, type:credential |
|
| testSend.swift:59:13:59:13 | password | label:password, type:credential |
|
||||||
| testSend.swift:54:17:54:17 | password | label:password, type:credential |
|
| testSend.swift:60:17:60:17 | password | label:password, type:credential |
|
||||||
| testSend.swift:55:23:55:23 | password | label:password, type:credential |
|
| testSend.swift:61:23:61:23 | password | label:password, type:credential |
|
||||||
| testSend.swift:56:27:56:27 | password | label:password, type:credential |
|
| testSend.swift:62:27:62:27 | password | label:password, type:credential |
|
||||||
| testSend.swift:57:27:57:27 | password | label:password, type:credential |
|
| testSend.swift:63:27:63:27 | password | label:password, type:credential |
|
||||||
| testSend.swift:65:27:65:27 | license_key | label:license_key, type:credential |
|
| testSend.swift:71:27:71:27 | license_key | label:license_key, type:credential |
|
||||||
| testSend.swift:66:27:66:30 | .mobileNumber | label:mobileNumber, type:private information |
|
| testSend.swift:72:27:72:30 | .mobileNumber | label:mobileNumber, type:private information |
|
||||||
| testSend.swift:69:27:69:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential |
|
| testSend.swift:75:27:75:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential |
|
||||||
| testURL.swift:13:54:13:54 | passwd | label:passwd, type:credential |
|
| testSend.swift:76:27:76:30 | .Telephone | label:Telephone, type:private information |
|
||||||
| testURL.swift:15:55:15:55 | account_no | label:account_no, type:private information |
|
| testSend.swift:77:27:77:30 | .birth_day | label:birth_day, type:private information |
|
||||||
| testURL.swift:16:55:16:55 | credit_card_no | label:credit_card_no, type:private information |
|
| testSend.swift:78:27:78:30 | .CarePlanID | label:CarePlanID, type:private information |
|
||||||
| testURL.swift:20:22:20:22 | passwd | label:passwd, type:credential |
|
| testSend.swift:79:27:79:30 | .BankCardNo | label:BankCardNo, type:private information |
|
||||||
|
| testSend.swift:80:27:80:30 | .MyCreditRating | label:MyCreditRating, type:private information |
|
||||||
|
| testURL.swift:17:54:17:54 | passwd | label:passwd, type:credential |
|
||||||
|
| testURL.swift:19:55:19:55 | account_no | label:account_no, type:private information |
|
||||||
|
| testURL.swift:20:55:20:55 | credit_card_no | label:credit_card_no, type:private information |
|
||||||
|
| testURL.swift:24:22:24:22 | passwd | label:passwd, type:credential |
|
||||||
|
| testURL.swift:28:55:28:55 | e_mail | label:e_mail, type:private information |
|
||||||
|
| testURL.swift:30:57:30:57 | a_homeaddr_z | label:a_homeaddr_z, type:private information |
|
||||||
|
| testURL.swift:32:55:32:55 | resident_ID | label:resident_ID, type:private information |
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ struct MyStruct {
|
|||||||
var mobileUrl: String
|
var mobileUrl: String
|
||||||
var mobilePlayer: String
|
var mobilePlayer: String
|
||||||
var passwordFeatureEnabled: Bool
|
var passwordFeatureEnabled: Bool
|
||||||
|
var Telephone: String
|
||||||
|
var birth_day: String
|
||||||
|
var CarePlanID: String
|
||||||
|
var BankCardNo: String
|
||||||
|
var MyCreditRating: String
|
||||||
|
var OneTimeCode: String
|
||||||
}
|
}
|
||||||
|
|
||||||
func test2(password : String, license_key: String, ms: MyStruct, connection : NWConnection) {
|
func test2(password : String, license_key: String, ms: MyStruct, connection : NWConnection) {
|
||||||
@@ -67,4 +73,10 @@ func test2(password : String, license_key: String, ms: MyStruct, connection : NW
|
|||||||
connection.send(content: ms.mobileUrl, completion: .idempotent) // GOOD (not sensitive)
|
connection.send(content: ms.mobileUrl, completion: .idempotent) // GOOD (not sensitive)
|
||||||
connection.send(content: ms.mobilePlayer, completion: .idempotent) // GOOD (not sensitive)
|
connection.send(content: ms.mobilePlayer, completion: .idempotent) // GOOD (not sensitive)
|
||||||
connection.send(content: ms.passwordFeatureEnabled, completion: .idempotent) // GOOD (not sensitive)
|
connection.send(content: ms.passwordFeatureEnabled, completion: .idempotent) // GOOD (not sensitive)
|
||||||
|
connection.send(content: ms.Telephone, completion: .idempotent) // BAD
|
||||||
|
connection.send(content: ms.birth_day, completion: .idempotent) // BAD
|
||||||
|
connection.send(content: ms.CarePlanID, completion: .idempotent) // BAD
|
||||||
|
connection.send(content: ms.BankCardNo, completion: .idempotent) // BAD
|
||||||
|
connection.send(content: ms.MyCreditRating, completion: .idempotent) // BAD
|
||||||
|
connection.send(content: ms.OneTimeCode, completion: .idempotent) // BAD [NOT DETECTED]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ struct URL
|
|||||||
|
|
||||||
// --- tests ---
|
// --- tests ---
|
||||||
|
|
||||||
|
var myString = ""
|
||||||
|
func setMyString(str: String) { myString = str }
|
||||||
|
func getMyString() -> String { return myString }
|
||||||
|
|
||||||
func test1(passwd : String, encrypted_passwd : String, account_no : String, credit_card_no : String) {
|
func test1(passwd : String, encrypted_passwd : String, account_no : String, credit_card_no : String) {
|
||||||
let a = URL(string: "http://example.com/login?p=" + passwd); // BAD
|
let a = URL(string: "http://example.com/login?p=" + passwd); // BAD
|
||||||
let b = URL(string: "http://example.com/login?p=" + encrypted_passwd); // GOOD (not sensitive)
|
let b = URL(string: "http://example.com/login?p=" + encrypted_passwd); // GOOD (not sensitive)
|
||||||
@@ -19,4 +23,11 @@ func test1(passwd : String, encrypted_passwd : String, account_no : String, cred
|
|||||||
let e = URL(string: "abc", relativeTo: base); // GOOD (not sensitive)
|
let e = URL(string: "abc", relativeTo: base); // GOOD (not sensitive)
|
||||||
let f = URL(string: passwd, relativeTo: base); // BAD
|
let f = URL(string: passwd, relativeTo: base); // BAD
|
||||||
let g = URL(string: "abc", relativeTo: f); // BAD (reported on line above)
|
let g = URL(string: "abc", relativeTo: f); // BAD (reported on line above)
|
||||||
|
|
||||||
|
let e_mail = myString
|
||||||
|
let h = URL(string: "http://example.com/login?em=" + e_mail); // BAD
|
||||||
|
var a_homeaddr_z = getMyString()
|
||||||
|
let i = URL(string: "http://example.com/login?home=" + a_homeaddr_z); // BAD
|
||||||
|
var resident_ID = getMyString()
|
||||||
|
let j = URL(string: "http://example.com/login?id=" + resident_ID); // BAD
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ struct Logger {
|
|||||||
|
|
||||||
// --- tests ---
|
// --- tests ---
|
||||||
|
|
||||||
func test1(password: String, passwordHash : String) {
|
func test1(password: String, passwordHash : String, passphrase: String, pass_phrase: String) {
|
||||||
print(password) // $ MISSING: hasCleartextLogging=87
|
print(password) // $ MISSING: hasCleartextLogging=87
|
||||||
print(password, separator: "") // $ MISSING: $ hasCleartextLogging=88
|
print(password, separator: "") // $ MISSING: $ hasCleartextLogging=88
|
||||||
print("", separator: password) // $ hasCleartextLogging=89
|
print("", separator: password) // $ hasCleartextLogging=89
|
||||||
@@ -132,6 +132,9 @@ func test1(password: String, passwordHash : String) {
|
|||||||
log.critical("\(passwordHash, privacy: .public)") // Safe
|
log.critical("\(passwordHash, privacy: .public)") // Safe
|
||||||
log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=133
|
log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=133
|
||||||
log.fault("\(passwordHash, privacy: .public)") // Safe
|
log.fault("\(passwordHash, privacy: .public)") // Safe
|
||||||
|
|
||||||
|
NSLog(passphrase) // $ hasCleartextLogging=136
|
||||||
|
NSLog(pass_phrase) // $ hasCleartextLogging=137
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyClass {
|
class MyClass {
|
||||||
@@ -145,14 +148,14 @@ func doSomething(password: String) { }
|
|||||||
func test3(x: String) {
|
func test3(x: String) {
|
||||||
// alternative evidence of sensitivity...
|
// alternative evidence of sensitivity...
|
||||||
|
|
||||||
NSLog(x) // $ MISSING: hasCleartextLogging=148
|
NSLog(x) // $ MISSING: hasCleartextLogging=152
|
||||||
doSomething(password: x);
|
doSomething(password: x);
|
||||||
NSLog(x) // $ hasCleartextLogging=149
|
NSLog(x) // $ hasCleartextLogging=152
|
||||||
|
|
||||||
let y = getPassword();
|
let y = getPassword();
|
||||||
NSLog(y) // $ hasCleartextLogging=152
|
NSLog(y) // $ hasCleartextLogging=155
|
||||||
|
|
||||||
let z = MyClass()
|
let z = MyClass()
|
||||||
NSLog(z.harmless) // Safe
|
NSLog(z.harmless) // Safe
|
||||||
NSLog(z.password) // $ hasCleartextLogging=157
|
NSLog(z.password) // $ hasCleartextLogging=160
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user