mirror of
https://github.com/github/codeql.git
synced 2026-05-04 13:15:21 +02:00
Merge pull request #9873 from luchua-bc/java/permissive-dot-regex
Java: CWE-625 Query to detect regex dot bypass
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class DotRegexFilter implements Filter {
|
||||
private static final String PROTECTED_PATTERN = "/protected/.*";
|
||||
private static final String CONSTRAINT_PATTERN = "/protected/xyz\\.xml";
|
||||
|
||||
private ServletContext context;
|
||||
|
||||
public void init(FilterConfig config) throws ServletException {
|
||||
this.context = config.getServletContext();
|
||||
}
|
||||
|
||||
// BAD: A string with line return e.g. `/protected/%0dxyz` can bypass the path check
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String source = httpRequest.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!httpRequest.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
// Redirect to the login page
|
||||
httpResponse.sendRedirect("/login.html");
|
||||
} else {
|
||||
// Not protected page - pass the request along the filter chain
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: A string with line return e.g. `/protected/%0dxyz` cannot bypass the path check
|
||||
public void doFilter2(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String source = httpRequest.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(CONSTRAINT_PATTERN);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!httpRequest.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
// Redirect to the login page
|
||||
httpResponse.sendRedirect("/login.html");
|
||||
} else {
|
||||
// Not protected page - pass the request along the filter chain
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
// Close resources
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
public class DotRegexServlet extends HttpServlet {
|
||||
private static final String PROTECTED_PATTERN = "/protected/.*";
|
||||
private static final String CONSTRAINT_PATTERN = "/protected/xyz\\.xml";
|
||||
|
||||
@Override
|
||||
// BAD: A string with line return e.g. `/protected/%0dxyz` can bypass the path check
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
response.sendRedirect("/login.html");
|
||||
return;
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: A string with line return e.g. `/protected/%0dxyz` cannot bypass the path check
|
||||
protected void doGet2(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN, Pattern.DOTALL);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
response.sendRedirect("/login.html");
|
||||
return;
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BAD: A string with line return e.g. `/protected/%0axyz` can bypass the path check
|
||||
protected void doGet3(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getRequestURI();
|
||||
|
||||
boolean matches = source.matches(PROTECTED_PATTERN);
|
||||
|
||||
if (matches) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
response.sendRedirect("/login.html");
|
||||
return;
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BAD: A string with line return e.g. `/protected/%0axyz` can bypass the path check
|
||||
protected void doGet4(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
boolean matches = Pattern.matches(PROTECTED_PATTERN, source);
|
||||
|
||||
if (matches) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
response.sendRedirect("/login.html");
|
||||
return;
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: Only a specific path can pass the validation
|
||||
protected void doGet5(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(CONSTRAINT_PATTERN);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
response.sendRedirect("/login.html");
|
||||
return;
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BAD: A string with line return e.g. `/protected/%0dxyz` can bypass the path check
|
||||
protected void doGet6(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/login");
|
||||
dispatcher.forward(request, response);
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(source);
|
||||
dispatcher.forward(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GOOD: A string with line return e.g. `/protected/%0dxyz` cannot bypass the path check
|
||||
protected void doGet7(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String source = request.getPathInfo();
|
||||
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN, Pattern.DOTALL);
|
||||
Matcher m = p.matcher(source);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (!request.getSession().getAttribute("secAttr").equals("secValue")) {
|
||||
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/login");
|
||||
dispatcher.forward(request, response);
|
||||
} else {
|
||||
// Not protected page - render content
|
||||
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(source);
|
||||
dispatcher.include(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Controller
|
||||
public class DotRegexSpring {
|
||||
private static final String PROTECTED_PATTERN = "/protected/.*";
|
||||
private static final String CONSTRAINT_PATTERN = "/protected/xyz\\.xml";
|
||||
|
||||
@GetMapping("param")
|
||||
// BAD: A string with line return e.g. `/protected/%0dxyz` can bypass the path check
|
||||
public String withParam(@RequestParam String path, Model model) throws UnsupportedEncodingException {
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN);
|
||||
path = decodePath(path);
|
||||
Matcher m = p.matcher(path);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (model.getAttribute("secAttr") == null || !model.getAttribute("secAttr").equals("secValue")) {
|
||||
return "redirect:login";
|
||||
}
|
||||
}
|
||||
// Not protected page - render content
|
||||
return path;
|
||||
}
|
||||
|
||||
@GetMapping("{path}")
|
||||
// BAD: A string with line return e.g. `%252Fprotected%252F%250dxyz` can bypass the path check
|
||||
public RedirectView withPathVariable1(@PathVariable String path, Model model) throws UnsupportedEncodingException {
|
||||
Pattern p = Pattern.compile(PROTECTED_PATTERN);
|
||||
path = decodePath(path);
|
||||
Matcher m = p.matcher(path);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (model.getAttribute("secAttr") == null || !model.getAttribute("secAttr").equals("secValue")) {
|
||||
RedirectView redirectView = new RedirectView("login", true);
|
||||
return redirectView;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@GetMapping("/sp/{path}")
|
||||
// GOOD: A string with line return e.g. `%252Fprotected%252F%250dxyz` cannot bypass the path check
|
||||
public String withPathVariable2(@PathVariable String path, Model model) throws UnsupportedEncodingException {
|
||||
Pattern p = Pattern.compile(CONSTRAINT_PATTERN);
|
||||
path = decodePath(path);
|
||||
Matcher m = p.matcher(path);
|
||||
|
||||
if (m.matches()) {
|
||||
// Protected page - check access token and redirect to login page
|
||||
if (model.getAttribute("secAttr") == null || !model.getAttribute("secAttr").equals("secValue")) {
|
||||
return "redirect:login";
|
||||
}
|
||||
}
|
||||
// Not protected page - render content
|
||||
return path;
|
||||
}
|
||||
|
||||
private String decodePath(String path) throws UnsupportedEncodingException {
|
||||
while (path.indexOf("%") > -1) {
|
||||
path = URLDecoder.decode(path, "UTF-8");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
edges
|
||||
| DotRegexFilter.java:29:19:29:43 | getPathInfo(...) : String | DotRegexFilter.java:32:25:32:30 | source |
|
||||
| DotRegexServlet.java:19:19:19:39 | getPathInfo(...) : String | DotRegexServlet.java:22:25:22:30 | source |
|
||||
| DotRegexServlet.java:57:19:57:41 | getRequestURI(...) : String | DotRegexServlet.java:59:21:59:26 | source |
|
||||
| DotRegexServlet.java:75:19:75:39 | getPathInfo(...) : String | DotRegexServlet.java:77:56:77:61 | source |
|
||||
| DotRegexServlet.java:112:19:112:39 | getPathInfo(...) : String | DotRegexServlet.java:115:25:115:30 | source |
|
||||
| DotRegexSpring.java:20:26:20:50 | path : String | DotRegexSpring.java:22:21:22:24 | path : String |
|
||||
| DotRegexSpring.java:22:10:22:25 | decodePath(...) : String | DotRegexSpring.java:23:25:23:28 | path |
|
||||
| DotRegexSpring.java:22:21:22:24 | path : String | DotRegexSpring.java:22:10:22:25 | decodePath(...) : String |
|
||||
| DotRegexSpring.java:22:21:22:24 | path : String | DotRegexSpring.java:69:28:69:38 | path : String |
|
||||
| DotRegexSpring.java:37:40:37:64 | path : String | DotRegexSpring.java:39:21:39:24 | path : String |
|
||||
| DotRegexSpring.java:39:10:39:25 | decodePath(...) : String | DotRegexSpring.java:40:25:40:28 | path |
|
||||
| DotRegexSpring.java:39:21:39:24 | path : String | DotRegexSpring.java:39:10:39:25 | decodePath(...) : String |
|
||||
| DotRegexSpring.java:39:21:39:24 | path : String | DotRegexSpring.java:69:28:69:38 | path : String |
|
||||
| DotRegexSpring.java:69:28:69:38 | path : String | DotRegexSpring.java:71:29:71:32 | path : String |
|
||||
| DotRegexSpring.java:69:28:69:38 | path : String | DotRegexSpring.java:73:10:73:13 | path : String |
|
||||
| DotRegexSpring.java:71:11:71:42 | decode(...) : String | DotRegexSpring.java:71:29:71:32 | path : String |
|
||||
| DotRegexSpring.java:71:11:71:42 | decode(...) : String | DotRegexSpring.java:73:10:73:13 | path : String |
|
||||
| DotRegexSpring.java:71:29:71:32 | path : String | DotRegexSpring.java:71:11:71:42 | decode(...) : String |
|
||||
nodes
|
||||
| DotRegexFilter.java:29:19:29:43 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String |
|
||||
| DotRegexFilter.java:32:25:32:30 | source | semmle.label | source |
|
||||
| DotRegexServlet.java:19:19:19:39 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String |
|
||||
| DotRegexServlet.java:22:25:22:30 | source | semmle.label | source |
|
||||
| DotRegexServlet.java:57:19:57:41 | getRequestURI(...) : String | semmle.label | getRequestURI(...) : String |
|
||||
| DotRegexServlet.java:59:21:59:26 | source | semmle.label | source |
|
||||
| DotRegexServlet.java:75:19:75:39 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String |
|
||||
| DotRegexServlet.java:77:56:77:61 | source | semmle.label | source |
|
||||
| DotRegexServlet.java:112:19:112:39 | getPathInfo(...) : String | semmle.label | getPathInfo(...) : String |
|
||||
| DotRegexServlet.java:115:25:115:30 | source | semmle.label | source |
|
||||
| DotRegexSpring.java:20:26:20:50 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:22:10:22:25 | decodePath(...) : String | semmle.label | decodePath(...) : String |
|
||||
| DotRegexSpring.java:22:21:22:24 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:23:25:23:28 | path | semmle.label | path |
|
||||
| DotRegexSpring.java:37:40:37:64 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:39:10:39:25 | decodePath(...) : String | semmle.label | decodePath(...) : String |
|
||||
| DotRegexSpring.java:39:21:39:24 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:40:25:40:28 | path | semmle.label | path |
|
||||
| DotRegexSpring.java:69:28:69:38 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:71:11:71:42 | decode(...) : String | semmle.label | decode(...) : String |
|
||||
| DotRegexSpring.java:71:29:71:32 | path : String | semmle.label | path : String |
|
||||
| DotRegexSpring.java:73:10:73:13 | path : String | semmle.label | path : String |
|
||||
subpaths
|
||||
| DotRegexSpring.java:22:21:22:24 | path : String | DotRegexSpring.java:69:28:69:38 | path : String | DotRegexSpring.java:73:10:73:13 | path : String | DotRegexSpring.java:22:10:22:25 | decodePath(...) : String |
|
||||
| DotRegexSpring.java:39:21:39:24 | path : String | DotRegexSpring.java:69:28:69:38 | path : String | DotRegexSpring.java:73:10:73:13 | path : String | DotRegexSpring.java:39:10:39:25 | decodePath(...) : String |
|
||||
#select
|
||||
| DotRegexFilter.java:32:25:32:30 | source | DotRegexFilter.java:29:19:29:43 | getPathInfo(...) : String | DotRegexFilter.java:32:25:32:30 | source | Potentially authentication bypass due to $@. | DotRegexFilter.java:29:19:29:43 | getPathInfo(...) | user-provided value |
|
||||
| DotRegexServlet.java:22:25:22:30 | source | DotRegexServlet.java:19:19:19:39 | getPathInfo(...) : String | DotRegexServlet.java:22:25:22:30 | source | Potentially authentication bypass due to $@. | DotRegexServlet.java:19:19:19:39 | getPathInfo(...) | user-provided value |
|
||||
| DotRegexServlet.java:59:21:59:26 | source | DotRegexServlet.java:57:19:57:41 | getRequestURI(...) : String | DotRegexServlet.java:59:21:59:26 | source | Potentially authentication bypass due to $@. | DotRegexServlet.java:57:19:57:41 | getRequestURI(...) | user-provided value |
|
||||
| DotRegexServlet.java:77:56:77:61 | source | DotRegexServlet.java:75:19:75:39 | getPathInfo(...) : String | DotRegexServlet.java:77:56:77:61 | source | Potentially authentication bypass due to $@. | DotRegexServlet.java:75:19:75:39 | getPathInfo(...) | user-provided value |
|
||||
| DotRegexServlet.java:115:25:115:30 | source | DotRegexServlet.java:112:19:112:39 | getPathInfo(...) : String | DotRegexServlet.java:115:25:115:30 | source | Potentially authentication bypass due to $@. | DotRegexServlet.java:112:19:112:39 | getPathInfo(...) | user-provided value |
|
||||
| DotRegexSpring.java:23:25:23:28 | path | DotRegexSpring.java:20:26:20:50 | path : String | DotRegexSpring.java:23:25:23:28 | path | Potentially authentication bypass due to $@. | DotRegexSpring.java:20:26:20:50 | path | user-provided value |
|
||||
| DotRegexSpring.java:40:25:40:28 | path | DotRegexSpring.java:37:40:37:64 | path : String | DotRegexSpring.java:40:25:40:28 | path | Potentially authentication bypass due to $@. | DotRegexSpring.java:37:40:37:64 | path | user-provided value |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-625/PermissiveDotRegex.ql
|
||||
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8
|
||||
Reference in New Issue
Block a user