-
Notifications
You must be signed in to change notification settings - Fork 3
Expand SQLi sinks #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,7 @@ package injection | |||||
|
|
||||||
| import ( | ||||||
| "fmt" | ||||||
| "go/types" | ||||||
| "strings" | ||||||
|
|
||||||
| "github.com/picatz/taint" | ||||||
|
|
@@ -15,13 +16,17 @@ import ( | |||||
| // userControlledValues are the sources of user controlled values that | ||||||
| // can be tained and end up in a SQL query. | ||||||
| var userControlledValues = taint.NewSources( | ||||||
| // Function (and method) calls | ||||||
| // Function (and method) calls that are user controlled | ||||||
| // over the netork. These are all taken into account as | ||||||
| // part of *net/http.Request, but are listed here for | ||||||
| // demonstration purposes. | ||||||
| // | ||||||
| // "(net/url.Values).Get", | ||||||
| // "(*net/url.URL).Query", | ||||||
| // "(*net/url.URL).Redacted", | ||||||
| // "(*net/url.URL).EscapedFragment", | ||||||
| // "(*net/url.Userinfo).Username", | ||||||
| // "(*net/url.Userinfo).Passworde", | ||||||
| // "(*net/url.Userinfo).Password", | ||||||
| // "(*net/url.Userinfo).String", | ||||||
| // "(*net/http.Request).FormFile", | ||||||
| // "(*net/http.Request).FormValue", | ||||||
|
|
@@ -39,7 +44,7 @@ var userControlledValues = taint.NewSources( | |||||
| // | ||||||
| // TODO: add more, consider pointer variants and specific fields on types | ||||||
| // TODO: consider support for protobuf defined *Request types... | ||||||
| // TODO: consider supprot for gRPC request metadata (HTTP2 headers) | ||||||
| // TODO: consider support for gRPC request metadata (HTTP2 headers) | ||||||
| // TODO: consider support for msgpack-rpc? | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -68,6 +73,139 @@ var injectableSQLMethods = taint.NewSinks( | |||||
| "(*github.com/jinzhu/gorm.DB).Raw", | ||||||
| "(*github.com/jinzhu/gorm.DB).Exec", | ||||||
| "(*github.com/jinzhu/gorm.DB).Order", | ||||||
| // GORM v2 | ||||||
|
||||||
| "(*gorm.io/gorm.DB).Where", | ||||||
| "(*gorm.io/gorm.DB).Or", | ||||||
| "(*gorm.io/gorm.DB).Not", | ||||||
| "(*gorm.io/gorm.DB).Group", | ||||||
| "(*gorm.io/gorm.DB).Having", | ||||||
| "(*gorm.io/gorm.DB).Joins", | ||||||
| "(*gorm.io/gorm.DB).Select", | ||||||
| "(*gorm.io/gorm.DB).Distinct", | ||||||
| "(*gorm.io/gorm.DB).Pluck", | ||||||
| "(*gorm.io/gorm.DB).Raw", | ||||||
| "(*gorm.io/gorm.DB).Exec", | ||||||
| "(*gorm.io/gorm.DB).Order", | ||||||
| // Alternative GORM v2 import path | ||||||
| "(*github.com/go-gorm/gorm.DB).Where", | ||||||
| "(*github.com/go-gorm/gorm.DB).Or", | ||||||
| "(*github.com/go-gorm/gorm.DB).Not", | ||||||
| "(*github.com/go-gorm/gorm.DB).Group", | ||||||
| "(*github.com/go-gorm/gorm.DB).Having", | ||||||
| "(*github.com/go-gorm/gorm.DB).Joins", | ||||||
| "(*github.com/go-gorm/gorm.DB).Select", | ||||||
| "(*github.com/go-gorm/gorm.DB).Distinct", | ||||||
| "(*github.com/go-gorm/gorm.DB).Pluck", | ||||||
| "(*github.com/go-gorm/gorm.DB).Raw", | ||||||
| "(*github.com/go-gorm/gorm.DB).Exec", | ||||||
| "(*github.com/go-gorm/gorm.DB).Order", | ||||||
| // sqlx | ||||||
| "(*github.com/jmoiron/sqlx.DB).Queryx", | ||||||
| "(*github.com/jmoiron/sqlx.DB).QueryRowx", | ||||||
| "(*github.com/jmoiron/sqlx.DB).Query", | ||||||
| "(*github.com/jmoiron/sqlx.DB).QueryRow", | ||||||
| "(*github.com/jmoiron/sqlx.DB).Select", | ||||||
| "(*github.com/jmoiron/sqlx.DB).Get", | ||||||
| "(*github.com/jmoiron/sqlx.DB).Exec", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).Queryx", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).QueryRowx", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).Query", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).QueryRow", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).Select", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).Get", | ||||||
| "(*github.com/jmoiron/sqlx.Tx).Exec", | ||||||
| // xorm | ||||||
| "(*xorm.io/xorm.Engine).Query", | ||||||
| "(*xorm.io/xorm.Engine).Exec", | ||||||
| "(*xorm.io/xorm.Engine).QueryString", | ||||||
| "(*xorm.io/xorm.Engine).QueryInterface", | ||||||
| "(*xorm.io/xorm.Engine).SQL", | ||||||
| "(*xorm.io/xorm.Engine).Where", | ||||||
| "(*xorm.io/xorm.Engine).And", | ||||||
| "(*xorm.io/xorm.Engine).Or", | ||||||
| "(*xorm.io/xorm.Engine).Alias", | ||||||
| "(*xorm.io/xorm.Engine).NotIn", | ||||||
| "(*xorm.io/xorm.Engine).In", | ||||||
| "(*xorm.io/xorm.Engine).Select", | ||||||
| "(*xorm.io/xorm.Engine).SetExpr", | ||||||
| "(*xorm.io/xorm.Engine).OrderBy", | ||||||
| "(*xorm.io/xorm.Engine).Having", | ||||||
| "(*xorm.io/xorm.Engine).GroupBy", | ||||||
| "(*xorm.io/xorm.Engine).Join", | ||||||
| "(*xorm.io/xorm.Session).Query", | ||||||
| "(*xorm.io/xorm.Session).Exec", | ||||||
| "(*xorm.io/xorm.Session).QueryString", | ||||||
| "(*xorm.io/xorm.Session).QueryInterface", | ||||||
| "(*xorm.io/xorm.Session).SQL", | ||||||
| "(*xorm.io/xorm.Session).Where", | ||||||
| "(*xorm.io/xorm.Session).And", | ||||||
| "(*xorm.io/xorm.Session).Or", | ||||||
| "(*xorm.io/xorm.Session).Alias", | ||||||
| "(*xorm.io/xorm.Session).NotIn", | ||||||
| "(*xorm.io/xorm.Session).In", | ||||||
| "(*xorm.io/xorm.Session).Select", | ||||||
| "(*xorm.io/xorm.Session).SetExpr", | ||||||
| "(*xorm.io/xorm.Session).OrderBy", | ||||||
| "(*xorm.io/xorm.Session).Having", | ||||||
| "(*xorm.io/xorm.Session).GroupBy", | ||||||
| "(*xorm.io/xorm.Session).Join", | ||||||
| // Alternative xorm import path | ||||||
| "(*github.com/go-xorm/xorm.Engine).Query", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Exec", | ||||||
| "(*github.com/go-xorm/xorm.Engine).QueryString", | ||||||
| "(*github.com/go-xorm/xorm.Engine).QueryInterface", | ||||||
| "(*github.com/go-xorm/xorm.Engine).SQL", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Where", | ||||||
| "(*github.com/go-xorm/xorm.Engine).And", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Or", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Alias", | ||||||
| "(*github.com/go-xorm/xorm.Engine).NotIn", | ||||||
| "(*github.com/go-xorm/xorm.Engine).In", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Select", | ||||||
| "(*github.com/go-xorm/xorm.Engine).SetExpr", | ||||||
| "(*github.com/go-xorm/xorm.Engine).OrderBy", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Having", | ||||||
| "(*github.com/go-xorm/xorm.Engine).GroupBy", | ||||||
| "(*github.com/go-xorm/xorm.Engine).Join", | ||||||
| "(*github.com/go-xorm/xorm.Session).Query", | ||||||
| "(*github.com/go-xorm/xorm.Session).Exec", | ||||||
| "(*github.com/go-xorm/xorm.Session).QueryString", | ||||||
| "(*github.com/go-xorm/xorm.Session).QueryInterface", | ||||||
| "(*github.com/go-xorm/xorm.Session).SQL", | ||||||
| "(*github.com/go-xorm/xorm.Session).Where", | ||||||
| "(*github.com/go-xorm/xorm.Session).And", | ||||||
| "(*github.com/go-xorm/xorm.Session).Or", | ||||||
| "(*github.com/go-xorm/xorm.Session).Alias", | ||||||
| "(*github.com/go-xorm/xorm.Session).NotIn", | ||||||
| "(*github.com/go-xorm/xorm.Session).In", | ||||||
| "(*github.com/go-xorm/xorm.Session).Select", | ||||||
| "(*github.com/go-xorm/xorm.Session).SetExpr", | ||||||
| "(*github.com/go-xorm/xorm.Session).OrderBy", | ||||||
| "(*github.com/go-xorm/xorm.Session).Having", | ||||||
| "(*github.com/go-xorm/xorm.Session).GroupBy", | ||||||
| "(*github.com/go-xorm/xorm.Session).Join", | ||||||
| // go-pg | ||||||
| "(*github.com/go-pg/pg.DB).Query", | ||||||
| "(*github.com/go-pg/pg.DB).QueryOne", | ||||||
| "(*github.com/go-pg/pg.DB).Exec", | ||||||
| "(*github.com/go-pg/pg.DB).ExecOne", | ||||||
| "(*github.com/go-pg/pg.Tx).Query", | ||||||
| "(*github.com/go-pg/pg.Tx).QueryOne", | ||||||
| "(*github.com/go-pg/pg.Tx).Exec", | ||||||
| "(*github.com/go-pg/pg.Tx).ExecOne", | ||||||
| // rqlite | ||||||
| "(*github.com/rqlite/gorqlite.Connection).Query", | ||||||
| "(*github.com/rqlite/gorqlite.Connection).QueryOne", | ||||||
| "(*github.com/rqlite/gorqlite.Connection).Write", | ||||||
| "(*github.com/rqlite/gorqlite.Connection).WriteOne", | ||||||
| "(*github.com/raindog308/gorqlite.Connection).Query", | ||||||
| "(*github.com/raindog308/gorqlite.Connection).QueryOne", | ||||||
| "(*github.com/raindog308/gorqlite.Connection).Write", | ||||||
| "(*github.com/raindog308/gorqlite.Connection).WriteOne", | ||||||
| // Squirrel | ||||||
| "github.com/Masterminds/squirrel.Expr", | ||||||
| "gopkg.in/Masterminds/squirrel.v1.Expr", | ||||||
| "github.com/lann/squirrel.Expr", | ||||||
| // | ||||||
| // TODO: add more, consider (non-)pointer variants? | ||||||
| ) | ||||||
|
|
@@ -83,27 +221,50 @@ var Analyzer = &analysis.Analyzer{ | |||||
|
|
||||||
| // imports returns true if the package imports any of the given packages. | ||||||
| func imports(pass *analysis.Pass, pkgs ...string) bool { | ||||||
| var imported bool | ||||||
| for _, imp := range pass.Pkg.Imports() { | ||||||
| visited := make(map[*types.Package]bool) | ||||||
| var walk func(*types.Package) bool | ||||||
| walk = func(p *types.Package) bool { | ||||||
| if visited[p] { | ||||||
| return false | ||||||
| } | ||||||
| visited[p] = true | ||||||
| for _, pkg := range pkgs { | ||||||
| if strings.HasSuffix(imp.Path(), pkg) { | ||||||
| imported = true | ||||||
| break | ||||||
| if strings.HasSuffix(p.Path(), pkg) { | ||||||
|
||||||
| if strings.HasSuffix(p.Path(), pkg) { | |
| if p.Path() == pkg { |
Copilot
AI
Jul 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Relying on string suffix matching may be brittle; consider inspecting the method's name or signature directly (e.g., using Signature().Name()) to accurately detect Context variants like QueryContext, ExecContext, etc.
| if strings.HasSuffix(queryEdge.Site.Value().Call.Value.String(), "Context") { | |
| if strings.Contains(queryEdge.Site.Common().Signature().Name(), "Context") { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package squirrel | ||
|
|
||
| type Sqlizer interface{} | ||
|
|
||
| func Expr(sql string, args ...interface{}) Sqlizer { return nil } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package gorm | ||
|
|
||
| type Model struct{} | ||
|
|
||
| type DB struct{} | ||
|
|
||
| type Config struct{} | ||
|
|
||
| type Dialector interface{} | ||
|
|
||
| func Open(d Dialector, config *Config) (*DB, error) { return nil, nil } | ||
|
|
||
| func (s *DB) Where(query interface{}, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Or(query interface{}, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Not(query interface{}, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Group(query string) *DB { return nil } | ||
| func (s *DB) Having(query interface{}, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Joins(query string, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Select(query interface{}, args ...interface{}) *DB { return nil } | ||
| func (s *DB) Distinct(args ...interface{}) *DB { return nil } | ||
| func (s *DB) Pluck(column string, value interface{}) *DB { return nil } | ||
| func (s *DB) Raw(sql string, values ...interface{}) *DB { return nil } | ||
| func (s *DB) Exec(sql string, values ...interface{}) *DB { return nil } | ||
| func (s *DB) Order(value interface{}) *DB { return nil } | ||
| func (s *DB) Find(dest interface{}, conds ...interface{}) *DB { return nil } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix spelling of 'netork' to 'network'.