mirror of
https://github.com/github/codeql.git
synced 2026-04-27 01:35:13 +02:00
Query to detect unsafe getResource calls in Java EE applications
This commit is contained in:
@@ -377,3 +377,18 @@ class RequestDispatchMethod extends Method {
|
||||
this.hasName(["forward", "include"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface `javax.servlet.ServletContext`.
|
||||
*/
|
||||
library class ServletContext extends RefType {
|
||||
ServletContext() { this.hasQualifiedName("javax.servlet", "ServletContext") }
|
||||
}
|
||||
|
||||
/** The `getResource` and `getResourceAsStream` methods of `ServletContext`. */
|
||||
class GetServletResourceMethod extends Method {
|
||||
GetServletResourceMethod() {
|
||||
this.getDeclaringType() instanceof ServletContext and
|
||||
this.hasName(["getResource", "getResourceAsStream"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// BAD: no URI validation
|
||||
URL url = servletContext.getResource(requestUrl);
|
||||
InputStream in = url.openStream();
|
||||
|
||||
InputStream in = request.getServletContext().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();
|
||||
URL url = sc.getResource(path.toString());
|
||||
@@ -36,6 +36,13 @@ attacks. It also shows how to remedy the problem by validating the user input.
|
||||
|
||||
<sample src="UnsafeServletRequestDispatch.java" />
|
||||
|
||||
<p>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.
|
||||
</p>
|
||||
|
||||
<sample src="UnsafeResourceGet.java" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>File Disclosure:
|
||||
@@ -47,5 +54,8 @@ attacks. It also shows how to remedy the problem by validating the user input.
|
||||
<li>Micro Focus:
|
||||
<a href="https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.file_disclosure_j2ee">File Disclosure: J2EE</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuldb.com/?id.81084">Apache Tomcat 6.0/7.0/8.0/9.0 Servletcontext Getresource/getresourceasstream/getresourcepaths Path Traversal</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import java
|
||||
import experimental.semmle.code.java.frameworks.Jsf
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
private import semmle.code.java.dataflow.StringPrefixes
|
||||
|
||||
@@ -18,6 +19,49 @@ private class RequestDispatcherSink extends UnsafeUrlForwardSink {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 GetVirtualFileMethod extends Method {
|
||||
GetVirtualFileMethod() {
|
||||
this.getDeclaringType().getASupertype*() instanceof VirtualFile and
|
||||
this.hasName("getChild")
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument to `getResource()` or `getResourceAsStream()`. */
|
||||
private class GetResourceSink extends UnsafeUrlForwardSink {
|
||||
GetResourceSink() {
|
||||
exists(MethodAccess ma |
|
||||
(
|
||||
ma.getMethod() instanceof GetServletResourceMethod or
|
||||
ma.getMethod() instanceof GetFacesResourceMethod or
|
||||
ma.getMethod() instanceof GetWildflyResourceMethod or
|
||||
ma.getMethod() instanceof GetVirtualFileMethod
|
||||
) and
|
||||
ma.getArgument(0) = this.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument to `new ModelAndView` or `ModelAndView.setViewName`. */
|
||||
private class SpringModelAndViewSink extends UnsafeUrlForwardSink {
|
||||
SpringModelAndViewSink() {
|
||||
|
||||
24
java/ql/src/experimental/semmle/code/java/frameworks/Jsf.qll
Normal file
24
java/ql/src/experimental/semmle/code/java/frameworks/Jsf.qll
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Provides classes and predicates for working with the Java Server Faces (JSF).
|
||||
*/
|
||||
|
||||
import semmle.code.java.Type
|
||||
|
||||
/**
|
||||
* The JSF class `FacesContext` for processing HTTP requests.
|
||||
*/
|
||||
class ExternalContext extends RefType {
|
||||
ExternalContext() {
|
||||
this.hasQualifiedName(["javax.faces.context", "jakarta.faces.context"], "ExternalContext")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The methods `getResource()` and `getResourceAsStream()` declared in JSF `ExternalContext`.
|
||||
*/
|
||||
class GetFacesResourceMethod extends Method {
|
||||
GetFacesResourceMethod() {
|
||||
this.getDeclaringType().getASupertype*() instanceof ExternalContext and
|
||||
this.hasName(["getResource", "getResourceAsStream"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.net.URL;
|
||||
|
||||
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;
|
||||
|
||||
public class UnsafeResourceGet extends HttpServlet {
|
||||
@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();
|
||||
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
ServletConfig cfg = getServletConfig();
|
||||
ServletContext sc = cfg.getServletContext();
|
||||
|
||||
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();
|
||||
|
||||
ServletConfig cfg = getServletConfig();
|
||||
ServletContext sc = cfg.getServletContext();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
edges
|
||||
| UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) : String | UnsafeRequestPath.java:23:33:23:36 | path |
|
||||
| UnsafeResourceGet.java:20:23:20:56 | getParameter(...) : String | UnsafeResourceGet.java:26:28:26:37 | requestUrl |
|
||||
| UnsafeResourceGet.java:60:24:60:58 | getParameter(...) : String | UnsafeResourceGet.java:66:68:66:78 | requestPath |
|
||||
| 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 +21,10 @@ edges
|
||||
nodes
|
||||
| UnsafeRequestPath.java:20:17:20:63 | getServletPath(...) : String | semmle.label | getServletPath(...) : String |
|
||||
| UnsafeRequestPath.java:23:33:23:36 | path | semmle.label | path |
|
||||
| UnsafeResourceGet.java:20:23:20:56 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeResourceGet.java:26:28:26:37 | requestUrl | semmle.label | requestUrl |
|
||||
| UnsafeResourceGet.java:60:24:60:58 | getParameter(...) : String | semmle.label | getParameter(...) : String |
|
||||
| UnsafeResourceGet.java:66:68:66:78 | requestPath | semmle.label | requestPath |
|
||||
| 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 +55,8 @@ 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 |
|
||||
| UnsafeResourceGet.java:26:28:26:37 | requestUrl | UnsafeResourceGet.java:20:23:20:56 | getParameter(...) : String | UnsafeResourceGet.java:26:28:26:37 | requestUrl | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:20:23:20:56 | getParameter(...) | user-provided value |
|
||||
| UnsafeResourceGet.java:66:68:66:78 | requestPath | UnsafeResourceGet.java:60:24:60:58 | getParameter(...) : String | UnsafeResourceGet.java:66:68:66:78 | requestPath | Potentially untrusted URL forward due to $@. | UnsafeResourceGet.java:60:24:60: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 |
|
||||
|
||||
Reference in New Issue
Block a user