+
+
+
+Directly incorporating user input into a HTTP request without validating the input
+can facilitate Server Side Request Forgery (SSRF) attacks. In these attacks, the server
+may be tricked into making a request and interacting with an attacker-controlled server.
+
+
+
+
+
+To guard against SSRF attacks, it is advisable to avoid putting user input
+directly into the request URL. Instead, maintain a list of authorized
+URLs on the server; then choose from that list based on the user input provided.
+
+
+
+
+The following example shows an HTTP request parameter being used directly in a forming a
+new request without validating the input, which facilitates SSRF attacks.
+It also shows how to remedy the problem by validating the user input against a known fixed string.
+
+
+
+
+
+
+
+ OWASP SSRF
+
+
+
+
diff --git a/java/ql/src/experimental/CWE-918/RequestForgery.ql b/java/ql/src/experimental/CWE-918/RequestForgery.ql
new file mode 100644
index 00000000000..f8cb7481c44
--- /dev/null
+++ b/java/ql/src/experimental/CWE-918/RequestForgery.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Server Sider Request Forgery (SSRF) from remote source
+ * @description Making web requests based on unvalidated user-input
+ * may cause server to communicate with malicious servers.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id java/ssrf
+ * @tags security
+ * external/cwe/cwe-918
+ */
+
+import java
+import semmle.code.java.dataflow.FlowSources
+import RequestForgery::RequestForgery
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, RequestForgeryRemoteConfiguration conf
+where conf.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Potential server side request forgery due to $@.",
+ source.getNode(), "a user-provided value"
diff --git a/java/ql/src/experimental/CWE-918/RequestForgery.qll b/java/ql/src/experimental/CWE-918/RequestForgery.qll
new file mode 100644
index 00000000000..2cb447bbc02
--- /dev/null
+++ b/java/ql/src/experimental/CWE-918/RequestForgery.qll
@@ -0,0 +1,57 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.frameworks.javase.URI
+import semmle.code.java.frameworks.javase.URL
+import semmle.code.java.frameworks.javase.Http
+import semmle.code.java.dataflow.DataFlow
+
+module RequestForgery {
+ import RequestForgeryCustomizations::RequestForgery
+
+ /**
+ * A taint-tracking configuration for reasoning about request forgery.
+ */
+ class RequestForgeryRemoteConfiguration extends TaintTracking::Configuration {
+ RequestForgeryRemoteConfiguration() { this = "Server Side Request Forgery" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ additionalStep(pred, succ)
+ }
+ }
+}
+
+predicate additionalStep(DataFlow::Node pred, DataFlow::Node succ) {
+ // propagate to a URI when its host is assigned to
+ exists(UriConstructor c | c.hostArg() = pred.asExpr() | succ.asExpr() = c)
+ or
+ // propagate to a URL when its host is assigned to
+ exists(UrlConstructor c | c.hostArg() = pred.asExpr() | succ.asExpr() = c)
+ or
+ // propagate to a RequestEntity when its url is assigned to
+ exists(MethodAccess m |
+ m.getMethod().getDeclaringType() instanceof SpringRequestEntity and
+ (
+ m.getMethod().hasName(["get", "post", "head", "delete", "options", "patch", "put"]) and
+ m.getArgument(0) = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ m.getMethod().hasName("method") and
+ m.getArgument(1) = pred.asExpr() and
+ m = succ.asExpr()
+ )
+ or
+ // propagate from a `RequestEntity<>$BodyBuilder` to a `RequestEntity`
+ // when the builder is tainted
+ exists(MethodAccess m, RefType t |
+ m.getMethod().getDeclaringType() = t and
+ t.hasQualifiedName("org.springframework.http", "RequestEntity<>$BodyBuilder") and
+ m.getMethod().hasName("body") and
+ m.getQualifier() = pred.asExpr() and
+ m = succ.asExpr()
+ )
+}
diff --git a/java/ql/src/experimental/CWE-918/RequestForgeryCustomizations.qll b/java/ql/src/experimental/CWE-918/RequestForgeryCustomizations.qll
new file mode 100644
index 00000000000..7c16646d333
--- /dev/null
+++ b/java/ql/src/experimental/CWE-918/RequestForgeryCustomizations.qll
@@ -0,0 +1,137 @@
+/** A module to reason about request forgery vulnerabilities. */
+
+import java
+import semmle.code.java.frameworks.Networking
+import semmle.code.java.frameworks.javase.URI
+import semmle.code.java.frameworks.javase.URL
+import semmle.code.java.frameworks.JaxWS
+import semmle.code.java.frameworks.javase.Http
+import semmle.code.java.dataflow.DataFlow
+
+/** A module to reason about request forgery vulnerabilities. */
+module RequestForgery {
+ /** A data flow sink for request forgery vulnerabilities. */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * An argument to an url `openConnection` or `openStream` call
+ * taken as a sink for request forgery vulnerabilities.
+ */
+ private class UrlOpen extends Sink {
+ UrlOpen() {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof UrlOpenConnectionMethod or
+ ma.getMethod() instanceof UrlOpenStreamMethod
+ |
+ this.asExpr() = ma.getQualifier()
+ )
+ }
+ }
+
+ /**
+ * An argument to an Apache `setURI` call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class ApacheSetUri extends Sink {
+ ApacheSetUri() {
+ exists(MethodAccess ma |
+ ma.getReceiverType() instanceof TypeApacheHttpRequest and
+ ma.getMethod().hasName("setURI")
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * An argument to any Apache Request Instantiation call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class ApacheHttpRequestInstantiation extends Sink {
+ ApacheHttpRequestInstantiation() {
+ exists(ClassInstanceExpr c | c.getConstructedType() instanceof TypeApacheHttpRequest |
+ this.asExpr() = c.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * An argument to a Apache RequestBuilder method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class ApacheHttpRequestBuilderArgument extends Sink {
+ ApacheHttpRequestBuilderArgument() {
+ exists(MethodAccess ma |
+ ma.getReceiverType() instanceof TypeApacheHttpRequestBuilder and
+ ma.getMethod().hasName(["setURI", "get", "post", "put", "optons", "head", "delete"])
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * An argument to any Java.net.http.request Instantiation call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class HttpRequestNewBuilder extends Sink {
+ HttpRequestNewBuilder() {
+ exists(MethodAccess call |
+ call.getCallee().hasName("newBuilder") and
+ call.getMethod().getDeclaringType().getName() = "HttpRequest"
+ |
+ this.asExpr() = call.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * An argument to an Http Builder `uri` call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class HttpBuilderUriArgument extends Sink {
+ HttpBuilderUriArgument() {
+ exists(MethodAccess ma | ma.getMethod() instanceof HttpBuilderUri |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * An argument to a Spring Rest Template method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class SpringRestTemplateArgument extends Sink {
+ SpringRestTemplateArgument() {
+ exists(MethodAccess ma |
+ this.asExpr() = ma.getMethod().(SpringRestTemplateUrlMethods).getUrlArgument(ma)
+ )
+ }
+ }
+
+ /**
+ * An argument to `javax.ws.rs.Client`s `target` method call taken as a
+ * sink for request forgery vulnerabilities.
+ */
+ private class JaxRsClientTarget extends Sink {
+ JaxRsClientTarget() {
+ exists(MethodAccess ma, JaxRsClient t |
+ // ma.getMethod().getDeclaringType().getQualifiedName() ="javax.ws.rs.client.Client" and
+ ma.getMethod().getDeclaringType() instanceof JaxRsClient and
+ ma.getMethod().hasName("target")
+ |
+ this.asExpr() = ma.getArgument(0)
+ )
+ }
+ }
+
+ /**
+ * A URI argument to `org.springframework.http.RequestEntity`s constructor call
+ * taken as a sink for request forgery vulnerabilities.
+ */
+ private class RequestEntityUriArg extends Sink {
+ RequestEntityUriArg() {
+ exists(SpringRequestEntityInstanceExpr e | e.getUriArg() = this.asExpr())
+ }
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
index 5e1c84b9ea1..dc7b687e2e3 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
@@ -10,6 +10,7 @@
import java
import semmle.code.java.frameworks.Networking
+import semmle.code.java.frameworks.ApacheHttp
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
@@ -21,19 +22,6 @@ private string getPrivateHostRegex() {
"(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?"
}
-/**
- * The Java class `org.apache.http.client.methods.HttpRequestBase`. Popular subclasses include `HttpGet`, `HttpPost`, and `HttpPut`.
- * And the Java class `org.apache.http.message.BasicHttpRequest`.
- */
-class ApacheHttpRequest extends RefType {
- ApacheHttpRequest() {
- this
- .getASourceSupertype*()
- .hasQualifiedName("org.apache.http.client.methods", "HttpRequestBase") or
- this.getASourceSupertype*().hasQualifiedName("org.apache.http.message", "BasicHttpRequest")
- }
-}
-
/**
* Class of Java URL constructor.
*/
@@ -167,7 +155,7 @@ class HttpURLOpenMethod extends Method {
/** Constructor of `ApacheHttpRequest` */
predicate apacheHttpRequest(DataFlow::Node node1, DataFlow::Node node2) {
exists(ConstructorCall cc |
- cc.getConstructedType() instanceof ApacheHttpRequest and
+ cc.getConstructedType() instanceof TypeApacheHttpRequestBase and
node2.asExpr() = cc and
cc.getAnArgument() = node1.asExpr()
)
diff --git a/java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll b/java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll
index 5bea8497a97..92d1f8a7e7e 100644
--- a/java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll
+++ b/java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll
@@ -13,3 +13,32 @@ class ApacheHttpEntityGetContent extends Method {
this.getName() = "getContent"
}
}
+
+/**
+ * A class derived from the `HttpRequestBase` or the `BasicHttpRequest`
+ * class of the Apache Http Client `org.apache.http` library
+ */
+class TypeApacheHttpRequestBase extends RefType {
+ TypeApacheHttpRequestBase() {
+ this
+ .getASourceSupertype*()
+ .hasQualifiedName("org.apache.http.client.methods", "HttpRequestBase") or
+ this.getASourceSupertype*().hasQualifiedName("org.apache.http.message", "BasicHttpRequest")
+ }
+}
+
+/*
+ * Any class which can be used to make an HTTP request using the Apache Http Client library
+ * Examples include `HttpGet`,`HttpPost` etc.
+ */
+
+class TypeApacheHttpRequest extends Class {
+ TypeApacheHttpRequest() { exists(TypeApacheHttpRequestBase t | this.extendsOrImplements(t)) }
+}
+
+/* A class representing the `RequestBuilder` class of the Apache Http Client library */
+class TypeApacheHttpRequestBuilder extends Class {
+ TypeApacheHttpRequestBuilder() {
+ hasQualifiedName("org.apache.http.client.methods", "RequestBuilder")
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/JaxWS.qll b/java/ql/src/semmle/code/java/frameworks/JaxWS.qll
index fdd483d29b4..6effa413f6c 100644
--- a/java/ql/src/semmle/code/java/frameworks/JaxWS.qll
+++ b/java/ql/src/semmle/code/java/frameworks/JaxWS.qll
@@ -170,6 +170,13 @@ class JaxRsResponseBuilder extends Class {
JaxRsResponseBuilder() { this.hasQualifiedName("javax.ws.rs.core", "ResponseBuilder") }
}
+/**
+ * The class `javax.ws.rs.client.Client`
+ */
+class JaxRsClient extends RefType {
+ JaxRsClient() { this.hasQualifiedName("javax.ws.rs.client", "Client") }
+}
+
/**
* A constructor that may be called by a JaxRS container to construct an instance to inject into a
* resource method or resource class constructor.
diff --git a/java/ql/src/semmle/code/java/frameworks/javase/Http.qll b/java/ql/src/semmle/code/java/frameworks/javase/Http.qll
new file mode 100644
index 00000000000..d48b61b0cf5
--- /dev/null
+++ b/java/ql/src/semmle/code/java/frameworks/javase/Http.qll
@@ -0,0 +1,20 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/** A class representing `HttpRequest.Builder`. */
+class TypeHttpRequestBuilder extends Interface {
+ TypeHttpRequestBuilder() { hasQualifiedName("java.net.http", "HttpRequest$Builder") }
+}
+
+/** A class representing `java.net.http.HttpRequest`. */
+class TypeHttpRequest extends Interface {
+ TypeHttpRequest() { hasQualifiedName("java.net.http", "HttpRequest") }
+}
+
+/** A class representing `java.net.http.HttpRequest$Builder`'s `uri` method. */
+class HttpBuilderUri extends Method {
+ HttpBuilderUri() {
+ this.getDeclaringType() instanceof TypeHttpRequestBuilder and
+ this.getName() = "uri"
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/javase/URI.qll b/java/ql/src/semmle/code/java/frameworks/javase/URI.qll
new file mode 100644
index 00000000000..c195962e56e
--- /dev/null
+++ b/java/ql/src/semmle/code/java/frameworks/javase/URI.qll
@@ -0,0 +1,43 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/** Any expresion or call which returns a new URI.*/
+abstract class UriCreation extends Top {
+ /**
+ * Returns the host of the newly created URI.
+ * In the case where the host is specified separately, this returns only the host.
+ * In the case where the uri is parsed from an input string,
+ * such as in `URI(`http://foo.com/mypath')`,
+ * this returns the entire argument passed i.e. `http://foo.com/mypath'.
+ */
+
+ abstract Expr hostArg();
+}
+
+/** An URI constructor expression */
+class UriConstructor extends ClassInstanceExpr, UriCreation {
+ UriConstructor() { this.getConstructor().getDeclaringType().getQualifiedName() = "java.net.URI" }
+
+ override Expr hostArg() {
+ // URI(String str)
+ result = this.getArgument(0) and this.getNumArgument() = 1
+ or
+ // URI(String scheme, String ssp, String fragment)
+ // URI(String scheme, String host, String path, String fragment)
+ // URI(String scheme, String authority, String path, String query, String fragment)
+ result = this.getArgument(1) and this.getNumArgument() = [3, 4, 5]
+ or
+ // URI(String scheme, String userInfo, String host, int port, String path, String query,
+ // String fragment)
+ result = this.getArgument(2) and this.getNumArgument() = 7
+ }
+}
+
+class UriCreate extends Call, UriCreation {
+ UriCreate() {
+ this.getCallee().getName() = "create" and
+ this.getCallee().getDeclaringType() instanceof TypeUri
+ }
+
+ override Expr hostArg() { result = this.getArgument(0) }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/javase/URL.qll b/java/ql/src/semmle/code/java/frameworks/javase/URL.qll
new file mode 100644
index 00000000000..681319ff562
--- /dev/null
+++ b/java/ql/src/semmle/code/java/frameworks/javase/URL.qll
@@ -0,0 +1,47 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+
+/* Am URL constructor expression */
+class UrlConstructor extends ClassInstanceExpr {
+ UrlConstructor() { this.getConstructor().getDeclaringType().getQualifiedName() = "java.net.URL" }
+
+ Expr hostArg() {
+ // URL(String spec)
+ this.getNumArgument() = 1 and result = this.getArgument(0)
+ or
+ // URL(String protocol, String host, int port, String file)
+ // URL(String protocol, String host, int port, String file, URLStreamHandler handler)
+ this.getNumArgument() = [4,5] and result = this.getArgument(1)
+ or
+ // URL(String protocol, String host, String file)
+ // but not
+ // URL(URL context, String spec, URLStreamHandler handler)
+ (
+ this.getNumArgument() = 3 and
+ this.getConstructor().getParameter(2).getType() instanceof TypeString
+ ) and
+ result = this.getArgument(1)
+ }
+
+ Expr protocolArg() {
+ // In all cases except where the first parameter is a URL, the argument
+ // containing the protocol is the first one, otherwise it is the second.
+ if this.getConstructor().getParameter(0).getType().getName() = "URL"
+ then result = this.getArgument(1)
+ else result = this.getArgument(0)
+ }
+}
+
+class UrlOpenStreamMethod extends Method {
+ UrlOpenStreamMethod() {
+ this.getDeclaringType() instanceof TypeUrl and
+ this.getName() = "openStream"
+ }
+}
+
+class UrlOpenConnectionMethod extends Method {
+ UrlOpenConnectionMethod() {
+ this.getDeclaringType() instanceof TypeUrl and
+ this.getName() = "openConnection"
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/spring/SpringHttp.qll b/java/ql/src/semmle/code/java/frameworks/spring/SpringHttp.qll
index 59016df25f8..bbc10c652c9 100644
--- a/java/ql/src/semmle/code/java/frameworks/spring/SpringHttp.qll
+++ b/java/ql/src/semmle/code/java/frameworks/spring/SpringHttp.qll
@@ -4,6 +4,7 @@
*/
import java
+import semmle.code.java.frameworks.Networking
/** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
class SpringHttpEntity extends Class {
@@ -38,3 +39,17 @@ class SpringResponseEntityBodyBuilder extends Interface {
class SpringHttpHeaders extends Class {
SpringHttpHeaders() { this.hasQualifiedName("org.springframework.http", "HttpHeaders") }
}
+
+/** Models `org.springframework.http.RequestEntity`s instantiation expressions. */
+class SpringRequestEntityInstanceExpr extends ClassInstanceExpr {
+ int numArgs;
+
+ SpringRequestEntityInstanceExpr() {
+ this.getConstructedType() instanceof SpringRequestEntity and
+ numArgs = this.getNumArgument()
+ }
+
+ Argument getUriArg() {
+ exists(Argument a | this.getAnArgument() = a and a.getType() instanceof TypeUri | result = a)
+ }
+}
diff --git a/java/ql/src/semmle/code/java/frameworks/spring/SpringWebClient.qll b/java/ql/src/semmle/code/java/frameworks/spring/SpringWebClient.qll
index 3a8d4bb084a..14bbb99db68 100644
--- a/java/ql/src/semmle/code/java/frameworks/spring/SpringWebClient.qll
+++ b/java/ql/src/semmle/code/java/frameworks/spring/SpringWebClient.qll
@@ -27,3 +27,116 @@ class SpringWebClient extends Interface {
this.hasQualifiedName("org.springframework.web.reactive.function.client", "WebClient")
}
}
+
+/**
+ * An abstract class representing all Spring Rest Template methods
+ * which take an URL as an argument.
+ */
+abstract class SpringRestTemplateUrlMethods extends Method {
+ /** Gets the argument which corresponds to a URL */
+ abstract Argument getUrlArgument(MethodAccess ma);
+}
+
+/** Models `RestTemplate` class's `doExecute` method */
+class RestTemplateDoExecute extends SpringRestTemplateUrlMethods {
+ RestTemplateDoExecute() {
+ this.getDeclaringType() instanceof SpringRestTemplate and
+ this.hasName("doExecute")
+ }
+
+ override Argument getUrlArgument(MethodAccess ma) {
+ // doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
+ // ResponseExtractor