Skip to content

Commit

Permalink
fix: ensure Referer header is provided or check allowed user agent
Browse files Browse the repository at this point in the history
  • Loading branch information
ymarcon committed Dec 22, 2024
1 parent d760f04 commit 0121605
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,36 @@ public class CSRFInterceptor extends AbstractSecurityComponent implements Reques

private static final String REFERER_HEADER = "Referer";

private static final String USER_AGENT_HEADER = "User-Agent";

private static final Pattern localhostPattern = Pattern.compile("^http[s]?://localhost:.*");

private static final Pattern loopbackhostPattern = Pattern.compile("^http[s]?://127\\.0\\.0\\.1:.*");

private final boolean productionMode;

private final List<String> csrfAllowed;
private final List<String> csrfAllowedHosts;

private final List<String> csrfAllowedAgents;

@Autowired
public CSRFInterceptor(@Value("${productionMode}") boolean productionMode,
@Value("${csrf.allowed}") String csrfAllowed) {
@Value("${csrf.allowed}") String csrfAllowedHosts,
@Value("${csrf.allowed-agents}") String csrfAllowedAgents) {
this.productionMode = productionMode;
this.csrfAllowed = Strings.isNullOrEmpty(csrfAllowed) ? Lists.newArrayList() : Splitter.on(",").splitToList(csrfAllowed.trim());
this.csrfAllowedHosts = Strings.isNullOrEmpty(csrfAllowedHosts) ? Lists.newArrayList() : Splitter.on(",").splitToList(csrfAllowedHosts.trim());
this.csrfAllowedAgents = Strings.isNullOrEmpty(csrfAllowedAgents) ? Lists.newArrayList() : Splitter.on(",").splitToList(csrfAllowedAgents.trim());
}

@Override
public void preProcess(HttpServletRequest httpServletRequest, ResourceMethodInvoker resourceMethod, ContainerRequestContext requestContext) {
if (!productionMode || csrfAllowed.contains("*")) return;
if (!productionMode || csrfAllowedHosts.contains("*")) return;

String host = requestContext.getHeaderString(HOST_HEADER);
if (matchesLocalhost(host)) return;

String referer = requestContext.getHeaderString(REFERER_HEADER);
if (referer != null) {
if (!Strings.isNullOrEmpty(referer)) {
String refererHostPort = "";
try {
URI refererURI = URI.create(referer);
Expand All @@ -70,20 +78,22 @@ public void preProcess(HttpServletRequest httpServletRequest, ResourceMethodInvo
// malformed url
}
// explicitly ok
if (csrfAllowed.contains(refererHostPort)) return;

boolean forbidden = false;
if (!matchesLocalhost(host) && !referer.startsWith(String.format("https://%s/", host))) {
forbidden = true;
}
if (csrfAllowedHosts.contains(refererHostPort)) return;

boolean forbidden = !referer.startsWith(String.format("https://%s/", host));
if (forbidden) {
log.warn("CSRF detection: Host={}, Referer={}", host, referer);
log.info(">> You can add {} to csrf.allowed setting", refererHostPort);
throw new ForbiddenException("CSRF error");
}
} else {
String userAgent = requestContext.getHeaderString(USER_AGENT_HEADER);
if (Strings.isNullOrEmpty(userAgent) || !matchesUserAgent(userAgent)) {
log.warn("CSRF detection: Host={}, User-Agent={}", host, userAgent);
log.info(">> Ensure 'Referer' HTTP header is set or allow this 'User-Agent' with 'csrf.allowed-agents' setting");
throw new ForbiddenException("CSRF error");
}
}
return;
}

private boolean matchesLocalhost(String host) {
Expand All @@ -93,13 +103,8 @@ private boolean matchesLocalhost(String host) {
|| host.startsWith("127.0.0.1:");
}

static String asHeader(Iterable<String> values) {
StringBuilder sb = new StringBuilder();
for (String s : values) {
if (!sb.isEmpty()) sb.append(", ");
sb.append(s);
}
return sb.toString();
private boolean matchesUserAgent(String userAgent) {
return csrfAllowedAgents.stream().anyMatch(ua -> userAgent.toLowerCase().contains(ua.toLowerCase()));
}

}
4 changes: 3 additions & 1 deletion opal-core/src/main/resources/META-INF/defaults.properties
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ apps.registration.exclude=
apps.discovery.interval = 10000

# CSRF
# allowed referers, comma separated <host:port>
# allowed referrers, comma separated <host:port>
csrf.allowed=
# allowed user agents when referrer is not specified
csrf.allowed-agents=curl,python

# CORS
# use * as wildcard, separate origins with commas
Expand Down

0 comments on commit 0121605

Please sign in to comment.