Skip to content

Commit 419c929

Browse files
cschoenduve-splunkCosmin Cojocar
authored andcommitted
G107 - SSRF (#236)
* Initial SSRF Rule * Added Selector evaluation * Added source code tests * Fixed spacing issues * Fixed Spacingv2 * Removed resty test
1 parent 63b25c1 commit 419c929

File tree

6 files changed

+149
-4
lines changed

6 files changed

+149
-4
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
4141
- G104: Audit errors not checked
4242
- G105: Audit the use of math/big.Int.Exp
4343
- G106: Audit the use of ssh.InsecureIgnoreHostKey
44+
- G107: Url provided to HTTP request as taint input
4445
- G201: SQL query construction using format string
4546
- G202: SQL query construction using string concatenation
4647
- G203: Use of unescaped data in HTML templates
4748
- G204: Audit use of command execution
4849
- G301: Poor file permissions used when creating a directory
49-
- G302: Poor file permisions used with chmod
50+
- G302: Poor file permissions used with chmod
5051
- G303: Creating tempfile using a predictable path
5152
- G304: File path provided as taint input
5253
- G305: File traversal when extracting zip archive
@@ -79,7 +80,7 @@ that are not considered build artifacts by the compiler (so test files).
7980
As with all automated detection tools there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment.
8081

8182
The annotation causes gosec to stop processing any further nodes within the
82-
AST so can apply to a whole block or more granularly to a single expression.
83+
AST so can apply to a whole block or more granularly to a single expression.
8384

8485
```go
8586

@@ -178,7 +179,7 @@ You can build the docker image as follows:
178179
make image
179180
```
180181

181-
You can run the `gosec` tool in a container against your local Go project. You just have to mount the project in the
182+
You can run the `gosec` tool in a container against your local Go project. You just have to mount the project in the
182183
`GOPATH` of the container:
183184

184185
```

cmd/tlsconfig/tlsconfig.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type goTLSConfiguration struct {
6262

6363
// getTLSConfFromURL retrieves the json containing the TLS configurations from the specified URL.
6464
func getTLSConfFromURL(url string) (*ServerSideTLSJson, error) {
65-
r, err := http.Get(url)
65+
r, err := http.Get(url) // #nosec G107
6666
if err != nil {
6767
return nil, err
6868
}

rules/rulelist.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func Generate(filters ...RuleFilter) RuleList {
6565
{"G104", "Audit errors not checked", NewNoErrorCheck},
6666
{"G105", "Audit the use of big.Exp function", NewUsingBigExp},
6767
{"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
68+
{"G107", "Url provided to HTTP request as taint input", NewSSRFCheck},
6869

6970
// injection
7071
{"G201", "SQL query construction using format string", NewSQLStrFormat},

rules/rules_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ var _ = Describe("gosec rules", func() {
7171
runner("G106", testutils.SampleCodeG106)
7272
})
7373

74+
It("should detect ssrf via http requests with variable url", func() {
75+
runner("G107", testutils.SampleCodeG107)
76+
})
77+
7478
It("should detect sql injection via format strings", func() {
7579
runner("G201", testutils.SampleCodeG201)
7680
})

rules/ssrf.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package rules
2+
3+
import (
4+
"go/ast"
5+
"go/types"
6+
7+
"github.com/securego/gosec"
8+
)
9+
10+
type ssrf struct {
11+
gosec.MetaData
12+
gosec.CallList
13+
}
14+
15+
// ID returns the identifier for this rule
16+
func (r *ssrf) ID() string {
17+
return r.MetaData.ID
18+
}
19+
20+
// ResolveVar tries to resolve the first argument of a call expression
21+
// The first argument is the url
22+
func (r *ssrf) ResolveVar(n *ast.CallExpr, c *gosec.Context) bool {
23+
if len(n.Args) > 0 {
24+
arg := n.Args[0]
25+
if ident, ok := arg.(*ast.Ident); ok {
26+
obj := c.Info.ObjectOf(ident)
27+
if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) {
28+
return true
29+
}
30+
}
31+
}
32+
return false
33+
}
34+
35+
// Match inspects AST nodes to determine if certain net/http methods are called with variable input
36+
func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
37+
// Call expression is using http package directly
38+
if node := r.ContainsCallExpr(n, c); node != nil {
39+
if r.ResolveVar(node, c) {
40+
return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
41+
}
42+
}
43+
// Look at the last selector identity for methods matching net/http's
44+
if node, ok := n.(*ast.CallExpr); ok {
45+
if selExpr, ok := node.Fun.(*ast.SelectorExpr); ok {
46+
// Pull last selector's identity name and compare to net/http methods
47+
if r.Contains("net/http", selExpr.Sel.Name) {
48+
if r.ResolveVar(node, c) {
49+
return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
50+
}
51+
}
52+
}
53+
}
54+
return nil, nil
55+
}
56+
57+
// NewSSRFCheck detects cases where HTTP requests are sent
58+
func NewSSRFCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
59+
rule := &ssrf{
60+
CallList: gosec.NewCallList(),
61+
MetaData: gosec.MetaData{
62+
ID: id,
63+
What: "Potential HTTP request made with variable url",
64+
Severity: gosec.Medium,
65+
Confidence: gosec.Medium,
66+
},
67+
}
68+
rule.AddAll("net/http", "Do", "Get", "Head", "Post", "PostForm", "RoundTrip")
69+
return rule, []ast.Node{(*ast.CallExpr)(nil)}
70+
}

testutils/source.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,75 @@ import (
192192
func main() {
193193
_ = ssh.InsecureIgnoreHostKey()
194194
}`, 1}}
195+
196+
// SampleCodeG107 - SSRF via http requests with variable url
197+
SampleCodeG107 = []CodeSample{{`
198+
package main
199+
import (
200+
"net/http"
201+
"io/ioutil"
202+
"fmt"
203+
"os"
204+
)
205+
func main() {
206+
url := os.Getenv("tainted_url")
207+
resp, err := http.Get(url)
208+
if err != nil {
209+
panic(err)
210+
}
211+
defer resp.Body.Close()
212+
body, err := ioutil.ReadAll(resp.Body)
213+
if err != nil {
214+
panic(err)
215+
}
216+
fmt.Printf("%s", body)
217+
}`, 1}, {`
218+
package main
219+
220+
import (
221+
"fmt"
222+
"net/http"
223+
)
224+
const url = "http://127.0.0.1"
225+
func main() {
226+
resp, err := http.Get(url)
227+
if err != nil {
228+
fmt.Println(err)
229+
}
230+
fmt.Println(resp.Status)
231+
}`, 0}, {`
232+
package main
233+
234+
import (
235+
"net/http"
236+
"fmt"
237+
"os"
238+
"strconv"
239+
)
240+
241+
type httpWrapper struct {
242+
DesiredCode string
243+
}
244+
245+
func (c *httpWrapper) Get(url string) (*http.Response, error) {
246+
return http.Get(url)
247+
}
248+
249+
func main() {
250+
code := os.Getenv("STATUS_CODE")
251+
var url = os.Getenv("URL")
252+
client := httpWrapper{code}
253+
resp1, err1 := client.Get(url)
254+
if err1 != nil {
255+
fmt.Println(err1)
256+
os.Exit(1)
257+
}
258+
if strconv.Itoa(resp1.StatusCode) == client.DesiredCode {
259+
fmt.Println("True")
260+
} else {
261+
fmt.Println("False")
262+
}
263+
}`, 2}}
195264
// SampleCodeG201 - SQL injection via format string
196265
SampleCodeG201 = []CodeSample{
197266
{`

0 commit comments

Comments
 (0)