Static Taint Analysis for Go

Following my previous blog post on Static Single Assignment(SSA), I've been working on a static taint analysis package for Go programs. It serves as a building block for other analysis like SQL injection, XSS, and more. This is how it works.

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.


Example Go code with SQL injection vulnerability
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.


Path

Trace Attack Path
hover to see vulnerability flow

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.


Creating sources and sinks for taint analysis
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.


Analyzing taint analysis results
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.


Install sqli CLI tool
$ go install github.com/picatz/taint/cmd/sqli@latest

To use the sqli CLI tool, change to the directory of the target Go program, and run the tool.


Run sqli analysis
$ 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.


Continue Reading