Repository: securego/gosec (language: go)

Issue:
Add a rule for Slowloris Attack 
### Summary 

Add rule which should detect if `ReadHeaderTimeout` is configured in the `http.Server`.  This should prevent a Slowloris Attack. See more details at https://medium.com/a-journey-with-go/go-understand-and-mitigate-slowloris-attack-711c1b1403f6. 




Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 732919d172..337477db8e 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,7 @@ directory you can supply `./...` as the input argument.
 - G109: Potential Integer overflow made by strconv.Atoi result conversion to int16/32
 - G110: Potential DoS vulnerability via decompression bomb
 - G111: Potential directory traversal
+- G112: Potential slowloris attack
 - G201: SQL query construction using format string
 - G202: SQL query construction using string concatenation
 - G203: Use of unescaped data in HTML templates
diff --git a/cwe/data.go b/cwe/data.go
index 8789ddd63a..0e377b96a6 100644
--- a/cwe/data.go
+++ b/cwe/data.go
@@ -89,6 +89,11 @@ var (
 			Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.",
 			Name:        "Insecure Temporary File",
 		},
+		{
+			ID:          "400",
+			Description: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.",
+			Name:        "Uncontrolled Resource Consumption",
+		},
 		{
 			ID:          "409",
 			Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.",
diff --git a/issue.go b/issue.go
index ecda70737e..832ab26d72 100644
--- a/issue.go
+++ b/issue.go
@@ -64,6 +64,7 @@ var ruleToCWE = map[string]string{
 	"G109": "190",
 	"G110": "409",
 	"G111": "22",
+	"G112": "400",
 	"G201": "89",
 	"G202": "89",
 	"G203": "79",
diff --git a/rules/rulelist.go b/rules/rulelist.go
index bbe72fafbd..01c0e51942 100644
--- a/rules/rulelist.go
+++ b/rules/rulelist.go
@@ -74,6 +74,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
 		{"G109", "Converting strconv.Atoi result to int32/int16", NewIntegerOverflowCheck},
 		{"G110", "Detect io.Copy instead of io.CopyN when decompression", NewDecompressionBombCheck},
 		{"G111", "Detect http.Dir('/') as a potential risk", NewDirectoryTraversal},
+		{"G112", "Detect ReadHeaderTimeout not configured as a potential risk", NewSlowloris},
 
 		// injection
 		{"G201", "SQL query construction using format string", NewSQLStrFormat},
diff --git a/rules/slowloris.go b/rules/slowloris.go
new file mode 100644
index 0000000000..0d8a8a88ad
--- /dev/null
+++ b/rules/slowloris.go
@@ -0,0 +1,70 @@
+// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rules
+
+import (
+	"go/ast"
+
+	"github.com/securego/gosec/v2"
+)
+
+type slowloris struct {
+	gosec.MetaData
+}
+
+func (r *slowloris) ID() string {
+	return r.MetaData.ID
+}
+
+func containsReadHeaderTimeout(node *ast.CompositeLit) bool {
+	if node == nil {
+		return false
+	}
+	for _, elt := range node.Elts {
+		if kv, ok := elt.(*ast.KeyValueExpr); ok {
+			if ident, ok := kv.Key.(*ast.Ident); ok {
+				if ident.Name == "ReadHeaderTimeout" {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+func (r *slowloris) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) {
+	switch node := n.(type) {
+	case *ast.CompositeLit:
+		actualType := ctx.Info.TypeOf(node.Type)
+		if actualType != nil && actualType.String() == "net/http.Server" {
+			if !containsReadHeaderTimeout(node) {
+				return gosec.NewIssue(ctx, node, r.ID(), r.What, r.Severity, r.Confidence), nil
+			}
+		}
+	}
+	return nil, nil
+}
+
+// NewSlowloris attempts to find the http.Server struct and check if the ReadHeaderTimeout is configured.
+func NewSlowloris(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
+	return &slowloris{
+		MetaData: gosec.MetaData{
+			ID:         id,
+			What:       "Potential Slowloris Attack because ReadHeaderTimeout is not configured in the http.Server",
+			Confidence: gosec.Low,
+			Severity:   gosec.Medium,
+		},
+	}, []ast.Node{(*ast.CompositeLit)(nil)}
+}


Test patch:
diff --git a/report/formatter_test.go b/report/formatter_test.go
index b4b78f6064..66496d8ed7 100644
--- a/report/formatter_test.go
+++ b/report/formatter_test.go
@@ -277,7 +277,7 @@ var _ = Describe("Formatter", func() {
 	Context("When using different report formats", func() {
 		grules := []string{
 			"G101", "G102", "G103", "G104", "G106", "G107", "G109",
-			"G110", "G111", "G201", "G202", "G203", "G204", "G301",
+			"G110", "G111", "G112", "G201", "G202", "G203", "G204", "G301",
 			"G302", "G303", "G304", "G305", "G401", "G402", "G403",
 			"G404", "G501", "G502", "G503", "G504", "G505",
 		}
diff --git a/rules/rules_test.go b/rules/rules_test.go
index d987303574..d10bd3d5a5 100644
--- a/rules/rules_test.go
+++ b/rules/rules_test.go
@@ -94,6 +94,10 @@ var _ = Describe("gosec rules", func() {
 			runner("G111", testutils.SampleCodeG111)
 		})
 
+		It("should detect potential slowloris attack", func() {
+			runner("G112", testutils.SampleCodeG112)
+		})
+
 		It("should detect sql injection via format strings", func() {
 			runner("G201", testutils.SampleCodeG201)
 		})
diff --git a/testutils/source.go b/testutils/source.go
index e6459502a3..ebb9fd0a78 100644
--- a/testutils/source.go
+++ b/testutils/source.go
@@ -1005,6 +1005,53 @@ func HelloServer(w http.ResponseWriter, r *http.Request) {
 }`}, 1, gosec.NewConfig()},
 	}
 
+	// SampleCodeG112 - potential slowloris attack
+	SampleCodeG112 = []CodeSample{
+		{[]string{`
+		package main
+
+		import (
+			"fmt"
+			"net/http"
+		)
+		
+		func main() {
+			http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
+			})
+			err := (&http.Server{
+				Addr: ":1234",
+			}).ListenAndServe()
+			if err != nil {
+				panic(err)
+			}
+		}
+		`}, 1, gosec.NewConfig()},
+		{[]string{`
+		package main
+
+		import (
+			"fmt"
+			"time"
+			"net/http"
+		)
+		
+		func main() {
+			http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+				fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
+			})
+			server := &http.Server{
+				Addr:              ":1234",
+				ReadHeaderTimeout: 3 * time.Second,
+			}
+			err := server.ListenAndServe()
+			if err != nil {
+				panic(err)
+			}
+		}
+		`}, 0, gosec.NewConfig()},
+	}
+
 	// SampleCodeG201 - SQL injection via format string
 	SampleCodeG201 = []CodeSample{
 		{[]string{`