Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Java taint-tracking queries now recognize equality and case-insensitive equality checks against fixed string constants as sanitizers. The queries `java/ssrf` and `java/unvalidated-url-redirection` also recognize checks of `URI.getHost()` against fixed collections of strings, including when the host is normalized to upper or lower case.
2 changes: 1 addition & 1 deletion java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.ThreadLocal
private import semmle.code.java.frameworks.ratpack.RatpackExec
private import semmle.code.java.frameworks.stapler.Stapler
private import semmle.code.java.security.ListOfConstantsSanitizer
private import semmle.code.java.security.ConstantValueSanitizer
private import semmle.code.java.security.PathSanitizer
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/**
* Provides a default taint sanitizer identifying comparisons against lists of
* compile-time constants.
*/
/** Provides a default taint sanitizer identifying checks against compile-time constants. */
overlay[local?]
module;

Expand Down Expand Up @@ -257,11 +254,39 @@ private predicate constantCollectionContainsCheck(Guard g, Expr e, boolean branc
}

/**
* A comparison against a list of compile-time constants, sanitizing taint by
* restricting to a set of known values.
* Holds if the guard `g` ensures that the string expression `e` equals a compile-time constant
* upon evaluating to `branch`.
*/
private predicate constantStringEqualityCheck(Guard g, Expr e, boolean branch) {
exists(MethodCall comparison, Expr constant |
g = comparison and
comparison.getMethod().getDeclaringType() instanceof TypeString and
comparison.getMethod().hasName(["equals", "equalsIgnoreCase"]) and
branch = true and
(
e = comparison.getQualifier() and constant = comparison.getArgument(0)
or
e = comparison.getArgument(0) and constant = comparison.getQualifier()
) and
exists(constant.(CompileTimeConstantExpr).getStringValue())
)
}

/**
* Holds if the guard `g` ensures that the expression `e` is one of a set of compile-time constants
* upon evaluating to `branch`.
*/
predicate constantValueGuardChecks(Guard g, Expr e, boolean branch) {
constantStringEqualityCheck(g, e, branch)
or
constantCollectionContainsCheck(g, e, branch)
}

/**
* A check against compile-time constants, sanitizing taint by restricting a value to a known set.
*/
private class ListOfConstantsComparisonSanitizerGuard extends TaintTracking::DefaultTaintSanitizer {
ListOfConstantsComparisonSanitizerGuard() {
this = DataFlow::BarrierGuard<constantCollectionContainsCheck/3>::getABarrierNode()
private class ConstantValueSanitizerGuard extends TaintTracking::DefaultTaintSanitizer {
ConstantValueSanitizerGuard() {
this = DataFlow::BarrierGuard<constantValueGuardChecks/3>::getABarrierNode()
}
}
15 changes: 3 additions & 12 deletions java/ql/lib/semmle/code/java/security/RequestForgery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.StringPrefixes
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.security.ConstantValueSanitizer
private import semmle.code.java.security.Sanitizers

/**
Expand Down Expand Up @@ -149,18 +150,8 @@ private predicate isHostComparisonSanitizer(Guard guard, Expr e, boolean branch)
)
)
or
exists(MethodCall comparison, Expr checked, Expr allowed |
guard = comparison and
comparison.getMethod().getDeclaringType() instanceof TypeString and
comparison.getMethod().hasName(["equals", "equalsIgnoreCase"]) and
branch = true and
(
checked = comparison.getQualifier() and allowed = comparison.getArgument(0)
or
checked = comparison.getArgument(0) and allowed = comparison.getQualifier()
) and
// New cases require a fixed allowlist entry, not another untrusted value.
exists(allowed.(CompileTimeConstantExpr).getStringValue()) and
exists(Expr checked |
constantValueGuardChecks(guard, checked, branch) and
e = getCheckedHost(checked).getQualifier()
Comment on lines +154 to 155
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| Type ConstantValue uses out-of-scope type variable E. Note the Java extractor is known to sometimes do this; the Kotlin extractor should not. |
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import java.util.*;

public class A {
public class ConstantValue {
private static final Set<String> SEPARATORS =
Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("\t", "\n", ";")));
Expand Down Expand Up @@ -32,4 +32,28 @@ private void testAdd(String inp) {
sink(inp);
}
}

private void checkEquals(String input) {
if ("AA".equals(input)) {
sink(input);
}
}

private void checkEqualsIgnoreCase(String input) {
if (input.equalsIgnoreCase("AA")) {
sink(input);
}
}

private void checkWrongBranch(String input) {
if (!"AA".equals(input)) {
sink(input);
}
}

private void checkUntrustedValue(String input, String allowed) {
if (allowed.equals(input)) {
sink(input);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
| ConstantValue.java:12:12:12:20 | separator |
| ConstantValue.java:23:12:23:17 | schema |
| ConstantValue.java:32:12:32:14 | inp |
| ConstantValue.java:38:12:38:16 | input |
| ConstantValue.java:44:12:44:16 | input |

This file was deleted.

3 changes: 0 additions & 3 deletions java/ql/test/library-tests/listofconstants/test.expected

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import java.net.URI;
import java.util.Arrays;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

Expand Down Expand Up @@ -203,6 +204,30 @@ public void constantAllowlistField(HttpServletRequest request, HttpServletRespon
}
}

public void constantCollection(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
String host = uri.getHost();
if (Set.of("example.com", "example.org").contains(host)) {
response.sendRedirect(uri.toString());
}
}

public void normalizedConstantCollection(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
String host = uri.getHost();
if (Set.of("EXAMPLE.COM", "EXAMPLE.ORG").contains(host.toUpperCase())) {
response.sendRedirect(uri.toString());
}
}

public void untrustedCollection(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
if (Set.of(request.getParameter("allowed")).contains(host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void orBypass(HttpServletRequest request, HttpServletResponse response, boolean skip) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
Expand Down
Loading
Loading