-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsql2.go
164 lines (146 loc) · 3.98 KB
/
sql2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Copyright 2018 Terence Tarvis. All rights reserved.
//
package main
import (
"go/ast"
//"fmt"
"regexp"
)
func init() {
register("sqlBackup",
"this is a backup test for the SQL injection test",
sql2Check,
funcDecl)
}
// May want to consider the possibility that someone gives db or Conn an alias
// Consider also checking if the sql package was imported
var expressions = []string{
"^((Conn.|db.))*(Exec)|(Query)$",
}
var regexps []*regexp.Regexp;
func loadRegexps() {
for _, expression := range expressions {
rexp := regexp.MustCompile(expression);
regexps = append(regexps, rexp);
}
}
func isSQLCall(funcName string) bool {
// move this somewhere else to be faster and only performed once
loadRegexps();
for _, re := range regexps {
if matches := re.MatchString(funcName); matches {
return true;
}
}
return false;
}
func checkTainted(expr *ast.Expr, tainted *map[string]bool) bool {
switch exp := (*expr).(type) {
case *ast.BinaryExpr:
x := exp.X;
y := exp.Y;
// yes this is recursive but how deeply nested to people actually write assignments?
return (checkTainted(&x, tainted) || checkTainted(&y, tainted));
case *ast.BasicLit:
// if it is a constant, it is not tainted
return false;
case *ast.Ident:
if(exp.Obj != nil) {
varName := exp.Obj.Name;
if _, isTainted := (*tainted)[varName]; isTainted {
return true;
}
}
}
return false;
}
func addTainted(exprs []ast.Expr, tainted *map[string]bool) {
for _, expr := range exprs {
if ident, ok := expr.(*ast.Ident); ok {
(*tainted)[ident.Obj.Name] = true;
}
}
}
func sql2Check(f *File, node ast.Node) {
// only run if the other SQL failed
// todo: consider replacing the entirety of the other checker
if(!SQLCheckFailed) {
return;
}
tainted := make(map[string]bool);
if fun, ok := node.(*ast.FuncDecl); ok {
// perform a sanity check
if(fun.Body == nil || len(fun.Body.List) < 1) {
return;
}
// get input parameter names and types
for _, field := range fun.Type.Params.List {
// add params to tainted list
if t, ok := field.Type.(*ast.Ident); ok {
if(t.Name == "string") {
for _, name := range field.Names {
tainted[name.Name] = true;
}
}
}
}
// get assignment statements and check if variables in assignments are tainted
for _, statement := range fun.Body.List {
if assign, ok := statement.(*ast.AssignStmt); ok {
for _, expr := range assign.Rhs {
switch exp := expr.(type) {
case *ast.BinaryExpr, *ast.Ident:
isTainted := checkTainted(&exp, &tainted);
if(isTainted) {
// add in variable name from lhs
// right now it is just adding in all the Lhs
addTainted(assign.Lhs, &tainted);
}
case *ast.BasicLit:
case *ast.CallExpr:
default:
// do nothing for now
// basic literals are safe
// call expressions might need to be reconsidered
}
}
// look for things that look like SQL query calls
// also for variables to map
for _, expr := range assign.Rhs {
if call, ok := expr.(*ast.CallExpr); ok {
funcName, err := getFullFuncName(call);
if err != nil {
// not sure what to do here
}
if(isSQLCall(funcName)) {
// check if arguments are tainted
for _, arg := range call.Args {
// todo: should checkTainted just use value arguments not references?
isTainted := checkTainted(&arg, &tainted);
if(isTainted) {
x := f.ASTString(expr);
f.Reportf(expr.Pos(), "audit tainted input to SQL query, %s", x);
}
}
}
}
}
}
// get things that are _probably_ sql exec calls
if exprStmt, ok := statement.(*ast.ExprStmt); ok {
if call, ok := exprStmt.X.(*ast.CallExpr); ok {
var funcName string;
funcName, err := getFullFuncName(call);
if err != nil {
funcName = getFuncName(call);
}
if(isSQLCall(funcName)) {
// extract parameters of the call
// check if they are tainted
}
}
}
}
}
return;
}