Merge branch 'main' into py-mad-xss

This commit is contained in:
Mathew Payne
2023-07-17 11:08:09 +01:00
committed by GitHub
87 changed files with 1712 additions and 223 deletions

View File

@@ -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()
}
/**

View File

@@ -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()
}
/**

View File

@@ -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()
}
/**

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Support for [gqlgen](https://github.com/99designs/gqlgen) has been added.

View File

@@ -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

View File

@@ -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()
}
/**

View 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()
}
}
}

View 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
)

View File

@@ -0,0 +1,2 @@
failures
testFailures

View File

@@ -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>

View File

@@ -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 *****************************

View File

@@ -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"`
}

View File

@@ -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{}

View File

@@ -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!
}

View File

@@ -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 }

View 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
}

View 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

View File

@@ -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,
1 package sink source summary sink:bean-validation sink:command-injection sink:file-content-store sink:fragment-injection sink:groovy-injection sink:hostname-verification sink:html-injection sink:information-leak sink:intent-redirection sink:jexl-injection sink:jndi-injection sink:js-injection sink:ldap-injection sink:log-injection sink:mvel-injection sink:ognl-injection sink:path-injection sink:pending-intents sink:regex-use sink:regex-use[-1] sink:regex-use[0] sink:regex-use[] sink:regex-use[f-1] sink:regex-use[f1] sink:regex-use[f] sink:request-forgery sink:response-splitting sink:sql-injection sink:template-injection sink:url-redirection sink:xpath-injection sink:xslt-injection source:android-external-storage-dir source:contentprovider source:remote summary:taint summary:value
55 jakarta.ws.rs.core 2 149 2 94 55
56 java.awt 3 3
57 java.beans 1 1
58 java.io 49 50 45 22 27 28 43 2
59 java.lang 31 92 93 13 8 5 4 1 56 57 36
60 java.net 13 3 21 23 13 3 21 23
61 java.nio 47 53 35 36 3 5 44 47 1 35 36
62 java.sql 13 2 4 9 2
63 java.util 44 45 484 485 1 34 5 2 1 2 44 45 440
64 javafx.scene.web 1 1
65 javax.faces.context 2 7 2 7
66 javax.imageio.stream 1 1
84 net.sf.json 2 338 2 321 17
85 net.sf.saxon.s9api 5 5
86 ognl 6 6
87 okhttp3 4 48 50 4 23 25 27
88 org.acegisecurity 49 49
89 org.antlr.runtime 1 1
90 org.apache.commons.codec 6 6
98 org.apache.commons.jexl2 15 15
99 org.apache.commons.jexl3 15 15
100 org.apache.commons.lang 765 594 171
101 org.apache.commons.lang3 6 424 425 6 293 294 131
102 org.apache.commons.logging 6 6
103 org.apache.commons.net 9 12 3 6 12
104 org.apache.commons.ognl 6 6
131 org.eclipse.jetty.client 1 1
132 org.fusesource.leveldbjni 1 1
133 org.geogebra.web.full.main 1 1
134 org.gradle.api.file 2 2
135 org.hibernate 7 7
136 org.influxdb 1 1
137 org.jboss.logging 324 324
181 ratpack.handling 6 4 6 4
182 ratpack.http 10 10 10 10
183 ratpack.util 35 35
184 retrofit2 1 1 1 1

View File

@@ -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

View File

@@ -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()
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}
/**

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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.
*/

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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 { }

View File

@@ -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(

View File

@@ -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()
}
}

View File

@@ -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) {

View File

@@ -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
node = any(ExtendCall call).getASourceOperand() and
lbl instanceof ObjectPrototype
)
}
override predicate isAdditionalFlowStep(

View File

@@ -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)

View File

@@ -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`. */

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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()
}
}
/**

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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()
}
/**

View File

@@ -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]

View File

@@ -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.
*

View File

@@ -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()
}
/**

View File

@@ -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.

View File

@@ -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`.
*/

View File

@@ -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()
)
}
}

View File

@@ -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
{ }
}

View 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>
}

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved resolution of calls performed on an object created with `Proc.new`.

View File

@@ -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>

View 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"

View File

@@ -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

View File

@@ -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

View File

@@ -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]
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 |

View File

@@ -0,0 +1 @@
experimental/xpath-injection/XpathInjection.ql

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added new heuristics to `SensitiveExprs.qll`, enhancing detection from the library.

View File

@@ -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()
}
/**

View File

@@ -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).*") }
}

View 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.

View 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>

View 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 + "'."

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'. |

View File

@@ -0,0 +1 @@
queries/Security/CWE-1333/ReDoS.ql

View 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: "")
}

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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]
}

View File

@@ -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
}

View File

@@ -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
}