Skip to content
15 changes: 2 additions & 13 deletions java/ql/src/Security/CWE/CWE-319/HttpsUrls.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.Networking
import DataFlow::PathGraph

class HTTPString extends StringLiteral {
Expand All @@ -29,18 +30,6 @@ class HTTPString extends StringLiteral {
}
}

class URLConstructor extends ClassInstanceExpr {
URLConstructor() { this.getConstructor().getDeclaringType().getQualifiedName() = "java.net.URL" }

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 URLOpenMethod extends Method {
URLOpenMethod() {
this.getDeclaringType().getQualifiedName() = "java.net.URL" and
Expand All @@ -63,7 +52,7 @@ class HTTPStringToURLOpenMethodFlowConfig extends TaintTracking::Configuration {
}

override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(URLConstructor u |
exists(UrlConstructorCall u |
node1.asExpr() = u.protocolArg() and
node2.asExpr() = u
)
Expand Down
20 changes: 20 additions & 0 deletions java/ql/src/experimental/CWE-918/RequestForgery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import java.net.http.HttpClient;

public class SSRF extends HttpServlet {
private static final String VALID_URI = "http://lgtm.com";
private HttpClient client = HttpClient.newHttpClient();

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
URI uri = new URI(request.getParameter("uri"));
// BAD: a request parameter is incorporated without validation into a Http request
HttpRequest r = HttpRequest.newBuilder(uri).build();
client.send(r, null);

// GOOD: the request parameter is validated against a known fixed string
if (VALID_URI.equals(request.getParameter("uri"))) {
HttpRequest r2 = HttpRequest.newBuilder(uri).build();
client.send(r2, null);
}
}
}
37 changes: 37 additions & 0 deletions java/ql/src/experimental/CWE-918/RequestForgery.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>


<overview>
<p>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.
</p>

</overview>
<recommendation>

<p>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.</p>

</recommendation>
<example>

<p>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.
</p>

<sample src="RequestForgery.java" />

</example>
<references>
<li>
<a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">OWASP SSRF</a>
</li>

</references>
</qhelp>
33 changes: 33 additions & 0 deletions java/ql/src/experimental/CWE-918/RequestForgery.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @name Server Side Request Forgery (SSRF)
* @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
import DataFlow::PathGraph

class RequestForgeryConfiguration extends TaintTracking::Configuration {
RequestForgeryConfiguration() { this = "Server Side Request Forgery" }

override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }

override predicate isSink(DataFlow::Node sink) { sink instanceof RequestForgerySink }

override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
requestForgeryStep(pred, succ)
}
}

from DataFlow::PathNode source, DataFlow::PathNode sink, RequestForgeryConfiguration conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Potential server side request forgery due to $@.",
source.getNode(), "a user-provided value"
193 changes: 193 additions & 0 deletions java/ql/src/experimental/CWE-918/RequestForgery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import java
import semmle.code.java.frameworks.Networking
import semmle.code.java.frameworks.ApacheHttp
import semmle.code.java.frameworks.spring.Spring
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.Http
import semmle.code.java.dataflow.DataFlow

predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
// propagate to a URI when its host is assigned to
exists(UriCreation c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
or
// propagate to a URL when its host is assigned to
exists(UrlConstructorCall c | c.getHostArg() = 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()
)
}

/** A data flow sink for request forgery vulnerabilities. */
abstract class RequestForgerySink extends DataFlow::Node { }

/**
* An argument to an url `openConnection` or `openStream` call
* taken as a sink for request forgery vulnerabilities.
*/
private class UrlOpen extends RequestForgerySink {
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 RequestForgerySink {
ApacheSetUri() {
exists(MethodAccess ma |
ma.getReceiverType() instanceof ApacheHttpRequest 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 RequestForgerySink {
ApacheHttpRequestInstantiation() {
exists(ClassInstanceExpr c | c.getConstructedType() instanceof ApacheHttpRequest |
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 RequestForgerySink {
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 RequestForgerySink {
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 RequestForgerySink {
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 RequestForgerySink {
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 RequestForgerySink {
JaxRsClientTarget() {
exists(MethodAccess ma |
ma.getMethod().getDeclaringType() instanceof JaxRsClient and
ma.getMethod().hasName("target")
|
this.asExpr() = ma.getArgument(0)
)
}
}

/**
* An argument to `org.springframework.http.RequestEntity`s constructor call
* which is an URI taken as a sink for request forgery vulnerabilities.
*/
private class RequestEntityUriArg extends RequestForgerySink {
RequestEntityUriArg() {
exists(ClassInstanceExpr e, Argument a |
e.getConstructedType() instanceof SpringRequestEntity and
e.getAnArgument() = a and
a.getType() instanceof TypeUri and
this.asExpr() = a
)
}
}

/**
* A class representing all Spring Rest Template methods
* which take an URL as an argument.
*/
private class SpringRestTemplateUrlMethods extends Method {
SpringRestTemplateUrlMethods() {
this.getDeclaringType() instanceof SpringRestTemplate and
this
.hasName([
"doExecute", "postForEntity", "postForLocation", "postForObject", "put", "exchange",
"execute", "getForEntity", "getForObject", "patchForObject"
])
}

/**
* Gets the argument which corresponds to a URL argument
* passed as a `java.net.URL` object or as a string or the like
*/
Argument getUrlArgument(MethodAccess ma) {
// doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
// ResponseExtractor<T> responseExtractor)
result = ma.getArgument(0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
*/
Expand Down
27 changes: 27 additions & 0 deletions java/ql/src/semmle/code/java/frameworks/ApacheHttp.qll
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* Provides classes and predicates related to `org.apache.http.*`.
*/

import java

class ApacheHttpGetParams extends Method {
Expand All @@ -13,3 +17,26 @@ class ApacheHttpEntityGetContent extends Method {
this.getName() = "getContent"
}
}

/**
* An HTTP request as represented by the Apache HTTP Client library. This is
* either `org.apache.http.client.methods.HttpRequestBase`,
* `org.apache.http.message.BasicHttpRequest`, or one of their subclasses.
*/
class ApacheHttpRequest extends RefType {
ApacheHttpRequest() {
this
.getASourceSupertype*()
.hasQualifiedName("org.apache.http.client.methods", "HttpRequestBase") or
this.getASourceSupertype*().hasQualifiedName("org.apache.http.message", "BasicHttpRequest")
}
}

/**
* The `org.apache.http.client.methods.RequestBuilder` class.
*/
class TypeApacheHttpRequestBuilder extends Class {
TypeApacheHttpRequestBuilder() {
this.hasQualifiedName("org.apache.http.client.methods", "RequestBuilder")
}
}
7 changes: 7 additions & 0 deletions java/ql/src/semmle/code/java/frameworks/JaxWS.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading