diff --git a/ql/src/experimental/RequestForgery/RequestForgery.ql b/ql/src/experimental/RequestForgery/RequestForgery.ql new file mode 100644 index 00000000000..1db31170eb0 --- /dev/null +++ b/ql/src/experimental/RequestForgery/RequestForgery.ql @@ -0,0 +1,20 @@ +/** + * @name Uncontrolled data used in network request + * @description Sending network requests with user-controlled data allows for request forgery attacks. + * @kind path-problem + * @problem.severity error + * @id go/request-forgery + * @tags security + * external/cwe/cwe-918 + */ + +import go +import RequestForgery::RequestForgery +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request +where + cfg.hasFlowPath(source, sink) and + request = sink.getNode().(Sink).getARequest() +select request, source, sink, "The $@ of this request depends on $@.", sink.getNode(), + sink.getNode().(Sink).getKind(), source, "a user-provided value" diff --git a/ql/src/experimental/RequestForgery/RequestForgery.qll b/ql/src/experimental/RequestForgery/RequestForgery.qll new file mode 100644 index 00000000000..347d7d0085e --- /dev/null +++ b/ql/src/experimental/RequestForgery/RequestForgery.qll @@ -0,0 +1,35 @@ +/** + * Provides a taint-tracking configuration for reasoning about request forgery + * (SSRF) vulnerabilities. + */ + +import go + +module RequestForgery { + import RequestForgeryCustomizations::RequestForgery + + /** + * A taint-tracking configuration for reasoning about request forgery. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "RequestForgery" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isSanitizerOut(DataFlow::Node node) { + super.isSanitizerOut(node) or + node instanceof SanitizerEdge + } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + super.isSanitizerGuard(guard) or guard instanceof SanitizerGuard + } + } +} diff --git a/ql/src/experimental/RequestForgery/RequestForgeryCustomizations.qll b/ql/src/experimental/RequestForgery/RequestForgeryCustomizations.qll new file mode 100644 index 00000000000..563b99dd474 --- /dev/null +++ b/ql/src/experimental/RequestForgery/RequestForgeryCustomizations.qll @@ -0,0 +1,57 @@ +/** + * Provides classes and predicates used by the request forgery query. + */ + +import go +import semmle.go.security.UrlConcatenation + +/** Provides classes and predicates for the request forgery query. */ +module RequestForgery { + /** A data flow source for request forgery vulnerabilities. */ + abstract class Source extends DataFlow::Node { } + + /** A data flow sink for request forgery vulnerabilities. */ + abstract class Sink extends DataFlow::Node { + /** Gets a request that uses this sink. */ + abstract DataFlow::Node getARequest(); + + /** + * Gets the name of a part of the request that may be tainted by this sink, + * such as the URL or the host. + */ + abstract string getKind(); + } + + /** A sanitizer for request forgery vulnerabilities. */ + abstract class Sanitizer extends DataFlow::Node { } + + /** An outgoing sanitizer edge for request forgery vulnerabilities. */ + abstract class SanitizerEdge extends DataFlow::Node { } + + /** + * A sanitizer guard for request forgery vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A third-party controllable input, considered as a flow source for request forgery. + */ + class UntrustedFlowAsSource extends Source, UntrustedFlowSource { } + + /** + * The URL of a URL request, viewed as a sink for request forgery. + */ + private class ClientRequestUrlAsSink extends Sink { + HTTP::ClientRequest request; + + ClientRequestUrlAsSink() { this = request.getUrl() } + + override DataFlow::Node getARequest() { result = request } + + override string getKind() { result = "URL" } + } + + private class HostnameSanitizer extends SanitizerEdge { + HostnameSanitizer() { hostnameSanitizingPrefixEdge(this, _) } + } +} diff --git a/ql/src/semmle/go/Concepts.qll b/ql/src/semmle/go/Concepts.qll index 57cf1a5516e..b43d75cf8cb 100644 --- a/ql/src/semmle/go/Concepts.qll +++ b/ql/src/semmle/go/Concepts.qll @@ -527,6 +527,43 @@ module HTTP { ResponseWriter getResponseWriter() { result = self.getResponseWriter() } } + /** Provides a class for modeling new HTTP client request APIs. */ + module ClientRequest { + /** + * A call that performs a request to a URL. + * + * Example: An HTTP POST request is a client request that sends some + * `data` to a `url`, where both the headers and the body of the request + * contribute to the `data`. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::ClientRequest` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets the URL of the request. + */ + abstract DataFlow::Node getUrl(); + } + } + + /** + * A call that performs a request to a URL. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::ClientRequest::Range` instead. + */ + class ClientRequest extends DataFlow::Node { + ClientRequest::Range self; + + ClientRequest() { this = self } + + /** + * Gets the URL of the request. + */ + DataFlow::Node getUrl() { result = self.getUrl() } + } + /** Provides a class for modeling new HTTP redirect APIs. */ module Redirect { /** diff --git a/ql/src/semmle/go/frameworks/HTTP.qll b/ql/src/semmle/go/frameworks/HTTP.qll index d16e9df2a41..20da009e8d1 100644 --- a/ql/src/semmle/go/frameworks/HTTP.qll +++ b/ql/src/semmle/go/frameworks/HTTP.qll @@ -147,4 +147,21 @@ private module StdlibHttp { override HTTP::ResponseWriter getResponseWriter() { result.getANode() = this.getArgument(0) } } + + /** A call to a function in the `net/http` package that performs an HTTP request to a URL. */ + private class RequestCall extends HTTP::ClientRequest::Range, DataFlow::CallNode { + RequestCall() { + exists(string functionName | + ( + this.getTarget().hasQualifiedName("net/http", functionName) + or + this.getTarget().(Method).hasQualifiedName("net/http", "Client", functionName) + ) and + (functionName = "Get" or functionName = "Post" or functionName = "PostForm") + ) + } + + /** Gets the URL of the request. */ + override DataFlow::Node getUrl() { result = this.getArgument(0) } + } }