|
| 1 | +package python |
| 2 | + |
| 3 | +import ( |
| 4 | + "regexp" |
| 5 | + "strings" |
| 6 | + |
| 7 | + sitter "github.com/smacker/go-tree-sitter" |
| 8 | + "globstar.dev/analysis" |
| 9 | +) |
| 10 | + |
| 11 | +var AwsLambdaTaintedSqlString *analysis.Analyzer = &analysis.Analyzer{ |
| 12 | + Name: "aws-lambda-tainted-sql-string", |
| 13 | + Language: analysis.LangPy, |
| 14 | + Description: "Detected user input directly used to build a SQL string, which can lead to SQL injection vulnerabilities. This could allow an attacker to steal or modify database contents. Use parameterized queries or an ORM like Sequelize to protect against injection attacks.", |
| 15 | + Category: analysis.CategorySecurity, |
| 16 | + Severity: analysis.SeverityWarning, |
| 17 | + Run: checkAwsLambdaTaintedSqlString, |
| 18 | +} |
| 19 | + |
| 20 | +func checkAwsLambdaTaintedSqlString(pass *analysis.Pass) (interface{}, error) { |
| 21 | + eventVarMap := make(map[string]bool) |
| 22 | + sqlStringVarMap := make(map[string]bool) |
| 23 | + |
| 24 | + // store variables for event subscripts and sql strings |
| 25 | + analysis.Preorder(pass, func(node *sitter.Node) { |
| 26 | + if node.Type() != "assignment" { |
| 27 | + return |
| 28 | + } |
| 29 | + |
| 30 | + leftNode := node.ChildByFieldName("left") |
| 31 | + rightNode := node.ChildByFieldName("right") |
| 32 | + |
| 33 | + if rightNode == nil { |
| 34 | + return |
| 35 | + } |
| 36 | + |
| 37 | + if isEventSubscript(rightNode, pass.FileContext.Source) { |
| 38 | + eventVarMap[leftNode.Content(pass.FileContext.Source)] = true |
| 39 | + } |
| 40 | + |
| 41 | + if isSqlString(rightNode, pass.FileContext.Source) { |
| 42 | + sqlStringVarMap[leftNode.Content(pass.FileContext.Source)] = true |
| 43 | + } |
| 44 | + }) |
| 45 | + |
| 46 | + // detect tainted sql strings in assignments |
| 47 | + analysis.Preorder(pass, func(node *sitter.Node) { |
| 48 | + if node.Type() != "assignment" { |
| 49 | + return |
| 50 | + } |
| 51 | + |
| 52 | + rightNode := node.ChildByFieldName("right") |
| 53 | + |
| 54 | + if rightNode == nil { |
| 55 | + return |
| 56 | + } |
| 57 | + |
| 58 | + if isTaintedSqlString(rightNode, pass.FileContext.Source, sqlStringVarMap, eventVarMap) { |
| 59 | + pass.Report(pass, node, "Detect `event` tainted SQL string - Potential SQL injection vulnerabilities") |
| 60 | + } |
| 61 | + }) |
| 62 | + |
| 63 | + return nil, nil |
| 64 | +} |
| 65 | + |
| 66 | +func isTaintedSqlString(node *sitter.Node, source []byte, sqlStringVarMap, eventVarMap map[string]bool) bool { |
| 67 | + switch node.Type() { |
| 68 | + case "binary_operator": |
| 69 | + leftNode := node.ChildByFieldName("left") |
| 70 | + rightNode := node.ChildByFieldName("right") |
| 71 | + |
| 72 | + if leftNode.Type() == "string" { |
| 73 | + if rightNode.Type() == "tuple" { |
| 74 | + tupleArgNodes := getNamedChildren(rightNode, 0) |
| 75 | + for _, tupArg := range tupleArgNodes { |
| 76 | + if tupArg.Type() == "identifier" { |
| 77 | + if eventVarMap[tupArg.Content(source)] { |
| 78 | + return true |
| 79 | + } |
| 80 | + } else if tupArg.Type() == "subscript" { |
| 81 | + return isEventSubscript(tupArg, source) |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + } else if rightNode.Type() == "identifier" { |
| 86 | + return eventVarMap[rightNode.Content(source)] |
| 87 | + } |
| 88 | + |
| 89 | + } else if leftNode.Type() == "identifier" { |
| 90 | + if !sqlStringVarMap[leftNode.Content(source)] { |
| 91 | + return false |
| 92 | + } |
| 93 | + |
| 94 | + if rightNode.Type() == "tuple" { |
| 95 | + tupleArgNodes := getNamedChildren(rightNode, 0) |
| 96 | + |
| 97 | + for _, tupArg := range tupleArgNodes { |
| 98 | + if tupArg.Type() == "identifier" { |
| 99 | + if eventVarMap[tupArg.Content(source)] { |
| 100 | + return true |
| 101 | + } |
| 102 | + } else if tupArg.Type() == "subscript" { |
| 103 | + return isEventSubscript(tupArg, source) |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + case "call": |
| 110 | + funcNode := node.ChildByFieldName("function") |
| 111 | + if funcNode.Type() != "attribute" { |
| 112 | + return false |
| 113 | + } |
| 114 | + |
| 115 | + funcObjNode := funcNode.ChildByFieldName("object") |
| 116 | + funcAttrNode := funcNode.ChildByFieldName("attribute") |
| 117 | + |
| 118 | + if funcAttrNode.Type() != "identifier" { |
| 119 | + return false |
| 120 | + } |
| 121 | + |
| 122 | + if funcAttrNode.Content(source) != "format" { |
| 123 | + return false |
| 124 | + } |
| 125 | + |
| 126 | + argsNode := node.ChildByFieldName("arguments") |
| 127 | + argsList := getNamedChildren(argsNode, 0) |
| 128 | + |
| 129 | + if funcObjNode.Type() == "string" { |
| 130 | + for _, argVal := range argsList { |
| 131 | + if argVal.Type() == "identifier" { |
| 132 | + if eventVarMap[argVal.Content(source)] { |
| 133 | + return true |
| 134 | + } |
| 135 | + } else if argVal.Type() == "subscript" { |
| 136 | + return isEventSubscript(argVal, source) |
| 137 | + } |
| 138 | + } |
| 139 | + } else if funcObjNode.Type() == "identifier" { |
| 140 | + if !sqlStringVarMap[funcObjNode.Content(source)] { |
| 141 | + return false |
| 142 | + } |
| 143 | + |
| 144 | + for _, argVal := range argsList { |
| 145 | + if argVal.Type() == "identifier" { |
| 146 | + if eventVarMap[argVal.Content(source)] { |
| 147 | + return true |
| 148 | + } |
| 149 | + } else if argVal.Type() == "subscript" { |
| 150 | + return isEventSubscript(argVal, source) |
| 151 | + } |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + case "string": |
| 156 | + if node.Content(source)[0] != 'f' { |
| 157 | + return false |
| 158 | + } |
| 159 | + |
| 160 | + allStringChildren := getNamedChildren(node, 0) |
| 161 | + for _, strnode := range allStringChildren { |
| 162 | + if strnode.Type() == "interpolation" { |
| 163 | + exprNode := strnode.ChildByFieldName("expression") |
| 164 | + if isEventSubscript(exprNode, source) { |
| 165 | + return true |
| 166 | + } |
| 167 | + |
| 168 | + if eventVarMap[exprNode.Content(source)] { |
| 169 | + return true |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + return false |
| 176 | +} |
| 177 | + |
| 178 | +func isSqlString(node *sitter.Node, source []byte) bool { |
| 179 | + if node.Type() != "string" { |
| 180 | + return false |
| 181 | + } |
| 182 | + |
| 183 | + sqlPattern := regexp.MustCompile(`\s*(?i)(select|delete|insert|create|update|alter|drop)\b.*=`) |
| 184 | + return sqlPattern.MatchString(node.Content(source)) |
| 185 | +} |
| 186 | + |
| 187 | +func isEventSubscript(node *sitter.Node, source []byte) bool { |
| 188 | + if node.Type() != "subscript" { |
| 189 | + return false |
| 190 | + } |
| 191 | + |
| 192 | + valIdNode := node |
| 193 | + |
| 194 | + // when there are more than 1 subscript accesses, we need to go down the tree |
| 195 | + // to get to the identifier |
| 196 | + for valIdNode.Type() != "identifier" { |
| 197 | + valIdNode = valIdNode.ChildByFieldName("value") |
| 198 | + } |
| 199 | + |
| 200 | + eventIdentifier := valIdNode.Content(source) |
| 201 | + return strings.Contains(eventIdentifier, "event") |
| 202 | +} |
0 commit comments