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
4 changes: 4 additions & 0 deletions java/ql/lib/change-notes/2026-09-15-uri-host-comparisons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The queries `java/ssrf` and `java/unvalidated-url-redirection` recognize case-insensitive comparisons of `URI.getHost()` with fixed string allowlist entries, as well as equality and case-insensitive comparisons when the host is stored in a local variable.
34 changes: 30 additions & 4 deletions java/ql/lib/semmle/code/java/security/RequestForgery.qll
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import semmle.code.java.frameworks.Properties
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.Sanitizers

/**
Expand Down Expand Up @@ -122,11 +123,21 @@ private class ExternalRequestForgerySanitizer extends RequestForgerySanitizer {
ExternalRequestForgerySanitizer() { barrierNode(this, "request-forgery") }
}

/**
* A comparison on the host of a url, that is a sanitizer for URL redirects.
* E.g. `"example.org".equals(url.getHost())"`
*/
/** Gets a host read, following local assignments with a single explicit SSA definition. */
private MethodCall getCheckedHost(Expr checked) {
result = checked.getUnderlyingExpr() and
result.getMethod().hasQualifiedName("java.net", "URI", "getHost")
or
exists(SsaExplicitWrite def |
checked.getUnderlyingExpr().(VarRead).getVariable() instanceof LocalScopeVariable and
def.getARead() = checked.getUnderlyingExpr() and
result = getCheckedHost(def.getValue())
)
}

/** Holds if a comparison validates the host of a URI. */
private predicate isHostComparisonSanitizer(Guard guard, Expr e, boolean branch) {
// Direct equality checks also accept nonconstant comparison operands.
guard =
any(MethodCall equalsCall |
equalsCall.getMethod().getName() = "equals" and
Expand All @@ -137,6 +148,21 @@ private predicate isHostComparisonSanitizer(Guard guard, Expr e, boolean branch)
e = hostCall.getQualifier()
)
)
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
e = getCheckedHost(checked).getQualifier()
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import java.net.URI;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HostComparisonRedirect {
public void unguarded(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
response.sendRedirect(uri.toString()); // $ Alert
}

public void directEquals(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
if ("example.com".equals(uri.getHost())) {
response.sendRedirect(uri.toString());
}
}

public void directEqualsIgnoreCase(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
if ("example.com".equalsIgnoreCase(uri.getHost())) {
response.sendRedirect(uri.toString());
}
}

public void reversedEqualsIgnoreCase(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
if (uri.getHost().equalsIgnoreCase("example.com")) {
response.sendRedirect(uri.toString());
}
}

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

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

public void streamEquals(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ SPURIOUS: Source
if (Arrays.stream(new String[]{"example.com"}).anyMatch(uri.getHost()::equals)) {
response.sendRedirect(uri.toString()); // $ SPURIOUS: Alert
}
}

public void reportedStreamHelper(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ SPURIOUS: Source
if (isAllowedStream(uri.getHost(), "example.com")) {
response.sendRedirect(uri.toString()); // $ SPURIOUS: Alert
}
}

private static boolean isAllowedStream(String host, String... allowedHosts) {
return Arrays.stream(allowedHosts).anyMatch(host::equalsIgnoreCase);
}

public void reportedLoopHelper(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ SPURIOUS: Source
if (isAllowedLoop(uri.getHost(), "example.com")) {
response.sendRedirect(uri.toString()); // $ SPURIOUS: Alert
}
}

private static boolean isAllowedLoop(String host, String... allowedHosts) {
for (String allowed : allowedHosts) {
if (allowed.equalsIgnoreCase(host)) {
return true;
}
}
return false;
}

public void wrongBranch(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
if (!"example.com".equals(uri.getHost())) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void differentUri(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI checked = new URI(request.getParameter("checked"));
URI used = new URI(request.getParameter("url")); // $ Source
if ("example.com".equals(checked.getHost())) {
response.sendRedirect(used.toString()); // $ Alert
}
}

public void constantUrl(HttpServletResponse response) throws Exception {
response.sendRedirect("https://example.com/");
}

public void caseInsensitiveWrongBranch(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
if (!"example.com".equalsIgnoreCase(uri.getHost())) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void caseInsensitiveDifferentUri(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI checked = new URI(request.getParameter("checked"));
URI used = new URI(request.getParameter("url")); // $ Source
if ("example.com".equalsIgnoreCase(checked.getHost())) {
response.sendRedirect(used.toString()); // $ Alert
}
}

public void reassignedHost(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
host = "example.com";
if ("example.com".equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void conditionallyReassignedHost(HttpServletRequest request, HttpServletResponse response, boolean skip) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
if (skip) {
host = "example.com";
}
if ("example.com".equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void reassignedUri(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI("https://example.com/");
String host = uri.getHost();
uri = new URI(request.getParameter("url")); // $ Source
if ("example.com".equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void untrustedComparison(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
if (request.getParameter("allowed").equalsIgnoreCase(uri.getHost())) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void unrelatedComparison(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
if (new FakeComparison().equalsIgnoreCase(uri.getHost())) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

private static class FakeComparison {
boolean equalsIgnoreCase(String host) {
return true;
}
}

public void aliasChain(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
String host = uri.getHost();
String alias = host;
String secondAlias = alias;
if ("example.com".equalsIgnoreCase(secondAlias)) {
response.sendRedirect(uri.toString());
}
}

public void finallyOverwrite(HttpServletRequest request, HttpServletResponse response, boolean skip) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
try {
if (skip) return;
host = "example.com";
} finally {
if ("example.com".equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}
}

public void rejectionReturns(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
String host = uri.getHost();
if (!"example.com".equalsIgnoreCase(host)) return;
response.sendRedirect(uri.toString());
}

private static final String ALLOWED_HOST = "example.com";

public void constantAllowlistField(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
String host = uri.getHost();
if (ALLOWED_HOST.equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString());
}
}

public void orBypass(HttpServletRequest request, HttpServletResponse response, boolean skip) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
String host = uri.getHost();
if ("example.com".equalsIgnoreCase(host) || skip) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void assignmentCast(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
Object value = uri.getHost();
String host = (String) value;
if ("example.com".equalsIgnoreCase(host)) {
response.sendRedirect(uri.toString());
}
}

public void comparisonCast(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url"));
Object host = uri.getHost();
if ("example.com".equalsIgnoreCase((String) host)) {
response.sendRedirect(uri.toString());
}
}

public void castHostOverwrite(HttpServletRequest request, HttpServletResponse response) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
Object host = uri.getHost();
host = "example.com";
if ("example.com".equalsIgnoreCase((String) host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}

public void castHostConditionalOverwrite(HttpServletRequest request, HttpServletResponse response, boolean skip) throws Exception {
URI uri = new URI(request.getParameter("url")); // $ Source
Object host = uri.getHost();
if (skip) host = "example.com";
if ("example.com".equalsIgnoreCase((String) host)) {
response.sendRedirect(uri.toString()); // $ Alert
}
}
}
Loading