diff --git a/java/ql/lib/semmle/code/java/frameworks/Servlets.qll b/java/ql/lib/semmle/code/java/frameworks/Servlets.qll
index de82d49a7aaa..b3fa7ff4e363 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Servlets.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Servlets.qll
@@ -377,3 +377,26 @@ class RequestDispatchMethod extends Method {
this.hasName(["forward", "include"])
}
}
+
+/**
+ * The interface `javax.servlet.ServletContext`.
+ */
+class ServletContext extends RefType {
+ ServletContext() { this.hasQualifiedName("javax.servlet", "ServletContext") }
+}
+
+/** The `getResource` method of `ServletContext`. */
+class GetServletResourceMethod extends Method {
+ GetServletResourceMethod() {
+ this.getDeclaringType() instanceof ServletContext and
+ this.hasName("getResource")
+ }
+}
+
+/** The `getResourceAsStream` method of `ServletContext`. */
+class GetServletResourceAsStreamMethod extends Method {
+ GetServletResourceAsStreamMethod() {
+ this.getDeclaringType() instanceof ServletContext and
+ this.hasName("getResourceAsStream")
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeResourceGet.java b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeResourceGet.java
new file mode 100644
index 000000000000..8b3583bf59e2
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeResourceGet.java
@@ -0,0 +1,18 @@
+// BAD: no URI validation
+URL url = request.getServletContext().getResource(requestUrl);
+url = getClass().getResource(requestUrl);
+InputStream in = url.openStream();
+
+InputStream in = request.getServletContext().getResourceAsStream(requestPath);
+in = getClass().getClassLoader().getResourceAsStream(requestPath);
+
+// GOOD: check for a trusted prefix, ensuring path traversal is not used to erase that prefix:
+// (alternatively use `Path.normalize` instead of checking for `..`)
+if (!requestPath.contains("..") && requestPath.startsWith("/trusted")) {
+ InputStream in = request.getServletContext().getResourceAsStream(requestPath);
+}
+
+Path path = Paths.get(requestUrl).normalize().toRealPath();
+if (path.startsWith("/trusted")) {
+ URL url = request.getServletContext().getResource(path.toString());
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qhelp b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qhelp
index 345eca1e5d43..b1bcf6350c3c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qhelp
@@ -36,6 +36,13 @@ attacks. It also shows how to remedy the problem by validating the user input.
+
The following examples show an HTTP request parameter or request path being used directly to
+retrieve a resource of a Java EE application without validating the input, which allows sensitive
+file exposure attacks. It also shows how to remedy the problem by validating the user input.
+
+
+
+
File Disclosure:
@@ -47,5 +54,8 @@ attacks. It also shows how to remedy the problem by validating the user input.
Micro Focus:
File Disclosure: J2EE
+CVE-2015-5174:
+ Apache Tomcat 6.0/7.0/8.0/9.0 Servletcontext getResource/getResourceAsStream/getResourcePaths Path Traversal
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql
index 9a9a70e45462..0e8fd0a4fe57 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql
@@ -14,6 +14,7 @@ import java
import UnsafeUrlForward
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
+import experimental.semmle.code.java.frameworks.Jsf
import experimental.semmle.code.java.PathSanitizer
import DataFlow::PathGraph
@@ -43,6 +44,20 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
override DataFlow::FlowFeature getAFeature() {
result instanceof DataFlow::FeatureHasSourceCallContext
}
+
+ override predicate isAdditionalTaintStep(DataFlow::Node prev, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ (
+ ma.getMethod() instanceof GetServletResourceMethod or
+ ma.getMethod() instanceof GetFacesResourceMethod or
+ ma.getMethod() instanceof GetClassResourceMethod or
+ ma.getMethod() instanceof GetClassLoaderResourceMethod or
+ ma.getMethod() instanceof GetWildflyResourceMethod
+ ) and
+ ma.getArgument(0) = prev.asExpr() and
+ ma = succ.asExpr()
+ )
+ }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeUrlForwardFlowConfig conf
diff --git a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll
index 9601a988f9fd..5471f55b212a 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll
+++ b/java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll
@@ -1,7 +1,9 @@
import java
+private import experimental.semmle.code.java.frameworks.Jsf
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.StringPrefixes
+private import semmle.code.java.frameworks.javaee.ejb.EJBRestrictions
/** A sink for unsafe URL forward vulnerabilities. */
abstract class UnsafeUrlForwardSink extends DataFlow::Node { }
@@ -19,6 +21,84 @@ private class RequestDispatcherSink extends UnsafeUrlForwardSink {
}
}
+/** The `getResource` method of `Class`. */
+class GetClassResourceMethod extends Method {
+ GetClassResourceMethod() {
+ this.getDeclaringType() instanceof TypeClass and
+ this.hasName("getResource")
+ }
+}
+
+/** The `getResourceAsStream` method of `Class`. */
+class GetClassResourceAsStreamMethod extends Method {
+ GetClassResourceAsStreamMethod() {
+ this.getDeclaringType() instanceof TypeClass and
+ this.hasName("getResourceAsStream")
+ }
+}
+
+/** The `getResource` method of `ClassLoader`. */
+class GetClassLoaderResourceMethod extends Method {
+ GetClassLoaderResourceMethod() {
+ this.getDeclaringType() instanceof ClassLoaderClass and
+ this.hasName("getResource")
+ }
+}
+
+/** The `getResourceAsStream` method of `ClassLoader`. */
+class GetClassLoaderResourceAsStreamMethod extends Method {
+ GetClassLoaderResourceAsStreamMethod() {
+ this.getDeclaringType() instanceof ClassLoaderClass and
+ this.hasName("getResourceAsStream")
+ }
+}
+
+/** The JBoss class `FileResourceManager`. */
+class FileResourceManager extends RefType {
+ FileResourceManager() {
+ this.hasQualifiedName("io.undertow.server.handlers.resource", "FileResourceManager")
+ }
+}
+
+/** The JBoss method `getResource` of `FileResourceManager`. */
+class GetWildflyResourceMethod extends Method {
+ GetWildflyResourceMethod() {
+ this.getDeclaringType().getASupertype*() instanceof FileResourceManager and
+ this.hasName("getResource")
+ }
+}
+
+/** The JBoss class `VirtualFile`. */
+class VirtualFile extends RefType {
+ VirtualFile() { this.hasQualifiedName("org.jboss.vfs", "VirtualFile") }
+}
+
+/** The JBoss method `getChild` of `FileResourceManager`. */
+class GetVirtualFileChildMethod extends Method {
+ GetVirtualFileChildMethod() {
+ this.getDeclaringType().getASupertype*() instanceof VirtualFile and
+ this.hasName("getChild")
+ }
+}
+
+/** An argument to `getResource()` or `getResourceAsStream()`. */
+private class GetResourceSink extends UnsafeUrlForwardSink {
+ GetResourceSink() {
+ sinkNode(this, "open-url")
+ or
+ exists(MethodAccess ma |
+ (
+ ma.getMethod() instanceof GetServletResourceAsStreamMethod or
+ ma.getMethod() instanceof GetFacesResourceAsStreamMethod or
+ ma.getMethod() instanceof GetClassResourceAsStreamMethod or
+ ma.getMethod() instanceof GetClassLoaderResourceAsStreamMethod or
+ ma.getMethod() instanceof GetVirtualFileChildMethod
+ ) and
+ ma.getArgument(0) = this.asExpr()
+ )
+ }
+}
+
/** An argument to `new ModelAndView` or `ModelAndView.setViewName`. */
private class SpringModelAndViewSink extends UnsafeUrlForwardSink {
SpringModelAndViewSink() {
@@ -80,7 +160,7 @@ private class ServletGetPathSource extends SourceModelCsv {
}
}
-/** Taint model related to `java.nio.file.Path`. */
+/** Taint model related to `java.nio.file.Path` and `io.undertow.server.handlers.resource.Resource`. */
private class FilePathFlowStep extends SummaryModelCsv {
override predicate row(string row) {
row =
@@ -88,7 +168,10 @@ private class FilePathFlowStep extends SummaryModelCsv {
"java.nio.file;Paths;true;get;;;Argument[0..1];ReturnValue;taint",
"java.nio.file;Path;true;resolve;;;Argument[-1..0];ReturnValue;taint",
"java.nio.file;Path;true;normalize;;;Argument[-1];ReturnValue;taint",
- "java.nio.file;Path;true;toString;;;Argument[-1];ReturnValue;taint"
+ "java.nio.file;Path;true;toString;;;Argument[-1];ReturnValue;taint",
+ "io.undertow.server.handlers.resource;Resource;true;getFile;;;Argument[-1];ReturnValue;taint",
+ "io.undertow.server.handlers.resource;Resource;true;getFilePath;;;Argument[-1];ReturnValue;taint",
+ "io.undertow.server.handlers.resource;Resource;true;getPath;;;Argument[-1];ReturnValue;taint"
]
}
}
diff --git a/java/ql/src/experimental/semmle/code/java/frameworks/Jsf.qll b/java/ql/src/experimental/semmle/code/java/frameworks/Jsf.qll
new file mode 100644
index 000000000000..a013c341c67c
--- /dev/null
+++ b/java/ql/src/experimental/semmle/code/java/frameworks/Jsf.qll
@@ -0,0 +1,34 @@
+/**
+ * Provides classes and predicates for working with the Java Server Faces (JSF).
+ */
+
+import java
+
+/**
+ * The JSF class `ExternalContext` for processing HTTP requests.
+ */
+class ExternalContext extends RefType {
+ ExternalContext() {
+ this.hasQualifiedName(["javax.faces.context", "jakarta.faces.context"], "ExternalContext")
+ }
+}
+
+/**
+ * The method `getResource()` declared in JSF `ExternalContext`.
+ */
+class GetFacesResourceMethod extends Method {
+ GetFacesResourceMethod() {
+ this.getDeclaringType().getASupertype*() instanceof ExternalContext and
+ this.hasName("getResource")
+ }
+}
+
+/**
+ * The method `getResourceAsStream()` declared in JSF `ExternalContext`.
+ */
+class GetFacesResourceAsStreamMethod extends Method {
+ GetFacesResourceAsStreamMethod() {
+ this.getDeclaringType().getASupertype*() instanceof ExternalContext and
+ this.hasName("getResourceAsStream")
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet.java b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet.java
new file mode 100644
index 000000000000..64c23334f187
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet.java
@@ -0,0 +1,270 @@
+package com.example;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.net.URI;
+import java.net.URL;
+import java.net.URISyntaxException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import io.undertow.server.handlers.resource.FileResourceManager;
+import io.undertow.server.handlers.resource.Resource;
+import org.jboss.vfs.VFS;
+import org.jboss.vfs.VirtualFile;
+
+public class UnsafeResourceGet extends HttpServlet {
+ private static final String BASE_PATH = "/pages";
+
+ @Override
+ // BAD: getResource constructed from `ServletContext` without input validation
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ ServletConfig cfg = getServletConfig();
+ ServletContext sc = cfg.getServletContext();
+
+ // A sample request /fake.jsp/../WEB-INF/web.xml can load the web.xml file
+ URL url = sc.getResource(requestUrl);
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ // GOOD: getResource constructed from `ServletContext` with input validation
+ protected void doGetGood(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ ServletConfig cfg = getServletConfig();
+ ServletContext sc = cfg.getServletContext();
+
+ Path path = Paths.get(requestUrl).normalize().toRealPath();
+ if (path.startsWith(BASE_PATH)) {
+ URL url = sc.getResource(path.toString());
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+ }
+
+ // GOOD: getResource constructed from `ServletContext` with null check only
+ protected void doGetGood2(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ PrintWriter writer = response.getWriter();
+
+ ServletConfig cfg = getServletConfig();
+ ServletContext sc = cfg.getServletContext();
+
+ // A sample request /fake.jsp/../WEB-INF/web.xml can load the web.xml file
+ URL url = sc.getResource(requestUrl);
+ if (url == null) {
+ writer.println("Requested source not found");
+ }
+ }
+
+ // GOOD: getResource constructed from `ServletContext` with `equals` check
+ protected void doGetGood3(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ ServletContext sc = request.getServletContext();
+
+ if (requestUrl.equals("/public/crossdomain.xml")) {
+ URL url = sc.getResource(requestUrl);
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+ }
+
+ @Override
+ // BAD: getResourceAsStream constructed from `ServletContext` without input validation
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+ ServletOutputStream out = response.getOutputStream();
+
+ // A sample request /fake.jsp/../WEB-INF/web.xml can load the web.xml file
+ InputStream in = request.getServletContext().getResourceAsStream(requestPath);
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ // GOOD: getResourceAsStream constructed from `ServletContext` with input validation
+ protected void doPostGood(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+ ServletOutputStream out = response.getOutputStream();
+
+ if (!requestPath.contains("..") && requestPath.startsWith("/trusted")) {
+ InputStream in = request.getServletContext().getResourceAsStream(requestPath);
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+ }
+
+ @Override
+ // BAD: getResource constructed from `Class` without input validation
+ protected void doHead(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ // A sample request /fake.jsp/../../../WEB-INF/web.xml can load the web.xml file
+ // Note the class is in two levels of subpackages and `Class.getResource` starts from its own directory
+ URL url = getClass().getResource(requestUrl);
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ // GOOD: getResource constructed from `Class` with input validation
+ protected void doHeadGood(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ Path path = Paths.get(requestUrl).normalize().toRealPath();
+ if (path.startsWith(BASE_PATH)) {
+ URL url = getClass().getResource(path.toString());
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+ }
+
+ @Override
+ // BAD: getResourceAsStream constructed from `ClassLoader` without input validation
+ protected void doPut(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+ ServletOutputStream out = response.getOutputStream();
+
+ ServletConfig cfg = getServletConfig();
+ ServletContext sc = cfg.getServletContext();
+
+ // A sample request /fake.jsp/../../../WEB-INF/web.xml can load the web.xml file
+ // Note the class is in two levels of subpackages and `ClassLoader.getResourceAsStream` starts from its own directory
+ InputStream in = getClass().getClassLoader().getResourceAsStream(requestPath);
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ // GOOD: getResourceAsStream constructed from `ClassLoader` with input validation
+ protected void doPutGood(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+ ServletOutputStream out = response.getOutputStream();
+
+ ServletConfig cfg = getServletConfig();
+ ServletContext sc = cfg.getServletContext();
+
+ if (!requestPath.contains("..") && requestPath.startsWith("/trusted")) {
+ InputStream in = getClass().getClassLoader().getResourceAsStream(requestPath);
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+ }
+
+ // BAD: getResource constructed from `ClassLoader` without input validation
+ protected void doPutBad(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestUrl = request.getParameter("requestURL");
+ ServletOutputStream out = response.getOutputStream();
+
+ // A sample request /fake.jsp/../../../WEB-INF/web.xml can load the web.xml file
+ // Note the class is in two levels of subpackages and `ClassLoader.getResource` starts from its own directory
+ URL url = getClass().getClassLoader().getResource(requestUrl);
+
+ InputStream in = url.openStream();
+ byte[] buf = new byte[4 * 1024]; // 4K buffer
+ int bytesRead;
+ while ((bytesRead = in.read(buf)) != -1) {
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ // BAD: getResource constructed using Undertow IO without input validation
+ protected void doPutBad2(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+
+ try {
+ FileResourceManager rm = new FileResourceManager(VFS.getChild(new URI("/usr/share")).getPhysicalFile());
+ Resource rs = rm.getResource(requestPath);
+
+ VirtualFile overlay = VFS.getChild(new URI("EAP_HOME/modules/"));
+ // Do file operations
+ overlay.getChild(rs.getPath());
+ } catch (URISyntaxException ue) {
+ throw new IOException("Cannot parse the URI");
+ }
+ }
+
+ // GOOD: getResource constructed using Undertow IO with input validation
+ protected void doPutGood2(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String requestPath = request.getParameter("requestPath");
+
+ try {
+ FileResourceManager rm = new FileResourceManager(VFS.getChild(new URI("/usr/share")).getPhysicalFile());
+ Resource rs = rm.getResource(requestPath);
+
+ VirtualFile overlay = VFS.getChild(new URI("EAP_HOME/modules/"));
+ String path = rs.getPath();
+ if (path.startsWith("/trusted_path") && !path.contains("..")) {
+ // Do file operations
+ overlay.getChild(path);
+ }
+ } catch (URISyntaxException ue) {
+ throw new IOException("Cannot parse the URI");
+ }
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet2.java b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet2.java
new file mode 100644
index 000000000000..b3d041d024cf
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeResourceGet2.java
@@ -0,0 +1,58 @@
+package com.example;
+
+import javax.faces.context.FacesContext;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+
+/** Sample class of JSF managed bean */
+public class UnsafeResourceGet2 {
+ // BAD: getResourceAsStream constructed from `ExternalContext` without input validation
+ public String parameterActionBad1() throws IOException {
+ FacesContext fc = FacesContext.getCurrentInstance();
+ Map params = fc.getExternalContext().getRequestParameterMap();
+ String loadUrl = params.get("loadUrl");
+
+ InputStreamReader isr = new InputStreamReader(fc.getExternalContext().getResourceAsStream(loadUrl));
+ BufferedReader br = new BufferedReader(isr);
+ if(br.ready()) {
+ //Do Stuff
+ return "result";
+ }
+
+ return "home";
+ }
+
+ // BAD: getResource constructed from `ExternalContext` without input validation
+ public String parameterActionBad2() throws IOException {
+ FacesContext fc = FacesContext.getCurrentInstance();
+ Map params = fc.getExternalContext().getRequestParameterMap();
+ String loadUrl = params.get("loadUrl");
+
+ URL url = fc.getExternalContext().getResource(loadUrl);
+
+ InputStream in = url.openStream();
+ //Do Stuff
+ return "result";
+ }
+
+ // GOOD: getResource constructed from `ExternalContext` with input validation
+ public String parameterActionGood1() throws IOException {
+ FacesContext fc = FacesContext.getCurrentInstance();
+ Map params = fc.getExternalContext().getRequestParameterMap();
+ String loadUrl = params.get("loadUrl");
+
+ if (loadUrl.equals("/public/crossdomain.xml")) {
+ URL url = fc.getExternalContext().getResource(loadUrl);
+
+ InputStream in = url.openStream();
+ //Do Stuff
+ return "result";
+ }
+
+ return "home";
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected
index 185508dfc575..179d2b372655 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-552/UnsafeUrlForward.expected
@@ -1,5 +1,18 @@
edges
| UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) : String | UnsafeRequestPath.java:23:33:23:36 | path |
+| UnsafeResourceGet2.java:16:32:16:79 | getRequestParameterMap(...) : Map | UnsafeResourceGet2.java:17:20:17:25 | params : Map |
+| UnsafeResourceGet2.java:17:20:17:25 | params : Map | UnsafeResourceGet2.java:17:20:17:40 | get(...) : Object |
+| UnsafeResourceGet2.java:17:20:17:40 | get(...) : Object | UnsafeResourceGet2.java:19:93:19:99 | loadUrl |
+| UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) : Map | UnsafeResourceGet2.java:33:20:33:25 | params : Map |
+| UnsafeResourceGet2.java:33:20:33:25 | params : Map | UnsafeResourceGet2.java:33:20:33:40 | get(...) : Object |
+| UnsafeResourceGet2.java:33:20:33:40 | get(...) : Object | UnsafeResourceGet2.java:37:20:37:22 | url |
+| UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | UnsafeResourceGet.java:41:20:41:22 | url |
+| UnsafeResourceGet.java:111:24:111:58 | getParameter(...) : String | UnsafeResourceGet.java:115:68:115:78 | requestPath |
+| UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | UnsafeResourceGet.java:150:20:150:22 | url |
+| UnsafeResourceGet.java:181:24:181:58 | getParameter(...) : String | UnsafeResourceGet.java:189:68:189:78 | requestPath |
+| UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | UnsafeResourceGet.java:226:20:226:22 | url |
+| UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | UnsafeResourceGet.java:245:21:245:22 | rs : Resource |
+| UnsafeResourceGet.java:245:21:245:22 | rs : Resource | UnsafeResourceGet.java:245:21:245:32 | getPath(...) |
| UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:32:51:32:59 | returnURL |
| UnsafeServletRequestDispatch.java:42:22:42:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:48:56:48:64 | returnURL |
| UnsafeServletRequestDispatch.java:71:17:71:44 | getParameter(...) : String | UnsafeServletRequestDispatch.java:76:53:76:56 | path |
@@ -19,6 +32,27 @@ edges
nodes
| UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) : String | semmle.label | getServletPath(...) : String |
| UnsafeRequestPath.java:23:33:23:36 | path | semmle.label | path |
+| UnsafeResourceGet2.java:16:32:16:79 | getRequestParameterMap(...) : Map | semmle.label | getRequestParameterMap(...) : Map |
+| UnsafeResourceGet2.java:17:20:17:25 | params : Map | semmle.label | params : Map |
+| UnsafeResourceGet2.java:17:20:17:40 | get(...) : Object | semmle.label | get(...) : Object |
+| UnsafeResourceGet2.java:19:93:19:99 | loadUrl | semmle.label | loadUrl |
+| UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) : Map | semmle.label | getRequestParameterMap(...) : Map |
+| UnsafeResourceGet2.java:33:20:33:25 | params : Map | semmle.label | params : Map |
+| UnsafeResourceGet2.java:33:20:33:40 | get(...) : Object | semmle.label | get(...) : Object |
+| UnsafeResourceGet2.java:37:20:37:22 | url | semmle.label | url |
+| UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:41:20:41:22 | url | semmle.label | url |
+| UnsafeResourceGet.java:111:24:111:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:115:68:115:78 | requestPath | semmle.label | requestPath |
+| UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:150:20:150:22 | url | semmle.label | url |
+| UnsafeResourceGet.java:181:24:181:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:189:68:189:78 | requestPath | semmle.label | requestPath |
+| UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:226:20:226:22 | url | semmle.label | url |
+| UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| UnsafeResourceGet.java:245:21:245:22 | rs : Resource | semmle.label | rs : Resource |
+| UnsafeResourceGet.java:245:21:245:32 | getPath(...) | semmle.label | getPath(...) |
| UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| UnsafeServletRequestDispatch.java:32:51:32:59 | returnURL | semmle.label | returnURL |
| UnsafeServletRequestDispatch.java:42:22:42:54 | getParameter(...) : String | semmle.label | getParameter(...) : String |
@@ -49,6 +83,14 @@ nodes
subpaths
#select
| UnsafeRequestPath.java:23:33:23:36 | path | UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) : String | UnsafeRequestPath.java:23:33:23:36 | path | Potentially untrusted URL forward due to $@. | UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) | user-provided value |
+| UnsafeResourceGet2.java:19:93:19:99 | loadUrl | UnsafeResourceGet2.java:16:32:16:79 | getRequestParameterMap(...) : Map | UnsafeResourceGet2.java:19:93:19:99 | loadUrl | Potentially untrusted URL forward due to $@. | UnsafeResourceGet2.java:16:32:16:79 | getRequestParameterMap(...) | user-provided value |
+| UnsafeResourceGet2.java:37:20:37:22 | url | UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) : Map | UnsafeResourceGet2.java:37:20:37:22 | url | Potentially untrusted URL forward due to $@. | UnsafeResourceGet2.java:32:32:32:79 | getRequestParameterMap(...) | user-provided value |
+| UnsafeResourceGet.java:41:20:41:22 | url | UnsafeResourceGet.java:32:23:32:56 | getParameter(...) : String | UnsafeResourceGet.java:41:20:41:22 | url | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:32:23:32:56 | getParameter(...) | user-provided value |
+| UnsafeResourceGet.java:115:68:115:78 | requestPath | UnsafeResourceGet.java:111:24:111:58 | getParameter(...) : String | UnsafeResourceGet.java:115:68:115:78 | requestPath | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:111:24:111:58 | getParameter(...) | user-provided value |
+| UnsafeResourceGet.java:150:20:150:22 | url | UnsafeResourceGet.java:143:23:143:56 | getParameter(...) : String | UnsafeResourceGet.java:150:20:150:22 | url | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:143:23:143:56 | getParameter(...) | user-provided value |
+| UnsafeResourceGet.java:189:68:189:78 | requestPath | UnsafeResourceGet.java:181:24:181:58 | getParameter(...) : String | UnsafeResourceGet.java:189:68:189:78 | requestPath | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:181:24:181:58 | getParameter(...) | user-provided value |
+| UnsafeResourceGet.java:226:20:226:22 | url | UnsafeResourceGet.java:219:23:219:56 | getParameter(...) : String | UnsafeResourceGet.java:226:20:226:22 | url | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:219:23:219:56 | getParameter(...) | user-provided value |
+| UnsafeResourceGet.java:245:21:245:32 | getPath(...) | UnsafeResourceGet.java:237:24:237:58 | getParameter(...) : String | UnsafeResourceGet.java:245:21:245:32 | getPath(...) | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:237:24:237:58 | getParameter(...) | user-provided value |
| UnsafeServletRequestDispatch.java:32:51:32:59 | returnURL | UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:32:51:32:59 | returnURL | Potentially untrusted URL forward due to $@. | UnsafeServletRequestDispatch.java:23:22:23:54 | getParameter(...) | user-provided value |
| UnsafeServletRequestDispatch.java:48:56:48:64 | returnURL | UnsafeServletRequestDispatch.java:42:22:42:54 | getParameter(...) : String | UnsafeServletRequestDispatch.java:48:56:48:64 | returnURL | Potentially untrusted URL forward due to $@. | UnsafeServletRequestDispatch.java:42:22:42:54 | getParameter(...) | user-provided value |
| UnsafeServletRequestDispatch.java:76:53:76:56 | path | UnsafeServletRequestDispatch.java:71:17:71:44 | getParameter(...) : String | UnsafeServletRequestDispatch.java:76:53:76:56 | path | Potentially untrusted URL forward due to $@. | UnsafeServletRequestDispatch.java:71:17:71:44 | getParameter(...) | user-provided value |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-552/options b/java/ql/test/experimental/query-tests/security/CWE-552/options
index ba166b547a02..095b86c3e9a4 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-552/options
+++ b/java/ql/test/experimental/query-tests/security/CWE-552/options
@@ -1 +1 @@
-//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8/
\ No newline at end of file
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8/:${testdir}/../../../../stubs/javax-faces-2.3/:${testdir}/../../../../stubs/undertow-io-2.2/:${testdir}/../../../../stubs/jboss-vfs-3.2/
diff --git a/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VFS.java b/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VFS.java
new file mode 100644
index 000000000000..e35282395586
--- /dev/null
+++ b/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VFS.java
@@ -0,0 +1,19 @@
+package org.jboss.vfs;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class VFS {
+ public static VirtualFile getChild(URL url) throws URISyntaxException {
+ return null;
+ }
+
+ public static VirtualFile getChild(URI uri) {
+ return null;
+ }
+
+ public static VirtualFile getChild(String path) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VirtualFile.java b/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VirtualFile.java
new file mode 100644
index 000000000000..ff6c17caff68
--- /dev/null
+++ b/java/ql/test/stubs/jboss-vfs-3.2/org/jboss/vfs/VirtualFile.java
@@ -0,0 +1,29 @@
+package org.jboss.vfs;
+
+import java.io.File;
+import java.io.IOException;
+
+public class VirtualFile {
+ VirtualFile(String name, VirtualFile parent) {
+ }
+
+ public String getName() {
+ return null;
+ }
+
+ public String getPathName() {
+ return null;
+ }
+
+ String getPathName(boolean url) {
+ return null;
+ }
+
+ public File getPhysicalFile() throws IOException {
+ return null;
+ }
+
+ public VirtualFile getChild(String path) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/FileResourceManager.java b/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/FileResourceManager.java
new file mode 100644
index 000000000000..815222f2f9f5
--- /dev/null
+++ b/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/FileResourceManager.java
@@ -0,0 +1,31 @@
+package io.undertow.server.handlers.resource;
+
+import java.io.File;
+
+public class FileResourceManager {
+ public FileResourceManager(final File base) {
+ }
+
+ public FileResourceManager(final File base, long transferMinSize) {
+ }
+
+ public FileResourceManager(final File base, long transferMinSize, boolean caseSensitive) {
+ }
+
+ public FileResourceManager(final File base, long transferMinSize, boolean followLinks, final String... safePaths) {
+ }
+
+ protected FileResourceManager(long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) {
+ }
+
+ public FileResourceManager(final File base, long transferMinSize, boolean caseSensitive, boolean followLinks, final String... safePaths) {
+ }
+
+ public File getBase() {
+ return null;
+ }
+
+ public Resource getResource(final String p) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/Resource.java b/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/Resource.java
new file mode 100644
index 000000000000..579e08107c6e
--- /dev/null
+++ b/java/ql/test/stubs/undertow-io-2.2/io/undertow/server/handlers/resource/Resource.java
@@ -0,0 +1,33 @@
+package io.undertow.server.handlers.resource;
+
+import java.io.File;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Date;
+import java.util.List;
+
+public interface Resource {
+ String getPath();
+
+ Date getLastModified();
+
+ String getLastModifiedString();
+
+ String getName();
+
+ boolean isDirectory();
+
+ List list();
+
+ Long getContentLength();
+
+ File getFile();
+
+ Path getFilePath();
+
+ File getResourceManagerRoot();
+
+ Path getResourceManagerRootPath();
+
+ URL getUrl();
+}