Skip to content

Commit 48bbf96

Browse files
authored
Adds directory traversal for Http.Dir("/")
1 parent 26f10e0 commit 48bbf96

File tree

7 files changed

+98
-4
lines changed

7 files changed

+98
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ directory you can supply `./...` as the input argument.
143143
- G108: Profiling endpoint automatically exposed on /debug/pprof
144144
- G109: Potential Integer overflow made by strconv.Atoi result conversion to int16/32
145145
- G110: Potential DoS vulnerability via decompression bomb
146+
- G111: Potential directory traversal
146147
- G201: SQL query construction using format string
147148
- G202: SQL query construction using string concatenation
148149
- G203: Use of unescaped data in HTML templates

issue.go

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var ruleToCWE = map[string]string{
6363
"G108": "200",
6464
"G109": "190",
6565
"G110": "409",
66+
"G111": "22",
6667
"G201": "89",
6768
"G202": "89",
6869
"G203": "79",

report/formatter_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,10 @@ var _ = Describe("Formatter", func() {
276276
})
277277
Context("When using different report formats", func() {
278278
grules := []string{
279-
"G101", "G102", "G103", "G104", "G106",
280-
"G107", "G109", "G110", "G201", "G202", "G203", "G204",
281-
"G301", "G302", "G303", "G304", "G305", "G401", "G402",
282-
"G403", "G404", "G501", "G502", "G503", "G504", "G505",
279+
"G101", "G102", "G103", "G104", "G106", "G107", "G109",
280+
"G110", "G111", "G201", "G202", "G203", "G204", "G301",
281+
"G302", "G303", "G304", "G305", "G401", "G402", "G403",
282+
"G404", "G501", "G502", "G503", "G504", "G505",
283283
}
284284

285285
It("csv formatted report should contain the CWE mapping", func() {

rules/directory-traversal.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package rules
2+
3+
import (
4+
"go/ast"
5+
"regexp"
6+
7+
"github.com/securego/gosec/v2"
8+
)
9+
10+
type traversal struct {
11+
pattern *regexp.Regexp
12+
gosec.MetaData
13+
}
14+
15+
func (r *traversal) ID() string {
16+
return r.MetaData.ID
17+
}
18+
19+
func (r *traversal) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) {
20+
switch node := n.(type) {
21+
case *ast.CallExpr:
22+
return r.matchCallExpr(node, ctx)
23+
}
24+
return nil, nil
25+
}
26+
27+
func (r *traversal) matchCallExpr(assign *ast.CallExpr, ctx *gosec.Context) (*gosec.Issue, error) {
28+
for _, i := range assign.Args {
29+
if basiclit, ok1 := i.(*ast.BasicLit); ok1 {
30+
if fun, ok2 := assign.Fun.(*ast.SelectorExpr); ok2 {
31+
if x, ok3 := fun.X.(*ast.Ident); ok3 {
32+
string := x.Name + "." + fun.Sel.Name + "(" + basiclit.Value + ")"
33+
if r.pattern.MatchString(string) {
34+
return gosec.NewIssue(ctx, assign, r.ID(), r.What, r.Severity, r.Confidence), nil
35+
}
36+
}
37+
}
38+
}
39+
}
40+
return nil, nil
41+
}
42+
43+
// NewDirectoryTraversal attempts to find the use of http.Dir("/")
44+
func NewDirectoryTraversal(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
45+
pattern := `http\.Dir\("\/"\)|http\.Dir\('\/'\)`
46+
if val, ok := conf["G101"]; ok {
47+
conf := val.(map[string]interface{})
48+
if configPattern, ok := conf["pattern"]; ok {
49+
if cfgPattern, ok := configPattern.(string); ok {
50+
pattern = cfgPattern
51+
}
52+
}
53+
}
54+
55+
return &traversal{
56+
pattern: regexp.MustCompile(pattern),
57+
MetaData: gosec.MetaData{
58+
ID: id,
59+
What: "Potential directory traversal",
60+
Confidence: gosec.Medium,
61+
Severity: gosec.Medium,
62+
},
63+
}, []ast.Node{(*ast.CallExpr)(nil)}
64+
}

rules/rulelist.go

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
7373
{"G108", "Profiling endpoint is automatically exposed", NewPprofCheck},
7474
{"G109", "Converting strconv.Atoi result to int32/int16", NewIntegerOverflowCheck},
7575
{"G110", "Detect io.Copy instead of io.CopyN when decompression", NewDecompressionBombCheck},
76+
{"G111", "Detect http.Dir('/') as a potential risk", NewDirectoryTraversal},
7677

7778
// injection
7879
{"G201", "SQL query construction using format string", NewSQLStrFormat},

rules/rules_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ var _ = Describe("gosec rules", func() {
9090
runner("G110", testutils.SampleCodeG110)
9191
})
9292

93+
It("should detect potential directory traversal", func() {
94+
runner("G111", testutils.SampleCodeG111)
95+
})
96+
9397
It("should detect sql injection via format strings", func() {
9498
runner("G201", testutils.SampleCodeG201)
9599
})

testutils/source.go

+23
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,29 @@ func main() {
982982
}`}, 0, gosec.NewConfig()},
983983
}
984984

985+
// SampleCodeG111 - potential directory traversal
986+
SampleCodeG111 = []CodeSample{
987+
{[]string{`
988+
package main
989+
990+
import (
991+
"fmt"
992+
"log"
993+
"net/http"
994+
"os"
995+
)
996+
997+
func main() {
998+
http.Handle("/bad/", http.StripPrefix("/bad/", http.FileServer(http.Dir("/"))))
999+
http.HandleFunc("/", HelloServer)
1000+
log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil))
1001+
}
1002+
1003+
func HelloServer(w http.ResponseWriter, r *http.Request) {
1004+
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
1005+
}`}, 1, gosec.NewConfig()},
1006+
}
1007+
9851008
// SampleCodeG201 - SQL injection via format string
9861009
SampleCodeG201 = []CodeSample{
9871010
{[]string{`

0 commit comments

Comments
 (0)