Static Taint Analysis for Go
Taint Analysis
Taint analysis is a static program analysis technique that tracks the flow of data through a program to identify where "source" of (user controlled) data reaches a "sink", a place where the data is used in an unsafe way. For example, if a user controlled string is passed to a function that executes a SQL query, the string is a source of data that reaches a sink, the SQL query. This would result in a SQL injection vulnerability in the program.
func business(db *sql.DB, q string) { rows, _ := db.Query(q) // potential sql injection ... } func run() { db, _ := sql.Open("sqlite3", ":memory:") mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { business(db, r.URL.Query().Get("sql-query")) }) http.ListenAndServe(":8080", mux) }
Static single-assignment (SSA) provides the foundation for performing taint analysis. SSA is an intermediate representation that is generally easier to work with than the original program's abstract syntax tree (AST). SSA provides value graph of the program, where each node is a value, instruction, or both. You can read more about SSA in my previous blog post.
The SSA value graph is transformed to a function call graph, where each node is a function from a target program, and each edge is a call to that function. This represents the program's control flow graph, that can be used to identify data flow through the program. The data found in the program is represented with specific types, that can be used to find the flow form a source to a sink. All of the nodes between the source and sink make up a path that represents a potential vulnerability.
Source
Path
Hover over the Source • Path • Sink elements above to see the magic happen in the graph below!
Click and drag the nodes!
To use the taint analysis package, we need to first create a set of source and sink values, and then pass them to the taint.Check function.
sources := taint.NewSources( "*net/http.Request", ) sinks := taint.NewSinks( "(*database/sql.DB).Query", "(*database/sql.DB).QueryContext", "(*database/sql.DB).QueryRow", "(*database/sql.DB).QueryRowContext", "(*database/sql.Tx).Query", "(*database/sql.Tx).QueryContext", "(*database/sql.Tx).QueryRow", "(*database/sql.Tx).QueryRowContext", ) results, _ := taint.Check(cg, sources, sinks)
The returned results contains of slice of taint.Result values, which contain the path from the source to the sink. We can use
information from the path to understand the context of the vulnerability.
For example, if the SQL query is being executed using a prepared statement which would prevent
SQL injection. This can be done by checking the last node in the path, which is the sink,
and checking if the sink is a *ssa.Const value. Otherwise we can report a potential vulnerability.
for _, result := range results { queryEdge := result.Path[len(result.Path)-1] queryArgs := queryEdge.Site.Common().Args[1:] if strings.HasPrefix(queryEdge.Site.Value().Call.Value.String(), "Context") { queryArgs = queryArgs[1:] } query := queryArgs[0] if _, isConst := query.(*ssa.Const); !isConst { pass.Reportf(result.SinkValue.Pos(), "potential sql injection") } }
A full example of the taint analysis package used to detect SQL injection vulnerabilities is
available on GitHub here. This can be installed as the sqli CLI tool,
which can be used to detect SQL injection vulnerabilities in Go programs.
$ go install github.com/picatz/taint/cmd/sqli@latestTo use the sqli CLI tool, change to the directory of the target Go program, and run the tool.
$ sqli ./... The ./... pattern is a go/analysis pattern, which is used to specify the packages to analyze. This pattern is used to specify the current
directory and all subdirectories, which is useful for analyzing a large Go program. Other
patterns such as path@version can be found here.
The core of the taint analysis algorithm is the taint.Check function, which takes a call graph of a target program and a set of source and sink values. It first checks for the reachability of the source, then performs "backwards" data flow analysis to determine if the source can reach the sink. This is
implemented in a few hundred lines of code using three internal functions, checkPath, checkSSAValue, and checkSSAInstruction.
If you have any questions, issues, or feedback, please feel free to open an issue on the repository. The ultimate goal for the future would be to donate it to the go/analysis collection of packages to be used for other taint analysis problems by the community.