diff --git a/go/ql/src/experimental/CWE-770/DenialOfService.qhelp b/go/ql/src/experimental/CWE-770/DenialOfService.qhelp new file mode 100644 index 000000000000..b91f1f7e3b06 --- /dev/null +++ b/go/ql/src/experimental/CWE-770/DenialOfService.qhelp @@ -0,0 +1,32 @@ + + + + +

Using untrusted input to created with the built-in make function + could lead to excessive memory allocation and potentially cause the program to crash due + to running out of memory. This vulnerability could be exploited to perform a DoS attack by consuming all available server resources.

+
+ + +

Implement a maximum allowed value for creates a slice with the built-in make function to prevent excessively large allocations. + For instance, you could restrict it to a reasonable upper limit.

+
+ + +

In the following example snippet, the n field is user-controlled.

+

The server trusts that n has an acceptable value, however when using a maliciously large value, + it allocates a slice of n of strings before filling the slice with data.

+ + + +

One way to prevent this vulnerability is by implementing a maximum allowed value for the user-controlled input:

+ + +
+ + +
  • + OWASP: Denial of Service Cheat Sheet +
  • +
    +
    \ No newline at end of file diff --git a/go/ql/src/experimental/CWE-770/DenialOfService.ql b/go/ql/src/experimental/CWE-770/DenialOfService.ql new file mode 100644 index 000000000000..199cd0df5520 --- /dev/null +++ b/go/ql/src/experimental/CWE-770/DenialOfService.ql @@ -0,0 +1,59 @@ +/** + * @name Denial Of Service + * @description slices created with the built-in make function from user-controlled sources using a + * maliciously large value possibly leading to a denial of service. + * @kind path-problem + * @problem.severity error + * @security-severity 9 + * @precision high + * @id go/denial-of-service + * @tags security + * experimental + * external/cwe/cwe-770 + */ + +import go + +/** + * Holds if the guard `g` on its branch `branch` checks that `e` is not constant and is less than some other value. + */ +predicate denialOfServiceSanitizerGuard(DataFlow::Node g, Expr e, boolean branch) { + exists(DataFlow::Node lesser | + e = lesser.asExpr() and + g.(DataFlow::RelationalComparisonNode).leq(branch, lesser, _, _) and + not e.isConst() + ) +} + +/** + * Module for defining predicates and tracking taint flow related to denial of service issues. + */ +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(Function f, DataFlow::CallNode cn | cn = f.getACall() | + f.hasQualifiedName("strconv", ["Atoi", "ParseInt", "ParseUint", "ParseFloat"]) and + node1 = cn.getArgument(0) and + node2 = cn.getResult(0) + ) + } + + predicate isBarrier(DataFlow::Node node) { + node = DataFlow::BarrierGuard::getABarrierNode() + } + + predicate isSink(DataFlow::Node sink) { sink = Builtin::make().getACall().getArgument(0) } +} + +/** + * Tracks taint flow for reasoning about denial of service, where source is + * user-controlled and unchecked. + */ +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink, source, sink, "This variable might be leading to denial of service." diff --git a/go/ql/src/experimental/CWE-770/DenialOfServiceBad.go b/go/ql/src/experimental/CWE-770/DenialOfServiceBad.go new file mode 100644 index 000000000000..b3a57d4f5e10 --- /dev/null +++ b/go/ql/src/experimental/CWE-770/DenialOfServiceBad.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func OutOfMemoryBad(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + + queryStr := query.Get("n") + collectionSize, err := strconv.Atoi(queryStr) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + result := make([]string, collectionSize) + for i := 0; i < collectionSize; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} diff --git a/go/ql/src/experimental/CWE-770/DenialOfServiceGood.go b/go/ql/src/experimental/CWE-770/DenialOfServiceGood.go new file mode 100644 index 000000000000..761501064f6e --- /dev/null +++ b/go/ql/src/experimental/CWE-770/DenialOfServiceGood.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func OutOfMemoryGood(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + MaxValue := 6 + queryStr := query.Get("n") + collectionSize, err := strconv.Atoi(queryStr) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if collectionSize < 0 || collectionSize > MaxValue { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + result := make([]string, collectionSize) + for i := 0; i < collectionSize; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} diff --git a/go/ql/test/experimental/CWE-770/DenialOfService.expected b/go/ql/test/experimental/CWE-770/DenialOfService.expected new file mode 100644 index 000000000000..4a2ae9d6646c --- /dev/null +++ b/go/ql/test/experimental/CWE-770/DenialOfService.expected @@ -0,0 +1,18 @@ +edges +| DenialOfServiceBad.go:11:12:11:16 | selection of URL | DenialOfServiceBad.go:11:12:11:24 | call to Query | provenance | | +| DenialOfServiceBad.go:11:12:11:24 | call to Query | DenialOfServiceBad.go:13:15:13:20 | source | provenance | | +| DenialOfServiceBad.go:13:15:13:20 | source | DenialOfServiceBad.go:13:15:13:29 | call to Get | provenance | | +| DenialOfServiceBad.go:13:15:13:29 | call to Get | DenialOfServiceBad.go:14:28:14:36 | sourceStr | provenance | | +| DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | DenialOfServiceBad.go:20:27:20:30 | sink | provenance | | +| DenialOfServiceBad.go:14:28:14:36 | sourceStr | DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | provenance | | +nodes +| DenialOfServiceBad.go:11:12:11:16 | selection of URL | semmle.label | selection of URL | +| DenialOfServiceBad.go:11:12:11:24 | call to Query | semmle.label | call to Query | +| DenialOfServiceBad.go:13:15:13:20 | source | semmle.label | source | +| DenialOfServiceBad.go:13:15:13:29 | call to Get | semmle.label | call to Get | +| DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | semmle.label | ... := ...[0] | +| DenialOfServiceBad.go:14:28:14:36 | sourceStr | semmle.label | sourceStr | +| DenialOfServiceBad.go:20:27:20:30 | sink | semmle.label | sink | +subpaths +#select +| DenialOfServiceBad.go:20:27:20:30 | sink | DenialOfServiceBad.go:11:12:11:16 | selection of URL | DenialOfServiceBad.go:20:27:20:30 | sink | This variable might be leading to denial of service. | diff --git a/go/ql/test/experimental/CWE-770/DenialOfService.qlref b/go/ql/test/experimental/CWE-770/DenialOfService.qlref new file mode 100644 index 000000000000..e5896bb61dfb --- /dev/null +++ b/go/ql/test/experimental/CWE-770/DenialOfService.qlref @@ -0,0 +1 @@ +experimental/CWE-770/DenialOfService.ql \ No newline at end of file diff --git a/go/ql/test/experimental/CWE-770/DenialOfServiceBad.go b/go/ql/test/experimental/CWE-770/DenialOfServiceBad.go new file mode 100644 index 000000000000..2d61cdbdafc2 --- /dev/null +++ b/go/ql/test/experimental/CWE-770/DenialOfServiceBad.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func OutOfMemoryBad(w http.ResponseWriter, r *http.Request) { + source := r.URL.Query() + + sourceStr := source.Get("n") + sink, err := strconv.Atoi(sourceStr) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + result := make([]string, sink) + for i := 0; i < sink; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} diff --git a/go/ql/test/experimental/CWE-770/DenialOfServiceGood.go b/go/ql/test/experimental/CWE-770/DenialOfServiceGood.go new file mode 100644 index 000000000000..a66edf74a830 --- /dev/null +++ b/go/ql/test/experimental/CWE-770/DenialOfServiceGood.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func OutOfMemoryGood1(w http.ResponseWriter, r *http.Request) { + source := r.URL.Query() + MaxValue := 6 + sourceStr := source.Get("n") + sink, err := strconv.Atoi(sourceStr) + if err != nil || sink < 0 { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + if sink > MaxValue { + return + } + result := make([]string, sink) + for i := 0; i < sink; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +} + +func OutOfMemoryGood2(w http.ResponseWriter, r *http.Request) { + source := r.URL.Query() + MaxValue := 6 + sourceStr := source.Get("n") + sink, err := strconv.Atoi(sourceStr) + if err != nil || sink < 0 { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + if sink <= MaxValue { + result := make([]string, sink) + for i := 0; i < sink; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) + } +} + +func OutOfMemoryGood3(w http.ResponseWriter, r *http.Request) { + source := r.URL.Query() + MaxValue := 6 + sourceStr := source.Get("n") + sink, err := strconv.Atoi(sourceStr) + if err != nil || sink < 0 { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + if sink > MaxValue { + sink = MaxValue + result := make([]string, sink) + for i := 0; i < sink; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) + } +} + +func OutOfMemoryGood4(w http.ResponseWriter, r *http.Request) { + source := r.URL.Query() + MaxValue := 6 + sourceStr := source.Get("n") + sink, err := strconv.Atoi(sourceStr) + if err != nil || sink < 0 { + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + if sink > MaxValue { + sink = MaxValue + } else { + tmp := sink + sink = tmp + } + result := make([]string, sink) + for i := 0; i < sink; i++ { + result[i] = fmt.Sprintf("Item %d", i+1) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(result) +}