Merge pull request #449 from owen-mc/model-couchbase-gocb

Model Couchbase Go library
This commit is contained in:
Owen Mansel-Chan
2021-01-14 17:00:05 +00:00
committed by GitHub
17 changed files with 4781 additions and 32 deletions

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Added support for [the offical Couchbase Go SDK library](https://github.com/couchbase/gocb), v1 and v2. The `go/sql-injection` query (which also handles non-SQL databases such as Couchbase) will now identify Couchbase queries built from untrusted external input.

View File

@@ -32,6 +32,7 @@ import semmle.go.dataflow.TaintTracking2
import semmle.go.frameworks.Beego
import semmle.go.frameworks.BeegoOrm
import semmle.go.frameworks.Chi
import semmle.go.frameworks.Couchbase
import semmle.go.frameworks.Echo
import semmle.go.frameworks.Email
import semmle.go.frameworks.Encoding

View File

@@ -0,0 +1,99 @@
/**
* Provides models of commonly used functions in the official Couchbase Go SDK library.
*/
import go
/**
* Provides models of commonly used functions in the official Couchbase Go SDK library.
*/
module Couchbase {
/**
* Gets a package path for the official Couchbase Go SDK library.
*
* Note that v1 and v2 have different APIs, but the names are disjoint so there is no need to
* distinguish between them.
*/
bindingset[result]
string packagePath() {
result =
package([
"gopkg.in/couchbase/gocb", "github.com/couchbase/gocb", "github.com/couchbaselabs/gocb"
], "")
}
/**
* Models of methods on `gocb/AnalyticsQuery` and `gocb/N1qlQuery` which which support a fluent
* interface by returning the receiver. They are not inherently relevant to taint.
*/
private class QueryMethodV1 extends TaintTracking::FunctionModel, Method {
QueryMethodV1() {
exists(string queryTypeName, string methodName |
queryTypeName = "AnalyticsQuery" and
methodName in [
"ContextId", "Deferred", "Pretty", "Priority", "RawParam", "ServerSideTimeout"
]
or
queryTypeName = "N1qlQuery" and
methodName in [
"AdHoc", "Consistency", "ConsistentWith", "Custom", "PipelineBatch", "PipelineCap",
"Profile", "ReadOnly", "ScanCap", "Timeout"
]
|
this.hasQualifiedName(packagePath(), queryTypeName, methodName)
)
}
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
inp.isReceiver() and outp.isResult()
}
}
private class QueryFromN1qlStatementV1 extends TaintTracking::FunctionModel {
QueryFromN1qlStatementV1() {
this.hasQualifiedName(packagePath(), ["NewAnalyticsQuery", "NewN1qlQuery"])
}
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
inp.isParameter(0) and outp.isResult()
}
}
/**
* A query used in an API function acting on a `Bucket` or `Cluster` struct of v1 of
* the official Couchbase Go library, gocb.
*/
private class CouchbaseV1Query extends NoSQL::Query::Range {
CouchbaseV1Query() {
// func (b *Bucket) ExecuteAnalyticsQuery(q *AnalyticsQuery, params interface{}) (AnalyticsResults, error)
// func (b *Bucket) ExecuteN1qlQuery(q *N1qlQuery, params interface{}) (QueryResults, error)
// func (c *Cluster) ExecuteAnalyticsQuery(q *AnalyticsQuery, params interface{}) (AnalyticsResults, error)
// func (c *Cluster) ExecuteN1qlQuery(q *N1qlQuery, params interface{}) (QueryResults, error)
exists(Method meth, string structName, string methodName |
structName in ["Bucket", "Cluster"] and
methodName in ["ExecuteN1qlQuery", "ExecuteAnalyticsQuery"] and
meth.hasQualifiedName(packagePath(), structName, methodName) and
this = meth.getACall().getArgument(0)
)
}
}
/**
* A query used in an API function acting on a `Bucket` or `Cluster` struct of v1 of
* the official Couchbase Go library, gocb.
*/
private class CouchbaseV2Query extends NoSQL::Query::Range {
CouchbaseV2Query() {
// func (c *Cluster) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error)
// func (c *Cluster) Query(statement string, opts *QueryOptions) (*QueryResult, error)
// func (s *Scope) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error)
// func (s *Scope) Query(statement string, opts *QueryOptions) (*QueryResult, error)
exists(Method meth, string structName, string methodName |
structName in ["Cluster", "Scope"] and
methodName in ["AnalyticsQuery", "Query"] and
meth.hasQualifiedName(packagePath(), structName, methodName) and
this = meth.getACall().getArgument(0)
)
}
}
}

