Promote SSRF query to main query set

This commit is contained in:
Chris Smowton
2021-03-23 15:53:34 +00:00
parent 6ca8d69b26
commit 487c1db6ed
22 changed files with 2 additions and 161 deletions

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -0,0 +1,192 @@
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)
}
}