diff --git a/change-notes/2020-07-28-library-models.md b/change-notes/2020-07-28-library-models.md new file mode 100644 index 00000000..33b16e40 --- /dev/null +++ b/change-notes/2020-07-28-library-models.md @@ -0,0 +1,11 @@ +lgtm,codescanning +* Basic support for the [Go-restful](https://github.com/emicklei/go-restful) HTTP library has been added, which + may lead to more results from the security queries. +* Basic support for the [Gorm](https://github.com/go-gorm/gorm) ORM library has been added (specifically, its SQL statement building facilities), which + may lead to more results from the security queries. +* Basic support for the [Sqlx](https://github.com/jmoiron/sqlx) database access library has been added, which + may lead to more results from the security queries. +* Basic support for the [Json-iterator](https://github.com/json-iterator/go) JSON library has been added, which + may lead to more results from the security queries. + + diff --git a/ql/src/go.qll b/ql/src/go.qll index 512fd06d..0f497ac8 100644 --- a/ql/src/go.qll +++ b/ql/src/go.qll @@ -26,6 +26,7 @@ import semmle.go.dataflow.GlobalValueNumbering import semmle.go.dataflow.SSA import semmle.go.dataflow.TaintTracking import semmle.go.frameworks.Email +import semmle.go.frameworks.Encoding import semmle.go.frameworks.Glog import semmle.go.frameworks.HTTP import semmle.go.frameworks.Macaron diff --git a/ql/src/semmle/go/frameworks/Encoding.qll b/ql/src/semmle/go/frameworks/Encoding.qll new file mode 100644 index 00000000..5d912539 --- /dev/null +++ b/ql/src/semmle/go/frameworks/Encoding.qll @@ -0,0 +1,28 @@ +/** + * Provides classes modelling taint propagation through marshalling and encoding functions. + */ + +import go + +/** A model of json-iterator's `Unmarshal` function, propagating taint from the JSON input to the decoded object. */ +private class JsonIteratorUnmarshalFunction extends TaintTracking::FunctionModel, + UnmarshalingFunction::Range { + JsonIteratorUnmarshalFunction() { + this.hasQualifiedName("github.com/json-iterator/go", ["Unmarshal", "UnmarshalFromString"]) + or + exists(Method m | + m.hasQualifiedName("github.com/json-iterator/go", "API", ["Unmarshal", "UnmarshalFromString"]) and + this.(Method).implements(m) + ) + } + + override DataFlow::FunctionInput getAnInput() { result.isParameter(0) } + + override DataFlow::FunctionOutput getOutput() { result.isParameter(1) } + + override string getFormat() { result = "JSON" } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp = getAnInput() and outp = getOutput() + } +} diff --git a/ql/src/semmle/go/frameworks/HTTP.qll b/ql/src/semmle/go/frameworks/HTTP.qll index a159d75d..0b1175f0 100644 --- a/ql/src/semmle/go/frameworks/HTTP.qll +++ b/ql/src/semmle/go/frameworks/HTTP.qll @@ -231,3 +231,42 @@ private module StdlibHttp { } } } + +/** + * Provides models of the go-restful library (https://github.com/emicklei/go-restful). + */ +private module GoRestfulHttp { + /** + * A model for methods defined on go-restful's `Request` object that may return user-controlled data. + */ + private class GoRestfulSourceMethod extends Method { + GoRestfulSourceMethod() { + this + .hasQualifiedName(package("github.com/emicklei/go-restful", ""), "Request", + ["QueryParameters", "QueryParameter", "BodyParameter", "HeaderParameter", + "PathParameter", "PathParameters"]) + } + } + + /** + * A model of go-restful's `Request` object as a source of user-controlled data. + */ + private class GoRestfulSource extends UntrustedFlowSource::Range { + GoRestfulSource() { this = any(GoRestfulSourceMethod g).getACall() } + } + + /** + * A model of go-restful's `Request.ReadEntity` method as a source of user-controlled data. + */ + private class GoRestfulReadEntitySource extends UntrustedFlowSource::Range { + GoRestfulReadEntitySource() { + exists(DataFlow::MethodCallNode call | + call + .getTarget() + .hasQualifiedName(package("github.com/emicklei/go-restful", ""), "Request", "ReadEntity") + | + this = any(FunctionOutput output | output.isParameter(0)).getExitNode(call) + ) + } + } +} diff --git a/ql/src/semmle/go/frameworks/SQL.qll b/ql/src/semmle/go/frameworks/SQL.qll index 1244e5cc..a959f9be 100644 --- a/ql/src/semmle/go/frameworks/SQL.qll +++ b/ql/src/semmle/go/frameworks/SQL.qll @@ -160,4 +160,29 @@ module SQL { } } } + + /** A model for sinks of github.com/jinzhu/gorm. */ + private class GormSink extends SQL::QueryString::Range { + GormSink() { + exists(Method meth, string name | + meth.hasQualifiedName("github.com/jinzhu/gorm", "DB", name) and + this = meth.getACall().getArgument(0) and + name in ["Where", "Raw", "Order", "Not", "Or", "Select", "Table", "Group", "Having", "Joins"] + ) + } + } + + /** A model for sinks of github.com/jmoiron/sqlx. */ + private class SqlxSink extends SQL::QueryString::Range { + SqlxSink() { + exists(Method meth, string name, int n | + meth.hasQualifiedName("github.com/jmoiron/sqlx", ["DB", "Tx"], name) and + this = meth.getACall().getArgument(n) + | + name = ["Select", "Get"] and n = 1 + or + name = ["MustExec", "Queryx", "NamedExec", "NamedQuery"] and n = 0 + ) + } + } } diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/go.mod b/ql/test/library-tests/semmle/go/frameworks/Encoding/go.mod new file mode 100644 index 00000000..91013339 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/go.mod @@ -0,0 +1,7 @@ +module jsonittest + +go 1.14 + +require ( + github.com/json-iterator/go v1.1.10 +) diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected new file mode 100644 index 00000000..af6b6712 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.expected @@ -0,0 +1,4 @@ +| jsoniter.go:28:15:28:24 | selection of field | jsoniter.go:23:20:23:38 | call to getUntrustedBytes : slice type | jsoniter.go:28:15:28:24 | selection of field | This command depends on $@. | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | a user-provided value | +| jsoniter.go:32:15:32:25 | selection of field | jsoniter.go:23:20:23:38 | call to getUntrustedBytes : slice type | jsoniter.go:32:15:32:25 | selection of field | This command depends on $@. | jsoniter.go:23:20:23:38 | call to getUntrustedBytes | a user-provided value | +| jsoniter.go:36:15:36:25 | selection of field | jsoniter.go:24:21:24:40 | call to getUntrustedString : string | jsoniter.go:36:15:36:25 | selection of field | This command depends on $@. | jsoniter.go:24:21:24:40 | call to getUntrustedString | a user-provided value | +| jsoniter.go:40:15:40:25 | selection of field | jsoniter.go:24:21:24:40 | call to getUntrustedString : string | jsoniter.go:40:15:40:25 | selection of field | This command depends on $@. | jsoniter.go:24:21:24:40 | call to getUntrustedString | a user-provided value | diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.go b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.go new file mode 100644 index 00000000..068a6709 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.go @@ -0,0 +1,42 @@ +package jsonittest + +import ( + jsoniter "github.com/json-iterator/go" + "os/exec" +) + +func getUntrustedString() string { + return "trouble" +} + +func getUntrustedBytes() []byte { + return []byte{} +} + +type myData struct { + field string +} + +func main() { + + var json = jsoniter.ExampleConfig{} + untrustedInput := getUntrustedBytes() + untrustedString := getUntrustedString() + + data := myData{} + json.Unmarshal(untrustedInput, &data) + exec.Command(data.field) + + data2 := myData{} + jsoniter.Unmarshal(untrustedInput, &data2) + exec.Command(data2.field) + + data3 := myData{} + json.UnmarshalFromString(untrustedString, &data3) + exec.Command(data3.field) + + data4 := myData{} + jsoniter.UnmarshalFromString(untrustedString, &data4) + exec.Command(data4.field) + +} diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql new file mode 100644 index 00000000..a02f8593 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/jsoniter.ql @@ -0,0 +1,15 @@ +import go +import semmle.go.security.CommandInjection + +class UntrustedFunction extends Function { + UntrustedFunction() { this.getName() = ["getUntrustedString", "getUntrustedBytes"] } +} + +class UntrustedSource extends DataFlow::Node, UntrustedFlowSource::Range { + UntrustedSource() { this = any(UntrustedFunction f).getACall() } +} + +from CommandInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/LICENSE b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/LICENSE new file mode 100644 index 00000000..2cf4f5ab --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 json-iterator + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/stub.go b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/stub.go new file mode 100644 index 00000000..fef9f755 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/github.com/json-iterator/go/stub.go @@ -0,0 +1,368 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/json-iterator/go, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/json-iterator/go (exports: API; functions: Unmarshal,UnmarshalFromString) + +// Package gopkg is a stub of github.com/json-iterator/go, generated by depstubber. +package gopkg + +import ( + json "encoding/json" + io "io" + big "math/big" +) + +// Manually trimmed to only functions of interest +type API interface { + Unmarshal(_ []byte, _ interface{}) error + UnmarshalFromString(_ string, _ interface{}) error +} + +// Manually added, as the real library's concrete API types are all private +type ExampleConfig struct{} + +func (_ *ExampleConfig) Unmarshal(_ []byte, _ interface{}) error { return nil } +func (_ *ExampleConfig) UnmarshalFromString(_ string, _ interface{}) error { return nil } + +type Any interface { + Get(_ ...interface{}) Any + GetInterface() interface{} + Keys() []string + LastError() error + MustBeValid() Any + Size() int + ToBool() bool + ToFloat32() float32 + ToFloat64() float64 + ToInt() int + ToInt32() int32 + ToInt64() int64 + ToString() string + ToUint() uint + ToUint32() uint32 + ToUint64() uint64 + ToVal(_ interface{}) + ValueType() ValueType + WriteTo(_ *Stream) +} + +type Binding struct { + Field interface{} + FromNames []string + ToNames []string + Encoder ValEncoder + Decoder ValDecoder +} + +type Decoder struct{} + +func (_ *Decoder) Buffered() io.Reader { + return nil +} + +func (_ *Decoder) Decode(_ interface{}) error { + return nil +} + +func (_ *Decoder) DisallowUnknownFields() {} + +func (_ *Decoder) More() bool { + return false +} + +func (_ *Decoder) UseNumber() {} + +type Encoder struct{} + +func (_ *Encoder) Encode(_ interface{}) error { + return nil +} + +func (_ *Encoder) SetEscapeHTML(_ bool) {} + +func (_ *Encoder) SetIndent(_ string, _ string) {} + +type Extension interface { + CreateDecoder(_ interface{}) ValDecoder + CreateEncoder(_ interface{}) ValEncoder + CreateMapKeyDecoder(_ interface{}) ValDecoder + CreateMapKeyEncoder(_ interface{}) ValEncoder + DecorateDecoder(_ interface{}, _ ValDecoder) ValDecoder + DecorateEncoder(_ interface{}, _ ValEncoder) ValEncoder + UpdateStructDescriptor(_ *StructDescriptor) +} + +type Iterator struct { + Error error + Attachment interface{} +} + +func (_ *Iterator) CurrentBuffer() string { + return "" +} + +func (_ *Iterator) Pool() IteratorPool { + return nil +} + +func (_ *Iterator) Read() interface{} { + return nil +} + +func (_ *Iterator) ReadAny() Any { + return nil +} + +func (_ *Iterator) ReadArray() bool { + return false +} + +func (_ *Iterator) ReadArrayCB(_ func(*Iterator) bool) bool { + return false +} + +func (_ *Iterator) ReadBigFloat() *big.Float { + return nil +} + +func (_ *Iterator) ReadBigInt() *big.Int { + return nil +} + +func (_ *Iterator) ReadBool() bool { + return false +} + +func (_ *Iterator) ReadFloat32() float32 { + return 0 +} + +func (_ *Iterator) ReadFloat64() float64 { + return 0 +} + +func (_ *Iterator) ReadInt() int { + return 0 +} + +func (_ *Iterator) ReadInt16() int16 { + return 0 +} + +func (_ *Iterator) ReadInt32() int32 { + return 0 +} + +func (_ *Iterator) ReadInt64() int64 { + return 0 +} + +func (_ *Iterator) ReadInt8() int8 { + return 0 +} + +func (_ *Iterator) ReadMapCB(_ func(*Iterator, string) bool) bool { + return false +} + +func (_ *Iterator) ReadNil() bool { + return false +} + +func (_ *Iterator) ReadNumber() json.Number { + return "" +} + +func (_ *Iterator) ReadObject() string { + return "" +} + +func (_ *Iterator) ReadObjectCB(_ func(*Iterator, string) bool) bool { + return false +} + +func (_ *Iterator) ReadString() string { + return "" +} + +func (_ *Iterator) ReadStringAsSlice() []byte { + return nil +} + +func (_ *Iterator) ReadUint() uint { + return 0 +} + +func (_ *Iterator) ReadUint16() uint16 { + return 0 +} + +func (_ *Iterator) ReadUint32() uint32 { + return 0 +} + +func (_ *Iterator) ReadUint64() uint64 { + return 0 +} + +func (_ *Iterator) ReadUint8() byte { + return 0 +} + +func (_ *Iterator) ReadVal(_ interface{}) {} + +func (_ *Iterator) ReportError(_ string, _ string) {} + +func (_ *Iterator) Reset(_ io.Reader) *Iterator { + return nil +} + +func (_ *Iterator) ResetBytes(_ []byte) *Iterator { + return nil +} + +func (_ *Iterator) Skip() {} + +func (_ *Iterator) SkipAndAppendBytes(_ []byte) []byte { + return nil +} + +func (_ *Iterator) SkipAndReturnBytes() []byte { + return nil +} + +func (_ *Iterator) WhatIsNext() ValueType { + return 0 +} + +type IteratorPool interface { + BorrowIterator(_ []byte) *Iterator + ReturnIterator(_ *Iterator) +} + +type Stream struct { + Error error + Attachment interface{} +} + +func (_ *Stream) Available() int { + return 0 +} + +func (_ *Stream) Buffer() []byte { + return nil +} + +func (_ *Stream) Buffered() int { + return 0 +} + +func (_ *Stream) Flush() error { + return nil +} + +func (_ *Stream) Pool() StreamPool { + return nil +} + +func (_ *Stream) Reset(_ io.Writer) {} + +func (_ *Stream) SetBuffer(_ []byte) {} + +func (_ *Stream) Write(_ []byte) (int, error) { + return 0, nil +} + +func (_ *Stream) WriteArrayEnd() {} + +func (_ *Stream) WriteArrayStart() {} + +func (_ *Stream) WriteBool(_ bool) {} + +func (_ *Stream) WriteEmptyArray() {} + +func (_ *Stream) WriteEmptyObject() {} + +func (_ *Stream) WriteFalse() {} + +func (_ *Stream) WriteFloat32(_ float32) {} + +func (_ *Stream) WriteFloat32Lossy(_ float32) {} + +func (_ *Stream) WriteFloat64(_ float64) {} + +func (_ *Stream) WriteFloat64Lossy(_ float64) {} + +func (_ *Stream) WriteInt(_ int) {} + +func (_ *Stream) WriteInt16(_ int16) {} + +func (_ *Stream) WriteInt32(_ int32) {} + +func (_ *Stream) WriteInt64(_ int64) {} + +func (_ *Stream) WriteInt8(_ int8) {} + +func (_ *Stream) WriteMore() {} + +func (_ *Stream) WriteNil() {} + +func (_ *Stream) WriteObjectEnd() {} + +func (_ *Stream) WriteObjectField(_ string) {} + +func (_ *Stream) WriteObjectStart() {} + +func (_ *Stream) WriteRaw(_ string) {} + +func (_ *Stream) WriteString(_ string) {} + +func (_ *Stream) WriteStringWithHTMLEscaped(_ string) {} + +func (_ *Stream) WriteTrue() {} + +func (_ *Stream) WriteUint(_ uint) {} + +func (_ *Stream) WriteUint16(_ uint16) {} + +func (_ *Stream) WriteUint32(_ uint32) {} + +func (_ *Stream) WriteUint64(_ uint64) {} + +func (_ *Stream) WriteUint8(_ byte) {} + +func (_ *Stream) WriteVal(_ interface{}) {} + +type StreamPool interface { + BorrowStream(_ io.Writer) *Stream + ReturnStream(_ *Stream) +} + +type StructDescriptor struct { + Type interface{} + Fields []*Binding +} + +func (_ *StructDescriptor) GetField(_ string) *Binding { + return nil +} + +func Unmarshal(_ []byte, _ interface{}) error { + return nil +} + +func UnmarshalFromString(_ string, _ interface{}) error { + return nil +} + +type ValDecoder interface { + Decode(_ interface{}, _ *Iterator) +} + +type ValEncoder interface { + Encode(_ interface{}, _ *Stream) + IsEmpty(_ interface{}) bool +} + +type ValueType int diff --git a/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/modules.txt new file mode 100644 index 00000000..c8d2d7c5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/Encoding/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/json-iterator/go v1.1.10 +## explicit +github.com/json-iterator/go diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/go.mod b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/go.mod new file mode 100644 index 00000000..421f0669 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/go.mod @@ -0,0 +1,8 @@ +module gorestfultest + +go 1.14 + +require ( + github.com/emicklei/go-restful/v3 v3.2.0 + github.com/json-iterator/go v1.1.10 // indirect +) diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.expected b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.expected new file mode 100644 index 00000000..ee3137d7 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.expected @@ -0,0 +1,14 @@ +| gorestful.go:13:15:13:47 | index expression | gorestful.go:13:15:13:44 | call to QueryParameters : slice type | gorestful.go:13:15:13:47 | index expression | This command depends on $@. | gorestful.go:13:15:13:44 | call to QueryParameters | a user-provided value | +| gorestful.go:14:15:14:43 | call to QueryParameter | gorestful.go:14:15:14:43 | call to QueryParameter | gorestful.go:14:15:14:43 | call to QueryParameter | This command depends on $@. | gorestful.go:14:15:14:43 | call to QueryParameter | a user-provided value | +| gorestful.go:16:15:16:17 | val | gorestful.go:15:12:15:39 | call to BodyParameter : tuple type | gorestful.go:16:15:16:17 | val | This command depends on $@. | gorestful.go:15:12:15:39 | call to BodyParameter | a user-provided value | +| gorestful.go:17:15:17:44 | call to HeaderParameter | gorestful.go:17:15:17:44 | call to HeaderParameter | gorestful.go:17:15:17:44 | call to HeaderParameter | This command depends on $@. | gorestful.go:17:15:17:44 | call to HeaderParameter | a user-provided value | +| gorestful.go:18:15:18:42 | call to PathParameter | gorestful.go:18:15:18:42 | call to PathParameter | gorestful.go:18:15:18:42 | call to PathParameter | This command depends on $@. | gorestful.go:18:15:18:42 | call to PathParameter | a user-provided value | +| gorestful.go:19:15:19:45 | index expression | gorestful.go:19:15:19:38 | call to PathParameters : map type | gorestful.go:19:15:19:45 | index expression | This command depends on $@. | gorestful.go:19:15:19:38 | call to PathParameters | a user-provided value | +| gorestful.go:22:15:22:21 | selection of cmd | gorestful.go:21:21:21:24 | &... : pointer type | gorestful.go:22:15:22:21 | selection of cmd | This command depends on $@. | gorestful.go:21:21:21:24 | &... | a user-provided value | +| gorestful_v2.go:13:15:13:47 | index expression | gorestful_v2.go:13:15:13:44 | call to QueryParameters : slice type | gorestful_v2.go:13:15:13:47 | index expression | This command depends on $@. | gorestful_v2.go:13:15:13:44 | call to QueryParameters | a user-provided value | +| gorestful_v2.go:14:15:14:43 | call to QueryParameter | gorestful_v2.go:14:15:14:43 | call to QueryParameter | gorestful_v2.go:14:15:14:43 | call to QueryParameter | This command depends on $@. | gorestful_v2.go:14:15:14:43 | call to QueryParameter | a user-provided value | +| gorestful_v2.go:16:15:16:17 | val | gorestful_v2.go:15:12:15:39 | call to BodyParameter : tuple type | gorestful_v2.go:16:15:16:17 | val | This command depends on $@. | gorestful_v2.go:15:12:15:39 | call to BodyParameter | a user-provided value | +| gorestful_v2.go:17:15:17:44 | call to HeaderParameter | gorestful_v2.go:17:15:17:44 | call to HeaderParameter | gorestful_v2.go:17:15:17:44 | call to HeaderParameter | This command depends on $@. | gorestful_v2.go:17:15:17:44 | call to HeaderParameter | a user-provided value | +| gorestful_v2.go:18:15:18:42 | call to PathParameter | gorestful_v2.go:18:15:18:42 | call to PathParameter | gorestful_v2.go:18:15:18:42 | call to PathParameter | This command depends on $@. | gorestful_v2.go:18:15:18:42 | call to PathParameter | a user-provided value | +| gorestful_v2.go:19:15:19:45 | index expression | gorestful_v2.go:19:15:19:38 | call to PathParameters : map type | gorestful_v2.go:19:15:19:45 | index expression | This command depends on $@. | gorestful_v2.go:19:15:19:38 | call to PathParameters | a user-provided value | +| gorestful_v2.go:22:15:22:21 | selection of cmd | gorestful_v2.go:21:21:21:24 | &... : pointer type | gorestful_v2.go:22:15:22:21 | selection of cmd | This command depends on $@. | gorestful_v2.go:21:21:21:24 | &... | a user-provided value | diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.go b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.go new file mode 100644 index 00000000..fa2a1959 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.go @@ -0,0 +1,23 @@ +package gorestfultest + +import ( + restful "github.com/emicklei/go-restful/v3" + "os/exec" +) + +type myObject struct { + cmd string +} + +func requestHandler(request *restful.Request, response *restful.Response) { + exec.Command(request.QueryParameters("xyz")[0]) // BAD + exec.Command(request.QueryParameter("xyz")) // BAD + val, _ := request.BodyParameter("xyz") + exec.Command(val) // BAD + exec.Command(request.HeaderParameter("xyz")) // BAD + exec.Command(request.PathParameter("xyz")) // BAD + exec.Command(request.PathParameters()["xyz"]) // BAD + obj := myObject{} + request.ReadEntity(&obj) + exec.Command(obj.cmd) // BAD +} diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.ql b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.ql new file mode 100644 index 00000000..4fa5f250 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful.ql @@ -0,0 +1,7 @@ +import go +import semmle.go.security.CommandInjection + +from CommandInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful_v2.go b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful_v2.go new file mode 100644 index 00000000..884c037b --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/gorestful_v2.go @@ -0,0 +1,23 @@ +package gorestfultest + +import ( + restful "github.com/emicklei/go-restful" + "os/exec" +) + +type myObjectV2 struct { + cmd string +} + +func requestHandlerV2(request *restful.Request, response *restful.Response) { + exec.Command(request.QueryParameters("xyz")[0]) // BAD + exec.Command(request.QueryParameter("xyz")) // BAD + val, _ := request.BodyParameter("xyz") + exec.Command(val) // BAD + exec.Command(request.HeaderParameter("xyz")) // BAD + exec.Command(request.PathParameter("xyz")) // BAD + exec.Command(request.PathParameters()["xyz"]) // BAD + obj := myObjectV2{} + request.ReadEntity(&obj) + exec.Command(obj.cmd) // BAD +} diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/LICENSE b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/LICENSE new file mode 100644 index 00000000..ece7ec61 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012,2013 Ernest Micklei + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/stub.go b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/stub.go new file mode 100644 index 00000000..7cde3962 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/stub.go @@ -0,0 +1,54 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/emicklei/go-restful/v3, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/emicklei/go-restful/v3 (exports: Request; functions: ) + +// Package gorestfulstub is a stub of github.com/emicklei/go-restful, generated by depstubber. +package gorestfulstub + +import ( + http "net/http" +) + +type Request struct { + Request *http.Request +} + +func (_ Request) Attribute(_ string) interface{} { + return nil +} + +func (_ Request) SelectedRoutePath() string { + return "" +} + +func (_ *Request) BodyParameter(_ string) (string, error) { + return "", nil +} + +func (_ *Request) HeaderParameter(_ string) string { + return "" +} + +func (_ *Request) PathParameter(_ string) string { + return "" +} + +func (_ *Request) PathParameters() map[string]string { + return nil +} + +func (_ *Request) QueryParameter(_ string) string { + return "" +} + +func (_ *Request) QueryParameters(_ string) []string { + return nil +} + +func (_ *Request) ReadEntity(_ interface{}) error { + return nil +} + +func (_ *Request) SetAttribute(_ string, _ interface{}) {} diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/v3/stub.go b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/v3/stub.go new file mode 100644 index 00000000..71d25f09 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/github.com/emicklei/go-restful/v3/stub.go @@ -0,0 +1,54 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/emicklei/go-restful/v3, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/emicklei/go-restful/v3 (exports: Request; functions: ) + +// Package gorestfulstub is a stub of github.com/emicklei/go-restful/v3, generated by depstubber. +package gorestfulstub + +import ( + http "net/http" +) + +type Request struct { + Request *http.Request +} + +func (_ Request) Attribute(_ string) interface{} { + return nil +} + +func (_ Request) SelectedRoutePath() string { + return "" +} + +func (_ *Request) BodyParameter(_ string) (string, error) { + return "", nil +} + +func (_ *Request) HeaderParameter(_ string) string { + return "" +} + +func (_ *Request) PathParameter(_ string) string { + return "" +} + +func (_ *Request) PathParameters() map[string]string { + return nil +} + +func (_ *Request) QueryParameter(_ string) string { + return "" +} + +func (_ *Request) QueryParameters(_ string) []string { + return nil +} + +func (_ *Request) ReadEntity(_ interface{}) error { + return nil +} + +func (_ *Request) SetAttribute(_ string, _ interface{}) {} diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/modules.txt new file mode 100644 index 00000000..ed7043d9 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Gorestful/vendor/modules.txt @@ -0,0 +1,6 @@ +# github.com/emicklei/go-restful/v3 v3.2.0 +## explicit +github.com/emicklei/go-restful/v3 +# github.com/json-iterator/go v1.1.10 +## explicit +github.com/json-iterator/go diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/go.mod b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/go.mod new file mode 100644 index 00000000..fad4c47a --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/go.mod @@ -0,0 +1,7 @@ +module gormtest + +go 1.14 + +require ( + github.com/jinzhu/gorm v1.9.15 +) diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.expected b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.expected new file mode 100644 index 00000000..55c7a9ff --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.expected @@ -0,0 +1,9 @@ +| gorm.go:15:11:15:19 | untrusted | +| gorm.go:16:9:16:17 | untrusted | +| gorm.go:17:11:17:19 | untrusted | +| gorm.go:18:8:18:16 | untrusted | +| gorm.go:19:12:19:20 | untrusted | +| gorm.go:20:11:20:19 | untrusted | +| gorm.go:21:11:21:19 | untrusted | +| gorm.go:22:12:22:20 | untrusted | +| gorm.go:23:11:23:19 | untrusted | diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.go b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.go new file mode 100644 index 00000000..f6389e9d --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.go @@ -0,0 +1,25 @@ +package gormtest + +import ( + "github.com/jinzhu/gorm" +) + +func getUntrustedString() string { + return "trouble" +} + +func main() { + + db := gorm.DB{} + untrusted := getUntrustedString() + db.Where(untrusted) + db.Not(untrusted) + db.Order(untrusted) + db.Or(untrusted) + db.Select(untrusted) + db.Table(untrusted) + db.Group(untrusted) + db.Having(untrusted) + db.Joins(untrusted) + +} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.ql b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.ql new file mode 100644 index 00000000..7b56fd97 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/gorm.ql @@ -0,0 +1,4 @@ +import go + +from SQL::QueryString qs +select qs diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/github.com/jinzhu/gorm/stub.go b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/github.com/jinzhu/gorm/stub.go new file mode 100644 index 00000000..04def438 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/github.com/jinzhu/gorm/stub.go @@ -0,0 +1,698 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/jinzhu/gorm, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/jinzhu/gorm (exports: DB; functions: ) + +// Package gorm is a stub of github.com/jinzhu/gorm, generated by depstubber. +package gorm + +import ( + context "context" + sql "database/sql" + reflect "reflect" + sync "sync" + time "time" +) + +type Association struct { + Error error +} + +func (_ *Association) Append(_ ...interface{}) *Association { + return nil +} + +func (_ *Association) Clear() *Association { + return nil +} + +func (_ *Association) Count() int { + return 0 +} + +func (_ *Association) Delete(_ ...interface{}) *Association { + return nil +} + +func (_ *Association) Find(_ interface{}) *Association { + return nil +} + +func (_ *Association) Replace(_ ...interface{}) *Association { + return nil +} + +type Callback struct{} + +func (_ *Callback) Create() *CallbackProcessor { + return nil +} + +func (_ *Callback) Delete() *CallbackProcessor { + return nil +} + +func (_ *Callback) Query() *CallbackProcessor { + return nil +} + +func (_ *Callback) RowQuery() *CallbackProcessor { + return nil +} + +func (_ *Callback) Update() *CallbackProcessor { + return nil +} + +type CallbackProcessor struct{} + +func (_ *CallbackProcessor) After(_ string) *CallbackProcessor { + return nil +} + +func (_ *CallbackProcessor) Before(_ string) *CallbackProcessor { + return nil +} + +func (_ *CallbackProcessor) Get(_ string) func(*Scope) { + return nil +} + +func (_ *CallbackProcessor) Register(_ string, _ func(*Scope)) {} + +func (_ *CallbackProcessor) Remove(_ string) {} + +func (_ *CallbackProcessor) Replace(_ string, _ func(*Scope)) {} + +type DB struct { + RWMutex sync.RWMutex + Value interface{} + Error error + RowsAffected int64 +} + +func (_ *DB) AddError(_ error) error { + return nil +} + +func (_ *DB) AddForeignKey(_ string, _ string, _ string, _ string) *DB { + return nil +} + +func (_ *DB) AddIndex(_ string, _ ...string) *DB { + return nil +} + +func (_ *DB) AddUniqueIndex(_ string, _ ...string) *DB { + return nil +} + +func (_ *DB) Assign(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Association(_ string) *Association { + return nil +} + +func (_ *DB) Attrs(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) AutoMigrate(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Begin() *DB { + return nil +} + +func (_ *DB) BeginTx(_ context.Context, _ *sql.TxOptions) *DB { + return nil +} + +func (_ *DB) BlockGlobalUpdate(_ bool) *DB { + return nil +} + +func (_ *DB) Callback() *Callback { + return nil +} + +func (_ *DB) Close() error { + return nil +} + +func (_ *DB) Commit() *DB { + return nil +} + +func (_ *DB) CommonDB() SQLCommon { + return nil +} + +func (_ *DB) Count(_ interface{}) *DB { + return nil +} + +func (_ *DB) Create(_ interface{}) *DB { + return nil +} + +func (_ *DB) CreateTable(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) DB() *sql.DB { + return nil +} + +func (_ *DB) Debug() *DB { + return nil +} + +func (_ *DB) Delete(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Dialect() Dialect { + return nil +} + +func (_ *DB) DropColumn(_ string) *DB { + return nil +} + +func (_ *DB) DropTable(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) DropTableIfExists(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Exec(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Find(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) First(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrCreate(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrInit(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *DB) GetErrors() []error { + return nil +} + +func (_ *DB) Group(_ string) *DB { + return nil +} + +func (_ *DB) HasBlockGlobalUpdate() bool { + return false +} + +func (_ *DB) HasTable(_ interface{}) bool { + return false +} + +func (_ *DB) Having(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) InstantSet(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Joins(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Last(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Limit(_ interface{}) *DB { + return nil +} + +func (_ *DB) Lock() {} + +func (_ *DB) LogMode(_ bool) *DB { + return nil +} + +func (_ *DB) Model(_ interface{}) *DB { + return nil +} + +func (_ *DB) ModifyColumn(_ string, _ string) *DB { + return nil +} + +func (_ *DB) New() *DB { + return nil +} + +func (_ *DB) NewRecord(_ interface{}) bool { + return false +} + +func (_ *DB) NewScope(_ interface{}) *Scope { + return nil +} + +func (_ *DB) Not(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Offset(_ interface{}) *DB { + return nil +} + +func (_ *DB) Omit(_ ...string) *DB { + return nil +} + +func (_ *DB) Or(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Order(_ interface{}, _ ...bool) *DB { + return nil +} + +func (_ *DB) Pluck(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Preload(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Preloads(_ interface{}) *DB { + return nil +} + +func (_ *DB) QueryExpr() *SqlExpr { + return nil +} + +func (_ *DB) RLock() {} + +func (_ *DB) RLocker() sync.Locker { + return nil +} + +func (_ *DB) RUnlock() {} + +func (_ *DB) Raw(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) RecordNotFound() bool { + return false +} + +func (_ *DB) Related(_ interface{}, _ ...string) *DB { + return nil +} + +func (_ *DB) RemoveForeignKey(_ string, _ string) *DB { + return nil +} + +func (_ *DB) RemoveIndex(_ string) *DB { + return nil +} + +func (_ *DB) Rollback() *DB { + return nil +} + +func (_ *DB) RollbackUnlessCommitted() *DB { + return nil +} + +func (_ *DB) Row() *sql.Row { + return nil +} + +func (_ *DB) Rows() (*sql.Rows, error) { + return nil, nil +} + +func (_ *DB) Save(_ interface{}) *DB { + return nil +} + +func (_ *DB) Scan(_ interface{}) *DB { + return nil +} + +func (_ *DB) ScanRows(_ *sql.Rows, _ interface{}) error { + return nil +} + +func (_ *DB) Scopes(_ ...func(*DB) *DB) *DB { + return nil +} + +func (_ *DB) Select(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Set(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) SetJoinTableHandler(_ interface{}, _ string, _ JoinTableHandlerInterface) {} + +func (_ *DB) SetLogger(_ interface{}) {} + +func (_ *DB) SetNowFuncOverride(_ func() time.Time) *DB { + return nil +} + +func (_ *DB) SingularTable(_ bool) {} + +func (_ *DB) SubQuery() *SqlExpr { + return nil +} + +func (_ *DB) Table(_ string) *DB { + return nil +} + +func (_ *DB) Take(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Transaction(_ func(*DB) error) error { + return nil +} + +func (_ *DB) Unlock() {} + +func (_ *DB) Unscoped() *DB { + return nil +} + +func (_ *DB) Update(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumn(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumns(_ interface{}) *DB { + return nil +} + +func (_ *DB) Updates(_ interface{}, _ ...bool) *DB { + return nil +} + +func (_ *DB) Where(_ interface{}, _ ...interface{}) *DB { + return nil +} + +type Dialect interface { + BindVar(_ int) string + BuildKeyName(_ string, _ string, _ ...string) string + CurrentDatabase() string + DataTypeOf(_ *StructField) string + DefaultValueStr() string + GetName() string + HasColumn(_ string, _ string) bool + HasForeignKey(_ string, _ string) bool + HasIndex(_ string, _ string) bool + HasTable(_ string) bool + LastInsertIDOutputInterstitial(_ string, _ string, _ []string) string + LastInsertIDReturningSuffix(_ string, _ string) string + LimitAndOffsetSQL(_ interface{}, _ interface{}) (string, error) + ModifyColumn(_ string, _ string, _ string) error + NormalizeIndexAndColumn(_ string, _ string) (string, string) + Quote(_ string) string + RemoveIndex(_ string, _ string) error + SelectFromDummyTable() string + SetDB(_ SQLCommon) +} + +type Field struct { + StructField *StructField + IsBlank bool + Field reflect.Value +} + +func (_ Field) TagSettingsDelete(_ string) {} + +func (_ Field) TagSettingsGet(_ string) (string, bool) { + return "", false +} + +func (_ Field) TagSettingsSet(_ string, _ string) {} + +func (_ *Field) Set(_ interface{}) error { + return nil +} + +type JoinTableForeignKey struct { + DBName string + AssociationDBName string +} + +type JoinTableHandlerInterface interface { + Add(_ JoinTableHandlerInterface, _ *DB, _ interface{}, _ interface{}) error + Delete(_ JoinTableHandlerInterface, _ *DB, _ ...interface{}) error + DestinationForeignKeys() []JoinTableForeignKey + JoinWith(_ JoinTableHandlerInterface, _ *DB, _ interface{}) *DB + Setup(_ *Relationship, _ string, _ reflect.Type, _ reflect.Type) + SourceForeignKeys() []JoinTableForeignKey + Table(_ *DB) string +} + +type ModelStruct struct { + PrimaryFields []*StructField + StructFields []*StructField + ModelType reflect.Type +} + +func (_ *ModelStruct) TableName(_ *DB) string { + return "" +} + +type Relationship struct { + Kind string + PolymorphicType string + PolymorphicDBName string + PolymorphicValue string + ForeignFieldNames []string + ForeignDBNames []string + AssociationForeignFieldNames []string + AssociationForeignDBNames []string + JoinTableHandler JoinTableHandlerInterface +} + +type SQLCommon interface { + Exec(_ string, _ ...interface{}) (sql.Result, error) + Prepare(_ string) (*sql.Stmt, error) + Query(_ string, _ ...interface{}) (*sql.Rows, error) + QueryRow(_ string, _ ...interface{}) *sql.Row +} + +type Scope struct { + Search interface{} + Value interface{} + SQL string + SQLVars []interface{} +} + +func (_ *Scope) AddToVars(_ interface{}) string { + return "" +} + +func (_ *Scope) Begin() *Scope { + return nil +} + +func (_ *Scope) CallMethod(_ string) {} + +func (_ *Scope) CombinedConditionSql() string { + return "" +} + +func (_ *Scope) CommitOrRollback() *Scope { + return nil +} + +func (_ *Scope) DB() *DB { + return nil +} + +func (_ *Scope) Dialect() Dialect { + return nil +} + +func (_ *Scope) Err(_ error) error { + return nil +} + +func (_ *Scope) Exec() *Scope { + return nil +} + +func (_ *Scope) FieldByName(_ string) (*Field, bool) { + return nil, false +} + +func (_ *Scope) Fields() []*Field { + return nil +} + +func (_ *Scope) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *Scope) GetModelStruct() *ModelStruct { + return nil +} + +func (_ *Scope) GetStructFields() []*StructField { + return nil +} + +func (_ *Scope) HasColumn(_ string) bool { + return false +} + +func (_ *Scope) HasError() bool { + return false +} + +func (_ *Scope) IndirectValue() reflect.Value { + return reflect.Value{} +} + +func (_ *Scope) InstanceGet(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *Scope) InstanceID() string { + return "" +} + +func (_ *Scope) InstanceSet(_ string, _ interface{}) *Scope { + return nil +} + +func (_ *Scope) Log(_ ...interface{}) {} + +func (_ *Scope) New(_ interface{}) *Scope { + return nil +} + +func (_ *Scope) NewDB() *DB { + return nil +} + +func (_ *Scope) OmitAttrs() []string { + return nil +} + +func (_ *Scope) PrimaryField() *Field { + return nil +} + +func (_ *Scope) PrimaryFields() []*Field { + return nil +} + +func (_ *Scope) PrimaryKey() string { + return "" +} + +func (_ *Scope) PrimaryKeyValue() interface{} { + return nil +} + +func (_ *Scope) PrimaryKeyZero() bool { + return false +} + +func (_ *Scope) Quote(_ string) string { + return "" +} + +func (_ *Scope) QuotedTableName() string { + return "" +} + +func (_ *Scope) Raw(_ string) *Scope { + return nil +} + +func (_ *Scope) SQLDB() SQLCommon { + return nil +} + +func (_ *Scope) SelectAttrs() []string { + return nil +} + +func (_ *Scope) Set(_ string, _ interface{}) *Scope { + return nil +} + +func (_ *Scope) SetColumn(_ interface{}, _ interface{}) error { + return nil +} + +func (_ *Scope) SkipLeft() {} + +func (_ *Scope) TableName() string { + return "" +} + +type SqlExpr struct{} + +type StructField struct { + DBName string + Name string + Names []string + IsPrimaryKey bool + IsNormal bool + IsIgnored bool + IsScanner bool + HasDefaultValue bool + Tag reflect.StructTag + TagSettings map[string]string + Struct reflect.StructField + IsForeignKey bool + Relationship *Relationship +} + +func (_ *StructField) TagSettingsDelete(_ string) {} + +func (_ *StructField) TagSettingsGet(_ string) (string, bool) { + return "", false +} + +func (_ *StructField) TagSettingsSet(_ string, _ string) {} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/modules.txt new file mode 100644 index 00000000..13d6ffb9 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Gorm/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/jinzhu/gorm v1.9.15 +## explicit +github.com/jinzhu/gorm diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/go.mod b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/go.mod new file mode 100644 index 00000000..991ce37d --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/go.mod @@ -0,0 +1,7 @@ +module gormtest + +go 1.14 + +require ( + github.com/jmoiron/sqlx v1.2.0 +) diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.expected b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.expected new file mode 100644 index 00000000..0540a78f --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.expected @@ -0,0 +1,12 @@ +| sqlx.go:15:17:15:25 | untrusted | +| sqlx.go:16:14:16:22 | untrusted | +| sqlx.go:17:14:17:22 | untrusted | +| sqlx.go:18:12:18:20 | untrusted | +| sqlx.go:19:15:19:23 | untrusted | +| sqlx.go:20:16:20:24 | untrusted | +| sqlx.go:23:17:23:25 | untrusted | +| sqlx.go:24:14:24:22 | untrusted | +| sqlx.go:25:14:25:22 | untrusted | +| sqlx.go:26:12:26:20 | untrusted | +| sqlx.go:27:15:27:23 | untrusted | +| sqlx.go:28:16:28:24 | untrusted | diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.go b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.go new file mode 100644 index 00000000..edc29c4d --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.go @@ -0,0 +1,30 @@ +package sqlxtest + +import ( + "github.com/jmoiron/sqlx" +) + +func getUntrustedString() string { + return "trouble" +} + +func main() { + + db := sqlx.DB{} + untrusted := getUntrustedString() + db.Select(nil, untrusted) + db.Get(nil, untrusted) + db.MustExec(untrusted) + db.Queryx(untrusted) + db.NamedExec(untrusted, nil) + db.NamedQuery(untrusted, nil) + + tx := sqlx.Tx{} + tx.Select(nil, untrusted) + tx.Get(nil, untrusted) + tx.MustExec(untrusted) + tx.Queryx(untrusted) + tx.NamedExec(untrusted, nil) + tx.NamedQuery(untrusted, nil) + +} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.ql b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.ql new file mode 100644 index 00000000..7b56fd97 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/sqlx.ql @@ -0,0 +1,4 @@ +import go + +from SQL::QueryString qs +select qs diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/github.com/jmoiron/sqlx/stub.go b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/github.com/jmoiron/sqlx/stub.go new file mode 100644 index 00000000..e4a15105 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/github.com/jmoiron/sqlx/stub.go @@ -0,0 +1,581 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for github.com/jmoiron/sqlx, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: github.com/jmoiron/sqlx (exports: DB,Tx; functions: ) + +// Package sqlx is a stub of github.com/jmoiron/sqlx, generated by depstubber. +package sqlx + +import ( + context "context" + sql "database/sql" + driver "database/sql/driver" + time "time" +) + +type DB struct { + DB *sql.DB + Mapper interface{} +} + +func (_ DB) Begin() (*sql.Tx, error) { + return nil, nil +} + +func (_ DB) BeginTx(_ context.Context, _ *sql.TxOptions) (*sql.Tx, error) { + return nil, nil +} + +func (_ DB) Close() error { + return nil +} + +func (_ DB) Conn(_ context.Context) (*sql.Conn, error) { + return nil, nil +} + +func (_ DB) Driver() driver.Driver { + return nil +} + +func (_ DB) Exec(_ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ DB) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ DB) Ping() error { + return nil +} + +func (_ DB) PingContext(_ context.Context) error { + return nil +} + +func (_ DB) Prepare(_ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ DB) PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ DB) Query(_ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ DB) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ DB) QueryRow(_ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ DB) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ DB) SetConnMaxLifetime(_ time.Duration) {} + +func (_ DB) SetMaxIdleConns(_ int) {} + +func (_ DB) SetMaxOpenConns(_ int) {} + +func (_ DB) Stats() sql.DBStats { + return sql.DBStats{} +} + +func (_ *DB) BeginTxx(_ context.Context, _ *sql.TxOptions) (*Tx, error) { + return nil, nil +} + +func (_ *DB) Beginx() (*Tx, error) { + return nil, nil +} + +func (_ *DB) BindNamed(_ string, _ interface{}) (string, []interface{}, error) { + return "", nil, nil +} + +func (_ *DB) DriverName() string { + return "" +} + +func (_ *DB) Get(_ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *DB) GetContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *DB) MapperFunc(_ func(string) string) {} + +func (_ *DB) MustBegin() *Tx { + return nil +} + +func (_ *DB) MustBeginTx(_ context.Context, _ *sql.TxOptions) *Tx { + return nil +} + +func (_ *DB) MustExec(_ string, _ ...interface{}) sql.Result { + return nil +} + +func (_ *DB) MustExecContext(_ context.Context, _ string, _ ...interface{}) sql.Result { + return nil +} + +func (_ *DB) NamedExec(_ string, _ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DB) NamedExecContext(_ context.Context, _ string, _ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *DB) NamedQuery(_ string, _ interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *DB) NamedQueryContext(_ context.Context, _ string, _ interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *DB) PrepareNamed(_ string) (*NamedStmt, error) { + return nil, nil +} + +func (_ *DB) PrepareNamedContext(_ context.Context, _ string) (*NamedStmt, error) { + return nil, nil +} + +func (_ *DB) Preparex(_ string) (*Stmt, error) { + return nil, nil +} + +func (_ *DB) PreparexContext(_ context.Context, _ string) (*Stmt, error) { + return nil, nil +} + +func (_ *DB) QueryRowx(_ string, _ ...interface{}) *Row { + return nil +} + +func (_ *DB) QueryRowxContext(_ context.Context, _ string, _ ...interface{}) *Row { + return nil +} + +func (_ *DB) Queryx(_ string, _ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *DB) QueryxContext(_ context.Context, _ string, _ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *DB) Rebind(_ string) string { + return "" +} + +func (_ *DB) Select(_ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *DB) SelectContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *DB) Unsafe() *DB { + return nil +} + +type NamedStmt struct { + Params []string + QueryString string + Stmt *Stmt +} + +func (_ *NamedStmt) Close() error { + return nil +} + +func (_ *NamedStmt) Exec(_ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *NamedStmt) ExecContext(_ context.Context, _ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *NamedStmt) Get(_ interface{}, _ interface{}) error { + return nil +} + +func (_ *NamedStmt) GetContext(_ context.Context, _ interface{}, _ interface{}) error { + return nil +} + +func (_ *NamedStmt) MustExec(_ interface{}) sql.Result { + return nil +} + +func (_ *NamedStmt) MustExecContext(_ context.Context, _ interface{}) sql.Result { + return nil +} + +func (_ *NamedStmt) Query(_ interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ *NamedStmt) QueryContext(_ context.Context, _ interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ *NamedStmt) QueryRow(_ interface{}) *Row { + return nil +} + +func (_ *NamedStmt) QueryRowContext(_ context.Context, _ interface{}) *Row { + return nil +} + +func (_ *NamedStmt) QueryRowx(_ interface{}) *Row { + return nil +} + +func (_ *NamedStmt) QueryRowxContext(_ context.Context, _ interface{}) *Row { + return nil +} + +func (_ *NamedStmt) Queryx(_ interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *NamedStmt) QueryxContext(_ context.Context, _ interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *NamedStmt) Select(_ interface{}, _ interface{}) error { + return nil +} + +func (_ *NamedStmt) SelectContext(_ context.Context, _ interface{}, _ interface{}) error { + return nil +} + +func (_ *NamedStmt) Unsafe() *NamedStmt { + return nil +} + +type Row struct { + Mapper interface{} +} + +func (_ *Row) ColumnTypes() ([]*sql.ColumnType, error) { + return nil, nil +} + +func (_ *Row) Columns() ([]string, error) { + return nil, nil +} + +func (_ *Row) Err() error { + return nil +} + +func (_ *Row) MapScan(_ map[string]interface{}) error { + return nil +} + +func (_ *Row) Scan(_ ...interface{}) error { + return nil +} + +func (_ *Row) SliceScan() ([]interface{}, error) { + return nil, nil +} + +func (_ *Row) StructScan(_ interface{}) error { + return nil +} + +type Rows struct { + Rows *sql.Rows + Mapper interface{} +} + +func (_ Rows) Close() error { + return nil +} + +func (_ Rows) ColumnTypes() ([]*sql.ColumnType, error) { + return nil, nil +} + +func (_ Rows) Columns() ([]string, error) { + return nil, nil +} + +func (_ Rows) Err() error { + return nil +} + +func (_ Rows) Next() bool { + return false +} + +func (_ Rows) NextResultSet() bool { + return false +} + +func (_ Rows) Scan(_ ...interface{}) error { + return nil +} + +func (_ *Rows) MapScan(_ map[string]interface{}) error { + return nil +} + +func (_ *Rows) SliceScan() ([]interface{}, error) { + return nil, nil +} + +func (_ *Rows) StructScan(_ interface{}) error { + return nil +} + +type Stmt struct { + Stmt *sql.Stmt + Mapper interface{} +} + +func (_ Stmt) Close() error { + return nil +} + +func (_ Stmt) Exec(_ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Stmt) ExecContext(_ context.Context, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Stmt) Query(_ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Stmt) QueryContext(_ context.Context, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Stmt) QueryRow(_ ...interface{}) *sql.Row { + return nil +} + +func (_ Stmt) QueryRowContext(_ context.Context, _ ...interface{}) *sql.Row { + return nil +} + +func (_ *Stmt) Get(_ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Stmt) GetContext(_ context.Context, _ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Stmt) MustExec(_ ...interface{}) sql.Result { + return nil +} + +func (_ *Stmt) MustExecContext(_ context.Context, _ ...interface{}) sql.Result { + return nil +} + +func (_ *Stmt) QueryRowx(_ ...interface{}) *Row { + return nil +} + +func (_ *Stmt) QueryRowxContext(_ context.Context, _ ...interface{}) *Row { + return nil +} + +func (_ *Stmt) Queryx(_ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *Stmt) QueryxContext(_ context.Context, _ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *Stmt) Select(_ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Stmt) SelectContext(_ context.Context, _ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Stmt) Unsafe() *Stmt { + return nil +} + +type Tx struct { + Tx *sql.Tx + Mapper interface{} +} + +func (_ Tx) Commit() error { + return nil +} + +func (_ Tx) Exec(_ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Tx) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ Tx) Prepare(_ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ Tx) PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) { + return nil, nil +} + +func (_ Tx) Query(_ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Tx) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) { + return nil, nil +} + +func (_ Tx) QueryRow(_ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ Tx) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row { + return nil +} + +func (_ Tx) Rollback() error { + return nil +} + +func (_ Tx) Stmt(_ *sql.Stmt) *sql.Stmt { + return nil +} + +func (_ Tx) StmtContext(_ context.Context, _ *sql.Stmt) *sql.Stmt { + return nil +} + +func (_ *Tx) BindNamed(_ string, _ interface{}) (string, []interface{}, error) { + return "", nil, nil +} + +func (_ *Tx) DriverName() string { + return "" +} + +func (_ *Tx) Get(_ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *Tx) GetContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *Tx) MustExec(_ string, _ ...interface{}) sql.Result { + return nil +} + +func (_ *Tx) MustExecContext(_ context.Context, _ string, _ ...interface{}) sql.Result { + return nil +} + +func (_ *Tx) NamedExec(_ string, _ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *Tx) NamedExecContext(_ context.Context, _ string, _ interface{}) (sql.Result, error) { + return nil, nil +} + +func (_ *Tx) NamedQuery(_ string, _ interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *Tx) NamedStmt(_ *NamedStmt) *NamedStmt { + return nil +} + +func (_ *Tx) NamedStmtContext(_ context.Context, _ *NamedStmt) *NamedStmt { + return nil +} + +func (_ *Tx) PrepareNamed(_ string) (*NamedStmt, error) { + return nil, nil +} + +func (_ *Tx) PrepareNamedContext(_ context.Context, _ string) (*NamedStmt, error) { + return nil, nil +} + +func (_ *Tx) Preparex(_ string) (*Stmt, error) { + return nil, nil +} + +func (_ *Tx) PreparexContext(_ context.Context, _ string) (*Stmt, error) { + return nil, nil +} + +func (_ *Tx) QueryRowx(_ string, _ ...interface{}) *Row { + return nil +} + +func (_ *Tx) QueryRowxContext(_ context.Context, _ string, _ ...interface{}) *Row { + return nil +} + +func (_ *Tx) Queryx(_ string, _ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *Tx) QueryxContext(_ context.Context, _ string, _ ...interface{}) (*Rows, error) { + return nil, nil +} + +func (_ *Tx) Rebind(_ string) string { + return "" +} + +func (_ *Tx) Select(_ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *Tx) SelectContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error { + return nil +} + +func (_ *Tx) Stmtx(_ interface{}) *Stmt { + return nil +} + +func (_ *Tx) StmtxContext(_ context.Context, _ interface{}) *Stmt { + return nil +} + +func (_ *Tx) Unsafe() *Tx { + return nil +} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/modules.txt new file mode 100644 index 00000000..4b671d77 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/Sqlx/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/jmoiron/sqlx v1.2.0 +## explicit +github.com/jmoiron/sqlx