View File

@@ -0,0 +1,17 @@
go 1.14
module test
require (
github.com/golang/snappy v0.0.2 // indirect
github.com/google/uuid v1.1.4 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
gopkg.in/couchbase/gocb.v1 v1.6.7
gopkg.in/couchbase/gocbcore.v7 v7.1.18 // indirect
gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4 // indirect
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 // indirect
gopkg.in/couchbaselabs/jsonx.v1 v1.0.0 // indirect
)

View File

@@ -0,0 +1,40 @@
package test
//go:generate depstubber -vendor gopkg.in/couchbase/gocb.v1 Bucket,Cluster NewAnalyticsQuery,NewN1qlQuery,QueryProfileNone,StatementPlus
import (
"net/http"
"time"
"gopkg.in/couchbase/gocb.v1"
)
func analyticsQuery(bucket gocb.Bucket, untrustedSource *http.Request) {
untrusted := untrustedSource.UserAgent()
q0 := gocb.NewAnalyticsQuery(untrusted)
q1 := q0.ContextId("")
q2 := q1.Deferred(true)
q3 := q2.Pretty(true)
q4 := q3.Priority(true)
q5 := q4.RawParam("name", nil)
duration, _ := time.ParseDuration("300s")
q6 := q5.ServerSideTimeout(duration)
bucket.ExecuteAnalyticsQuery(q6, nil) // $sqlinjection=q6
}
func n1qlQuery(cluster gocb.Cluster, untrustedSource *http.Request) {
untrusted := untrustedSource.UserAgent()
q0 := gocb.NewN1qlQuery(untrusted)
q1 := q0.AdHoc(true)
q2 := q1.Consistency(gocb.StatementPlus)
q3 := q2.ConsistentWith(&gocb.MutationState{})
q4 := q3.Custom("name", nil)
q5 := q4.PipelineBatch(2)
q6 := q5.PipelineCap(5)
q7 := q6.Profile(gocb.QueryProfileNone)
q8 := q7.ReadOnly(false)
q9 := q8.ScanCap(10)
duration, _ := time.ParseDuration("300s")
q10 := q9.Timeout(duration)
cluster.ExecuteN1qlQuery(q10, nil) // $sqlinjection=q10
}

View File

