diff --git a/change-notes/1.24/analysis-go.md b/change-notes/1.24/analysis-go.md index 36544e9d7ac..1a9dc1caeb8 100644 --- a/change-notes/1.24/analysis-go.md +++ b/change-notes/1.24/analysis-go.md @@ -4,7 +4,7 @@ * Alert suppression can now be done with single-line block comments (`/* ... */`) as well as line comments (`// ...`). * Analysis of flow through fields has been improved. -* More sources of untrusted input are modelled, which may lead to more results from the security queries. +* More sources of untrusted input as well as vulnerable sinks are modelled, which may lead to more results from the security queries. ## New queries diff --git a/ql/src/semmle/go/frameworks/SQL.qll b/ql/src/semmle/go/frameworks/SQL.qll index 5a61aa7804a..ea09a434637 100644 --- a/ql/src/semmle/go/frameworks/SQL.qll +++ b/ql/src/semmle/go/frameworks/SQL.qll @@ -71,5 +71,97 @@ module SQL { ) } } + + /** A string that might identify package `go-pg/pg` or a specific version of it. */ + bindingset[result] + private string gopg() { + result.regexpMatch("github.com/go-pg/pg(/v[^/]+)?") + } + + /** A string that might identify package `go-pg/pg/orm` or a specific version of it. */ + bindingset[result] + private string gopgorm() { + result.regexpMatch("github.com/go-pg/pg(/v[^/]+)?/orm") + } + + /** + * A string argument to an API of `go-pg/pg` that is directly interpreted as SQL without + * taking syntactic structure into account. + */ + private class PgQueryString extends Range { + PgQueryString() { + exists(Function f, int arg | + f.hasQualifiedName(gopg(), "Q") and + arg = 0 + or + exists(string tp, string m | f.(Method).hasQualifiedName(gopg(), tp, m) | + (tp = "Conn" or tp = "DB" or tp = "Tx") and + ( + m = "FormatQuery" and arg = 1 + or + m = "Prepare" and arg = 0 + ) + ) + | + this = f.getACall().getArgument(arg) + ) + } + } + + /** + * A string argument to an API of `go-pg/pg/orm` that is directly interpreted as SQL without + * taking syntactic structure into account. + */ + private class PgOrmQueryString extends Range { + PgOrmQueryString() { + exists(Function f, int arg | + f.hasQualifiedName(gopgorm(), "Q") and + arg = 0 + or + exists(string tp, string m | + f.(Method).hasQualifiedName(gopgorm(), tp, m) + | + tp = "Query" and + ( + m = "ColumnExpr" or + m = "For" or + m = "Having" or + m = "Where" or + m = "WhereIn" or + m = "WhereInMulti" or + m = "WhereOr" + ) and + arg = 0 + or + tp = "Query" and + m = "FormatQuery" and + arg = 1 + ) + | + this = f.getACall().getArgument(arg) + ) + } + } + + /** A taint model for various methods on the struct `Formatter` of `go-pg/pg/orm`. */ + private class PgOrmFormatterFunction extends TaintTracking::FunctionModel, Method { + FunctionInput i; + FunctionOutput o; + + PgOrmFormatterFunction() { + exists(string m | this.hasQualifiedName(gopgorm(), "Formatter", m) | + // func (f Formatter) Append(dst []byte, src string, params ...interface{}) []byte + // func (f Formatter) AppendBytes(dst, src []byte, params ...interface{}) []byte + // func (f Formatter) FormatQuery(dst []byte, query string, params ...interface{}) []byte + (m = "Append" or m = "AppendBytes" or m = "FormatQuery") and + i.isParameter(1) and + (o.isParameter(0) or o.isResult()) + ) + } + + override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) { + inp = i and outp = o + } + } } }