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 }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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 }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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 }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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.GoMicro
|
||||
import semmle.go.frameworks.GoRestfulHttp
|
||||
import semmle.go.frameworks.Gqlgen
|
||||
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
|
||||
import semmle.go.frameworks.K8sIoApiCoreV1
|
||||
import semmle.go.frameworks.K8sIoClientGo
|
||||
|
||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
|
||||
ParamNodeEx getParamNode() { result = p }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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
|
||||
java.awt,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3
|
||||
java.beans,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,
|
||||
java.io,49,,45,,,22,,,,,,,,,,,,,,27,,,,,,,,,,,,,,,,,,,43,2
|
||||
java.lang,31,,92,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,56,36
|
||||
java.net,13,3,21,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,21,
|
||||
java.nio,47,,35,,,3,,,,,,,,,,,,,,44,,,,,,,,,,,,,,,,,,,35,
|
||||
java.io,50,,45,,,22,,,,,,,,,,,,,,28,,,,,,,,,,,,,,,,,,,43,2
|
||||
java.lang,31,,93,,13,,,,,,,,,,,,8,,,5,,,4,,,1,,,,,,,,,,,,,57,36
|
||||
java.net,13,3,23,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,3,23,
|
||||
java.nio,53,,36,,,5,,,,,,,,,,,,,,47,,,,,,,,,1,,,,,,,,,,36,
|
||||
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,,,,,,,,,,,
|
||||
javax.faces.context,2,7,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,
|
||||
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.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,
|
||||
ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
||||
okhttp3,4,,48,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,25
|
||||
okhttp3,4,,50,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,23,27
|
||||
org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49,
|
||||
org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
||||
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.jexl3,15,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
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.net,9,12,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,12,,
|
||||
org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,
|
||||
@@ -131,6 +131,7 @@ org.dom4j,20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,20,,,,,,
|
||||
org.eclipse.jetty.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
||||
org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,
|
||||
org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,
|
||||
org.gradle.api.file,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,
|
||||
org.hibernate,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,,
|
||||
org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,
|
||||
org.jboss.logging,324,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,,
|
||||
@@ -180,4 +181,4 @@ ratpack.func,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35
|
||||
ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4,
|
||||
ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10,
|
||||
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,,,,,,
|
||||
`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 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 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,,,,,,
|
||||
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,730,41,7,,,,,
|
||||
JBoss Logging,``org.jboss.logging``,,,324,,,,,,
|
||||
`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
|
||||
Kotlin Standard Library,``kotlin*``,,1849,16,14,,,,,2
|
||||
`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
|
||||
Totals,,283,13595,2059,286,16,122,33,1,390
|
||||
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,13606,2067,290,16,122,33,1,391
|
||||
|
||||
|
||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
|
||||
ParamNodeEx getParamNode() { result = p }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
import java
|
||||
private import AutomodelApplicationModeCharacteristics
|
||||
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
|
||||
Endpoint endpoint, string message, ApplicationModeMetadataExtractor meta, DollarAtString package,
|
||||
DollarAtString type, DollarAtString subtypes, DollarAtString name, DollarAtString signature,
|
||||
@@ -23,6 +58,7 @@ where
|
||||
not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u |
|
||||
u.appliesToEndpoint(endpoint)
|
||||
) 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
|
||||
// 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
|
||||
|
||||
@@ -23,7 +23,8 @@ class DomBasedXssAtmConfig extends AtmConfig {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof DomBasedXss::Sanitizer
|
||||
node instanceof DomBasedXss::Sanitizer or
|
||||
DomBasedXss::isOptionallySanitizedNode(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
@@ -31,10 +32,6 @@ class DomBasedXssAtmConfig extends AtmConfig {
|
||||
guard instanceof QuoteGuard or
|
||||
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
|
||||
|
||||
@@ -23,7 +23,8 @@ class XssThroughDomAtmConfig extends AtmConfig {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof DomBasedXss::Sanitizer
|
||||
node instanceof DomBasedXss::Sanitizer or
|
||||
DomBasedXss::isOptionallySanitizedNode(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
@@ -34,10 +35,6 @@ class XssThroughDomAtmConfig extends AtmConfig {
|
||||
guard instanceof QuoteGuard or
|
||||
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.
|
||||
*/
|
||||
@@ -494,7 +514,7 @@ private BasicBlock getADominatedBasicBlock(BarrierGuardNode guard, ConditionGuar
|
||||
*
|
||||
* 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)
|
||||
or
|
||||
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
|
||||
* or one implied by a barrier guard.
|
||||
*/
|
||||
private predicate isLabeledBarrierEdge(
|
||||
private predicate isLabeledBarrierEdgeRaw(
|
||||
Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel 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.
|
||||
*/
|
||||
|
||||
@@ -62,6 +62,26 @@ module TaintTracking {
|
||||
*/
|
||||
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. */
|
||||
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
@@ -108,6 +128,22 @@ module TaintTracking {
|
||||
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) {
|
||||
super.isBarrierGuard(guard) 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 isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
CleartextLogging::isSanitizerEdge(pred, succ)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node 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.
|
||||
*/
|
||||
predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
deprecated predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -33,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
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) {
|
||||
CleartextLogging::isAdditionalTaintStep(src, trg)
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
sanitizingPrefixEdge(source, sink)
|
||||
}
|
||||
override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isAdditionalRequestForgeryStep(pred, succ)
|
||||
|
||||
@@ -33,9 +33,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
hostnameSanitizingPrefixEdge(source, sink)
|
||||
}
|
||||
override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
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 { }
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
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 |
|
||||
// sanitized = sanitize ? sanitizer(source) : source;
|
||||
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. */
|
||||
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.
|
||||
node = any(DataFlow::MethodCallNode call | call.getMethodName() = "join") and
|
||||
lbl = prefixLabel()
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label
|
||||
) {
|
||||
isOptionallySanitizedEdge(pred, succ) and
|
||||
label = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()]
|
||||
or
|
||||
isOptionallySanitizedNode(node) and
|
||||
lbl = [DataFlow::FlowLabel::taint(), prefixLabel(), TaintedUrlSuffix::label()]
|
||||
}
|
||||
|
||||
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.
|
||||
// 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.
|
||||
exists(DataFlow::PropRead read |
|
||||
read = DOM::locationRef().getAPropertyRead() and
|
||||
pred = read.getBase() and
|
||||
succ = read
|
||||
)
|
||||
node = DOM::locationRef().getAPropertyRead()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,9 @@ class Configuration extends TaintTracking::Configuration {
|
||||
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
|
||||
pred instanceof Sink and
|
||||
// constrain succ
|
||||
pred = succ.getAPredecessor()
|
||||
this.isSink(node)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
|
||||
@@ -55,20 +55,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl
|
||||
) {
|
||||
override predicate isSanitizerOut(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
// 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.
|
||||
exists(ExtendCall call |
|
||||
pred = call.getASourceOperand() and
|
||||
(
|
||||
succ = call.getDestinationOperand().getALocalSource()
|
||||
or
|
||||
succ = call
|
||||
) and
|
||||
lbl instanceof ObjectPrototype
|
||||
)
|
||||
node = any(ExtendCall call).getASourceOperand() and
|
||||
lbl instanceof ObjectPrototype
|
||||
}
|
||||
|
||||
override predicate isAdditionalFlowStep(
|
||||
|
||||
@@ -26,9 +26,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
sanitizingPrefixEdge(source, sink)
|
||||
}
|
||||
override predicate isSanitizerOut(DataFlow::Node node) { sanitizingPrefixEdge(node, _) }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isAdditionalRequestForgeryStep(pred, succ)
|
||||
|
||||
@@ -22,7 +22,8 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
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) {
|
||||
@@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
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`. */
|
||||
|
||||
@@ -27,9 +27,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
hostnameSanitizingPrefixEdge(source, sink)
|
||||
}
|
||||
override predicate isSanitizerOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
guard instanceof LocalUrlSanitizingGuard or
|
||||
|
||||
@@ -31,10 +31,8 @@ class Configration extends TaintTracking::Configuration {
|
||||
node instanceof DomBasedXss::Sanitizer
|
||||
or
|
||||
node instanceof UnsafeJQueryPlugin::Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
|
||||
or
|
||||
DomBasedXss::isOptionallySanitizedNode(node)
|
||||
}
|
||||
|
||||
// override to require that there is a path without unmatched return steps
|
||||
|
||||
@@ -31,16 +31,13 @@ class Configuration extends TaintTracking::Configuration {
|
||||
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 through concatenation:
|
||||
StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1))
|
||||
StringConcatenation::taintStep(node, _, _, any(int i | i >= 1))
|
||||
or
|
||||
// prefixing through a poor-mans templating system:
|
||||
exists(StringReplaceCall replace |
|
||||
replace = succ and
|
||||
pred = replace.getRawReplacement()
|
||||
)
|
||||
node = any(StringReplaceCall call).getRawReplacement()
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
|
||||
|
||||
@@ -34,11 +34,24 @@ module UnvalidatedDynamicMethodCall {
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel lbl
|
||||
) {
|
||||
any(Sanitizer s).sanitizes(pred, succ, lbl)
|
||||
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel label) {
|
||||
super.isLabeledBarrier(node, label)
|
||||
or
|
||||
node.(Sanitizer).getFlowLabel() = label
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
|
||||
@@ -20,7 +20,8 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof DomBasedXss::Sanitizer
|
||||
node instanceof DomBasedXss::Sanitizer or
|
||||
DomBasedXss::isOptionallySanitizedNode(node)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
|
||||
@@ -32,10 +33,6 @@ class Configuration extends TaintTracking::Configuration {
|
||||
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) {
|
||||
succ = DataFlow::globalVarRef("URL").getAMemberCall("createObjectURL") and
|
||||
pred = succ.(DataFlow::InvokeNode).getArgument(0)
|
||||
|
||||
@@ -29,8 +29,8 @@ class Configuration extends TaintTracking::Configuration {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node source, DataFlow::Node sink) {
|
||||
this.strictSanitizingPrefixEdge(source, sink)
|
||||
override predicate isSanitizerOut(DataFlow::Node node) {
|
||||
this.strictSanitizingPrefixEdge(node, _)
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
||||
|
||||
@@ -22,10 +22,7 @@ class TestDataFlowConfiguration extends DataFlow::Configuration {
|
||||
f.getName().matches("%noReturnTracking%") and
|
||||
node = f.getAReturnedExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) {
|
||||
src = src and
|
||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
or
|
||||
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,8 @@ class TestTaintTrackingConfiguration extends TaintTracking::Configuration {
|
||||
f.getName().matches("%noReturnTracking%") and
|
||||
node = f.getAReturnedExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSanitizerEdge(DataFlow::Node src, DataFlow::Node snk) {
|
||||
src = src and
|
||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
or
|
||||
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,11 +96,8 @@ class GermanFlowConfig extends DataFlow::Configuration {
|
||||
f.getName().matches("%noReturnTracking%") and
|
||||
node = f.getAReturnedExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node snk) {
|
||||
src = src and
|
||||
snk.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
or
|
||||
node.asExpr().(PropAccess).getPropertyName() = "notTracked"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import javascript
|
||||
|
||||
DataFlow::Node sourceVariable() { result.asExpr().(VarRef).getName() = "sourceVariable" }
|
||||
|
||||
StringOps::ConcatenationRoot sinkConcatenation() {
|
||||
result.getConstantStringParts().matches("<sink>%</sink>")
|
||||
}
|
||||
|
||||
class ExampleConfiguration extends TaintTracking::Configuration {
|
||||
ExampleConfiguration() { this = "ExampleConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source.asExpr().(CallExpr).getCalleeName() = "SOURCE"
|
||||
or
|
||||
source = sourceVariable()
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
@@ -12,8 +20,14 @@ class ExampleConfiguration extends TaintTracking::Configuration {
|
||||
callExpr.getCalleeName() = "SINK" and
|
||||
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) {
|
||||
exists(CallExpr callExpr |
|
||||
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:401:16:401:34 | Object.hasOwn(o, v) | tst.js:401:33:401:33 | v | true |
|
||||
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:8:14:8: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: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:21:11:26:5 | stringifed |
|
||||
| build-leaks.js:21:24:26:5 | {\\n ... )\\n } |
|
||||
| build-leaks.js:22:24:25:14 | Object. ... }, {}) |
|
||||
| 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:46 | raw[key] |
|
||||
| build-leaks.js:24:20:24:22 | env |
|
||||
| build-leaks.js:30:22:30:31 | stringifed |
|
||||
| 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: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: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: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: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: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:49:22:51 | env |
|
||||
| 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 }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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
|
||||
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]
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -2706,7 +2706,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
|
||||
ParamNodeEx getParamNode() { result = p }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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() =
|
||||
any(CfgNodes::ExprNodes::MethodCallCfgNode mc |
|
||||
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`
|
||||
* 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
|
||||
* libxml-ruby.
|
||||
@@ -123,6 +134,40 @@ private predicate xmlMiniEntitySubstitutionEnabled() {
|
||||
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`.
|
||||
*/
|
||||
|
||||
@@ -18,16 +18,7 @@ private class PotentialRequestHandler extends DataFlow::CallableNode {
|
||||
(
|
||||
this.(DataFlow::MethodNode).getMethodName() = "call"
|
||||
or
|
||||
not this instanceof DataFlow::MethodNode and
|
||||
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))
|
||||
)
|
||||
this = API::getTopLevelCall("run").getArgument(0).asCallable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
override string toString() { result = p + ": " + ap }
|
||||
override string toString() { result = p + concat(" : " + ppReprType(t)) + " " + ap }
|
||||
|
||||
predicate hasLocationInfo(
|
||||
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
|
||||
* expected to be expensive. Holds with `unfold = true` otherwise.
|
||||
*/
|
||||
private predicate evalUnfold(AccessPathApprox apa, boolean unfold) {
|
||||
if forceHighPrecision(apa.getHead())
|
||||
if forceUnfold(apa)
|
||||
then unfold = true
|
||||
else
|
||||
exists(int aps, int nodes, int apLimit, int tupleLimit |
|
||||
@@ -3092,6 +3101,12 @@ module Impl<FullStateConfigSig Config> {
|
||||
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. */
|
||||
string toString() { result = this.getNodeEx().toString() + this.ppType() + this.ppAp() }
|
||||
|
||||
@@ -3100,7 +3115,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
* representation of the call context.
|
||||
*/
|
||||
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)
|
||||
)
|
||||
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).*(" +
|
||||
// Inspired by the list on https://cwe.mitre.org/data/definitions/359.html
|
||||
// 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
|
||||
"post.?code|zip.?code|home.?address|" +
|
||||
"post.?code|zip.?code|home.?addr|" +
|
||||
// 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)
|
||||
"latitude|longitude|" +
|
||||
"l(atitude|ongitude)|nationality|" +
|
||||
// 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.
|
||||
"email|" +
|
||||
"e(mail|_mail)|" +
|
||||
// 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
|
||||
"employer|spouse" +
|
||||
"employ(er|ee)|spouse|maiden.?name" +
|
||||
// ---
|
||||
").*"
|
||||
}
|
||||
@@ -80,13 +85,32 @@ private string regexpProbablySafe() {
|
||||
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.
|
||||
*/
|
||||
private class SensitiveVarDecl extends VarDecl {
|
||||
SensitiveDataType sensitiveType;
|
||||
|
||||
SensitiveVarDecl() { this.getName().regexpMatch(sensitiveType.getRegexp()) }
|
||||
SensitiveVarDecl() { this.getName() = sensitiveStrings(sensitiveType) }
|
||||
|
||||
predicate hasInfo(string label, SensitiveDataType type) {
|
||||
label = this.getName() and
|
||||
@@ -99,15 +123,11 @@ private class SensitiveVarDecl extends VarDecl {
|
||||
*/
|
||||
private class SensitiveFunction extends Function {
|
||||
SensitiveDataType sensitiveType;
|
||||
string name; // name of the function, not including the argument list.
|
||||
|
||||
SensitiveFunction() {
|
||||
name = this.getShortName() and
|
||||
name.regexpMatch(sensitiveType.getRegexp())
|
||||
}
|
||||
SensitiveFunction() { this.getShortName() = sensitiveStrings(sensitiveType) }
|
||||
|
||||
predicate hasInfo(string label, SensitiveDataType type) {
|
||||
label = name and
|
||||
label = this.getShortName() and
|
||||
sensitiveType = type
|
||||
}
|
||||
}
|
||||
@@ -118,7 +138,7 @@ private class SensitiveFunction extends Function {
|
||||
private class SensitiveArgument extends Argument {
|
||||
SensitiveDataType sensitiveType;
|
||||
|
||||
SensitiveArgument() { this.getLabel().regexpMatch(sensitiveType.getRegexp()) }
|
||||
SensitiveArgument() { this.getLabel() = sensitiveStrings(sensitiveType) }
|
||||
|
||||
predicate hasInfo(string label, SensitiveDataType type) {
|
||||
label = this.getLabel() and
|
||||
@@ -169,6 +189,7 @@ class SensitiveExpr extends Expr {
|
||||
* A function that is likely used to encrypt or hash data.
|
||||
*/
|
||||
private class EncryptionFunction extends Function {
|
||||
cached
|
||||
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: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:52:13:52:13 | password | testSend.swift:59:27:59:27 | str1 |
|
||||
| testSend.swift:53:13:53:13 | password | testSend.swift:60:27:60:27 | str2 |
|
||||
| testSend.swift:54:13:54:25 | call to pad(_:) | testSend.swift:61:27:61:27 | str3 |
|
||||
| testSend.swift:54:17:54: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(_:) |
|
||||
| testURL.swift:13:54:13:54 | passwd | testURL.swift:13:22:13:54 | ... .+(_:_:) ... |
|
||||
| testURL.swift:15:55:15:55 | account_no | testURL.swift:15:22:15:55 | ... .+(_:_:) ... |
|
||||
| testURL.swift:16:55:16:55 | credit_card_no | testURL.swift:16:22:16:55 | ... .+(_:_:) ... |
|
||||
| testSend.swift:58:13:58:13 | password | testSend.swift:65:27:65:27 | str1 |
|
||||
| testSend.swift:59:13:59:13 | password | testSend.swift:66:27:66:27 | str2 |
|
||||
| testSend.swift:60:13:60:25 | call to pad(_:) | testSend.swift:67:27:67:27 | str3 |
|
||||
| testSend.swift:60:17:60:17 | password | testSend.swift:41:10:41:18 | data |
|
||||
| testSend.swift:60:17:60:17 | password | testSend.swift:60:13:60:25 | call to pad(_:) |
|
||||
| testURL.swift:17:54:17:54 | passwd | testURL.swift:17:22:17:54 | ... .+(_:_:) ... |
|
||||
| testURL.swift:19:55:19:55 | account_no | testURL.swift:19:22:19: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
|
||||
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| 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:41:10:41:18 | 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:53:13:53:13 | password | semmle.label | password |
|
||||
| testSend.swift:54:13:54:25 | call to pad(_:) | semmle.label | call to pad(_:) |
|
||||
| testSend.swift:54:17:54:17 | password | semmle.label | password |
|
||||
| testSend.swift:59:27:59:27 | str1 | semmle.label | str1 |
|
||||
| testSend.swift:60:27:60:27 | str2 | semmle.label | str2 |
|
||||
| testSend.swift:61:27:61:27 | str3 | semmle.label | str3 |
|
||||
| testSend.swift:65:27:65:27 | license_key | semmle.label | license_key |
|
||||
| testSend.swift:66:27:66:30 | .mobileNumber | semmle.label | .mobileNumber |
|
||||
| testURL.swift:13:22:13:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testURL.swift:13:54:13:54 | passwd | semmle.label | passwd |
|
||||
| testURL.swift:15:22:15:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testURL.swift:15:55:15:55 | account_no | semmle.label | account_no |
|
||||
| testURL.swift:16:22:16:55 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testURL.swift:16:55:16:55 | credit_card_no | semmle.label | credit_card_no |
|
||||
| testURL.swift:20:22:20:22 | passwd | semmle.label | passwd |
|
||||
| testSend.swift:58:13:58:13 | password | semmle.label | password |
|
||||
| testSend.swift:59:13:59:13 | password | semmle.label | password |
|
||||
| testSend.swift:60:13:60:25 | call to pad(_:) | semmle.label | call to pad(_:) |
|
||||
| testSend.swift:60:17:60:17 | password | semmle.label | password |
|
||||
| testSend.swift:65:27:65:27 | str1 | semmle.label | str1 |
|
||||
| testSend.swift:66:27:66:27 | str2 | semmle.label | str2 |
|
||||
| testSend.swift:67:27:67:27 | str3 | semmle.label | str3 |
|
||||
| testSend.swift:71:27:71:27 | license_key | semmle.label | license_key |
|
||||
| testSend.swift:72:27:72:30 | .mobileNumber | semmle.label | .mobileNumber |
|
||||
| testSend.swift:76:27:76:30 | .Telephone | semmle.label | .Telephone |
|
||||
| testSend.swift:77:27:77:30 | .birth_day | semmle.label | .birth_day |
|
||||
| testSend.swift:78:27:78:30 | .CarePlanID | semmle.label | .CarePlanID |
|
||||
| testSend.swift:79:27:79:30 | .BankCardNo | semmle.label | .BankCardNo |
|
||||
| testSend.swift:80:27:80:30 | .MyCreditRating | semmle.label | .MyCreditRating |
|
||||
| testURL.swift:17:22:17:54 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| 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
|
||||
| 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
|
||||
| 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: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: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: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: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: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: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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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: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: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: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: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: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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| testSend.swift:29:19:29: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:53:13:53:13 | password | label:password, type:credential |
|
||||
| testSend.swift:54:17:54:17 | password | label:password, type:credential |
|
||||
| testSend.swift:55:23:55:23 | password | label:password, type:credential |
|
||||
| testSend.swift:56:27:56:27 | password | label:password, type:credential |
|
||||
| testSend.swift:57:27:57:27 | password | label:password, type:credential |
|
||||
| testSend.swift:65:27:65:27 | license_key | label:license_key, type:credential |
|
||||
| testSend.swift:66:27:66:30 | .mobileNumber | label:mobileNumber, type:private information |
|
||||
| testSend.swift:69:27:69:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential |
|
||||
| testURL.swift:13:54:13:54 | passwd | label:passwd, type:credential |
|
||||
| testURL.swift:15:55:15:55 | account_no | label:account_no, type:private information |
|
||||
| testURL.swift:16:55:16:55 | credit_card_no | label:credit_card_no, type:private information |
|
||||
| testURL.swift:20:22:20:22 | passwd | label:passwd, type:credential |
|
||||
| testSend.swift:58:13:58:13 | password | label:password, type:credential |
|
||||
| testSend.swift:59:13:59:13 | password | label:password, type:credential |
|
||||
| testSend.swift:60:17:60:17 | password | label:password, type:credential |
|
||||
| testSend.swift:61:23:61:23 | password | label:password, type:credential |
|
||||
| testSend.swift:62:27:62:27 | password | label:password, type:credential |
|
||||
| testSend.swift:63:27:63:27 | password | label:password, type:credential |
|
||||
| testSend.swift:71:27:71:27 | license_key | label:license_key, type:credential |
|
||||
| testSend.swift:72:27:72:30 | .mobileNumber | label:mobileNumber, type:private information |
|
||||
| testSend.swift:75:27:75:30 | .passwordFeatureEnabled | label:passwordFeatureEnabled, type:credential |
|
||||
| testSend.swift:76:27:76:30 | .Telephone | label:Telephone, type:private information |
|
||||
| testSend.swift:77:27:77:30 | .birth_day | label:birth_day, type:private information |
|
||||
| testSend.swift:78:27:78:30 | .CarePlanID | label:CarePlanID, type:private information |
|
||||
| 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 mobilePlayer: String
|
||||
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) {
|
||||
@@ -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.mobilePlayer, 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 ---
|
||||
|
||||
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) {
|
||||
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)
|
||||
@@ -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 f = URL(string: passwd, relativeTo: base); // BAD
|
||||
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 ---
|
||||
|
||||
func test1(password: String, passwordHash : String) {
|
||||
func test1(password: String, passwordHash : String, passphrase: String, pass_phrase: String) {
|
||||
print(password) // $ MISSING: hasCleartextLogging=87
|
||||
print(password, separator: "") // $ MISSING: $ hasCleartextLogging=88
|
||||
print("", separator: password) // $ hasCleartextLogging=89
|
||||
@@ -132,6 +132,9 @@ func test1(password: String, passwordHash : String) {
|
||||
log.critical("\(passwordHash, privacy: .public)") // Safe
|
||||
log.fault("\(password, privacy: .public)") // $ MISSING: hasCleartextLogging=133
|
||||
log.fault("\(passwordHash, privacy: .public)") // Safe
|
||||
|
||||
NSLog(passphrase) // $ hasCleartextLogging=136
|
||||
NSLog(pass_phrase) // $ hasCleartextLogging=137
|
||||
}
|
||||
|
||||
class MyClass {
|
||||
@@ -145,14 +148,14 @@ func doSomething(password: String) { }
|
||||
func test3(x: String) {
|
||||
// alternative evidence of sensitivity...
|
||||
|
||||
NSLog(x) // $ MISSING: hasCleartextLogging=148
|
||||
NSLog(x) // $ MISSING: hasCleartextLogging=152
|
||||
doSomething(password: x);
|
||||
NSLog(x) // $ hasCleartextLogging=149
|
||||
NSLog(x) // $ hasCleartextLogging=152
|
||||
|
||||
let y = getPassword();
|
||||
NSLog(y) // $ hasCleartextLogging=152
|
||||
NSLog(y) // $ hasCleartextLogging=155
|
||||
|
||||
let z = MyClass()
|
||||
NSLog(z.harmless) // Safe
|
||||
NSLog(z.password) // $ hasCleartextLogging=157
|
||||
NSLog(z.password) // $ hasCleartextLogging=160
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user