@@ -0,0 +1,18 @@
import go
import TestUtilities.InlineExpectationsTest
import semmle.go.security.SqlInjection
class SqlInjectionTest extends InlineExpectationsTest {
SqlInjectionTest() { this = "SqlInjectionTest" }
override string getARelevantTag() { result = "sqlinjection" }
override predicate hasActualResult(string file, int line, string element, string tag, string value) {
tag = "sqlinjection" and
exists(DataFlow::Node sink | any(SqlInjection::Configuration c).hasFlow(_, sink) |
element = sink.toString() and
value = sink.toString() and
sink.hasLocationInfo(file, line, _, _, _)
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
# github.com/golang/snappy v0.0.2
## explicit
github.com/golang/snappy
# github.com/google/uuid v1.1.4
## explicit
github.com/google/uuid
# github.com/opentracing/opentracing-go v1.2.0
## explicit
github.com/opentracing/opentracing-go
# github.com/pkg/errors v0.9.1
## explicit
github.com/pkg/errors
# golang.org/x/net v0.0.0-20201224014010-6772e930b67b
## explicit
golang.org/x/net
# golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
## explicit
golang.org/x/sync
# gopkg.in/couchbase/gocb.v1 v1.6.7
## explicit
gopkg.in/couchbase/gocb.v1
# gopkg.in/couchbase/gocbcore.v7 v7.1.18
## explicit
gopkg.in/couchbase/gocbcore.v7
# gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4
## explicit
gopkg.in/couchbaselabs/gocbconnstr.v1
# gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4
## explicit
gopkg.in/couchbaselabs/gojcbmock.v1
# gopkg.in/couchbaselabs/jsonx.v1 v1.0.0
## explicit
gopkg.in/couchbaselabs/jsonx.v1

View File

@@ -1,14 +0,0 @@
| main.go:24:22:24:29 | pipeline |
| main.go:27:27:27:32 | filter |
| main.go:29:23:29:28 | filter |
| main.go:30:22:30:27 | filter |
| main.go:32:32:32:37 | filter |
| main.go:35:17:35:22 | filter |
| main.go:36:20:36:25 | filter |
| main.go:37:29:37:34 | filter |
| main.go:38:30:38:35 | filter |
| main.go:39:29:39:34 | filter |
| main.go:45:23:45:28 | filter |
| main.go:47:23:47:28 | filter |
| main.go:48:22:48:27 | filter |
| main.go:49:18:49:25 | pipeline |

View File

@@ -1,3 +1,17 @@
import go
import TestUtilities.InlineExpectationsTest
select any(NoSQL::Query q)
class NoSQLQueryTest extends InlineExpectationsTest {
NoSQLQueryTest() { this = "NoSQLQueryTest" }
override string getARelevantTag() { result = "nosqlquery" }
override predicate hasActualResult(string file, int line, string element, string tag, string value) {
exists(NoSQL::Query q |
q.hasLocationInfo(file, line, _, _, _) and
element = q.toString() and
value = q.toString() and
tag = "nosqlquery"
)
}
}

View File

@@ -2,4 +2,14 @@ module main
go 1.14
require go.mongodb.org/mongo-driver v1.3.2
require (
github.com/couchbase/gocb/v2 v2.2.0
github.com/google/uuid v1.1.4 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
go.mongodb.org/mongo-driver v1.3.2
gopkg.in/couchbase/gocb.v1 v1.6.7
gopkg.in/couchbase/gocbcore.v7 v7.1.18 // indirect
gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4 // indirect
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 // indirect
gopkg.in/couchbaselabs/jsonx.v1 v1.0.0 // indirect
)

View File

@@ -2,10 +2,15 @@ package main
//go:generate depstubber -vendor go.mongodb.org/mongo-driver/bson/primitive D
//go:generate depstubber -vendor go.mongodb.org/mongo-driver/mongo Collection,Pipeline
//go:generate depstubber -vendor gopkg.in/couchbase/gocb.v1 Bucket,Cluster
//go:generate depstubber -vendor github.com/couchbase/gocb/v2 Cluster,Scope
import (
"context"
gocbv2 "github.com/couchbase/gocb/v2"
gocbv1 "gopkg.in/couchbase/gocb.v1"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
@@ -21,32 +26,46 @@ func test(coll *mongo.Collection, filter interface{}, models []mongo.WriteModel,
matchStage := bson.D{{"$match", filter}}
pipeline := mongo.Pipeline{matchStage}
coll.Aggregate(ctx, pipeline, nil)
coll.Aggregate(ctx, pipeline, nil) // $nosqlquery=pipeline
coll.BulkWrite(ctx, models, nil)
coll.Clone(nil)
coll.CountDocuments(ctx, filter, nil)
coll.CountDocuments(ctx, filter, nil) // $nosqlquery=filter
coll.Database()
coll.DeleteMany(ctx, filter, nil)
coll.DeleteOne(ctx, filter, nil)
coll.DeleteMany(ctx, filter, nil) // $nosqlquery=filter
coll.DeleteOne(ctx, filter, nil) // $nosqlquery=filter
coll.Distinct(ctx, fieldName, filter)
coll.Distinct(ctx, fieldName, filter) // $nosqlquery=filter
coll.Drop(ctx)
coll.EstimatedDocumentCount(ctx, nil)
coll.Find(ctx, filter, nil)
coll.FindOne(ctx, filter, nil)
coll.FindOneAndDelete(ctx, filter, nil)
coll.FindOneAndReplace(ctx, filter, nil)
coll.FindOneAndUpdate(ctx, filter, nil)
coll.Find(ctx, filter, nil) // $nosqlquery=filter
coll.FindOne(ctx, filter, nil) // $nosqlquery=filter
coll.FindOneAndDelete(ctx, filter, nil) // $nosqlquery=filter
coll.FindOneAndReplace(ctx, filter, nil) // $nosqlquery=filter
coll.FindOneAndUpdate(ctx, filter, nil) // $nosqlquery=filter
coll.Indexes()
coll.InsertMany(ctx, documents)
coll.InsertOne(ctx, document, nil)
coll.Name()
replacement := bson.D{{"location", "NYC"}}
coll.ReplaceOne(ctx, filter, replacement)
coll.ReplaceOne(ctx, filter, replacement) // $nosqlquery=filter
update := bson.D{{"$inc", bson.D{{"age", 1}}}}
coll.UpdateMany(ctx, filter, update)
coll.UpdateOne(ctx, filter, update)
coll.Watch(ctx, pipeline)
coll.UpdateMany(ctx, filter, update) // $nosqlquery=filter
coll.UpdateOne(ctx, filter, update) // $nosqlquery=filter
coll.Watch(ctx, pipeline) // $nosqlquery=pipeline
}
func testGocbV1(bucket gocbv1.Bucket, cluster gocbv1.Cluster, aq *gocbv1.AnalyticsQuery, nq *gocbv1.N1qlQuery) {
bucket.ExecuteAnalyticsQuery(aq, nil) // $nosqlquery=aq
cluster.ExecuteAnalyticsQuery(aq, nil) // $nosqlquery=aq
bucket.ExecuteN1qlQuery(nq, nil) // $nosqlquery=nq
cluster.ExecuteN1qlQuery(nq, nil) // $nosqlquery=nq
}
func testGocbV2(cluster gocbv2.Cluster, scope gocbv2.Scope) {
cluster.AnalyticsQuery("a", nil) // $nosqlquery="a"
scope.AnalyticsQuery("b", nil) // $nosqlquery="b"
cluster.Query("c", nil) // $nosqlquery="c"
scope.Query("d", nil) // $nosqlquery="d"
}
func main() {}

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,6 @@
// Package primitive is a stub of go.mongodb.org/mongo-driver/bson/primitive, generated by depstubber.
package primitive
import ()
type D []E
func (_ D) Map() M {

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,27 @@
# go.mongodb.org/mongo-driver v1.3.2
## explicit
go.mongodb.org/mongo-driver
# github.com/couchbase/gocb/v2 v2.2.0
## explicit
github.com/couchbase/gocb/v2
# github.com/google/uuid v1.1.4
## explicit
github.com/google/uuid
# github.com/opentracing/opentracing-go v1.2.0
## explicit
github.com/opentracing/opentracing-go
# gopkg.in/couchbase/gocb.v1 v1.6.7
## explicit
gopkg.in/couchbase/gocb.v1
# gopkg.in/couchbase/gocbcore.v7 v7.1.18
## explicit
gopkg.in/couchbase/gocbcore.v7
# gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4
## explicit
gopkg.in/couchbaselabs/gocbconnstr.v1
# gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4
## explicit
gopkg.in/couchbaselabs/gojcbmock.v1
# gopkg.in/couchbaselabs/jsonx.v1 v1.0.0
## explicit
gopkg.in/couchbaselabs/jsonx.v1