diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java index bd824d08..03513489 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java @@ -53,39 +53,51 @@ import java.util.List; /** * Created by Martin Stockhammer on 19.01.17. - * + *

* This interceptor tries to check if requests come from a valid origin and * are not generated by another site on behalf of the real client. - * + *

* We are using some of the techniques mentioned in * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet - * + *

* Try to find Origin and Referer of the request. * Match them to the target address, that may be either statically configured or is determined * by the Host/X-Forwarded-For Header. - * - * */ @Provider @Service( "requestValidationInterceptor#rest" ) -public class RequestValidationInterceptor extends AbstractInterceptor implements ContainerRequestFilter { +public class RequestValidationInterceptor + extends AbstractInterceptor + implements ContainerRequestFilter +{ private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + private static final String X_FORWARDED_HOST = "X-Forwarded-Host"; + private static final String X_XSRF_TOKEN = "X-XSRF-TOKEN"; + private static final String ORIGIN = "Origin"; + private static final String REFERER = "Referer"; + private static final int DEFAULT_HTTP = 80; + private static final int DEFAULT_HTTPS = 443; private final Logger log = LoggerFactory.getLogger( getClass() ); private boolean enabled = true; + private boolean checkToken = true; + private boolean useStaticUrl = false; + private boolean denyAbsentHeaders = true; + private List baseUrl = new ArrayList(); + private HttpServletRequest httpRequest = null; @Inject @@ -93,258 +105,337 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements private HttpBasicAuthentication httpAuthenticator; @Inject - @Named( value = "tokenManager#default") + @Named( value = "tokenManager#default" ) TokenManager tokenManager; private UserConfiguration config; - - private class HeaderValidationInfo { + + private class HeaderValidationInfo + { final static int UNKNOWN = -1; + final static int OK = 0; + final static int F_REFERER_HOST = 1; + final static int F_REFERER_PORT = 2; + final static int F_ORIGIN_HOST = 8; + final static int F_ORIGIN_PORT = 16; + final static int F_ORIGIN_PROTOCOL = 32; + boolean headerFound = false; URL targetUrl; + URL originUrl; + URL refererUrl; - + String targetHost; + String originHost; + String refererHost; - + int targetPort; + int originPort; + int refererPort; int status = UNKNOWN; - - public HeaderValidationInfo(URL targetUrl) { - setTargetUrl(targetUrl); + + public HeaderValidationInfo( URL targetUrl ) + { + setTargetUrl( targetUrl ); } - public URL getTargetUrl() { + public URL getTargetUrl() + { return targetUrl; } - public void setTargetUrl(URL targetUrl) { + public void setTargetUrl( URL targetUrl ) + { this.targetUrl = targetUrl; - this.targetHost=getHost(targetUrl); - this.targetPort=getPort(targetUrl); + this.targetHost = getHost( targetUrl ); + this.targetPort = getPort( targetUrl ); } - public URL getOriginUrl() { + public URL getOriginUrl() + { return originUrl; } - public void setOriginUrl(URL originUrl) { - this.originUrl=originUrl; - this.originHost=getHost(originUrl); - this.originPort=getPort(originUrl); + public void setOriginUrl( URL originUrl ) + { + this.originUrl = originUrl; + this.originHost = getHost( originUrl ); + this.originPort = getPort( originUrl ); checkOrigin(); - this.headerFound=true; + this.headerFound = true; } - public URL getRefererUrl() { + public URL getRefererUrl() + { return refererUrl; } - public void setRefererUrl(URL refererUrl) { - this.refererUrl=refererUrl; - this.refererHost=getHost(refererUrl); - this.refererPort=getPort(refererUrl); + public void setRefererUrl( URL refererUrl ) + { + this.refererUrl = refererUrl; + this.refererHost = getHost( refererUrl ); + this.refererPort = getPort( refererUrl ); checkReferer(); - this.headerFound=true; + this.headerFound = true; } - public String getTargetHost() { + public String getTargetHost() + { return targetHost; } - public void setTargetHost(String targetHost) { + public void setTargetHost( String targetHost ) + { this.targetHost = targetHost; } - public String getOriginHost() { + public String getOriginHost() + { return originHost; } - public void setOriginHost(String originHost) { + public void setOriginHost( String originHost ) + { this.originHost = originHost; } - public String getRefererHost() { + public String getRefererHost() + { return refererHost; } - public void setRefererHost(String refererHost) { + public void setRefererHost( String refererHost ) + { this.refererHost = refererHost; } - public int getTargetPort() { + public int getTargetPort() + { return targetPort; } - public void setTargetPort(int targetPort) { + public void setTargetPort( int targetPort ) + { this.targetPort = targetPort; } - public int getOriginPort() { + public int getOriginPort() + { return originPort; } - public void setOriginPort(int originPort) { + public void setOriginPort( int originPort ) + { this.originPort = originPort; } - public int getRefererPort() { + public int getRefererPort() + { return refererPort; } - public void setRefererPort(int refererPort) { + public void setRefererPort( int refererPort ) + { this.refererPort = refererPort; } - public void setStatus(int status) { + public void setStatus( int status ) + { this.status |= status; } - public int getStatus() { + public int getStatus() + { return this.status; } // Origin check for Protocol, Host, Port - public void checkOrigin() { - if (this.getStatus()==UNKNOWN) { - this.status=OK; + public void checkOrigin() + { + if ( this.getStatus() == UNKNOWN ) + { + this.status = OK; } - if (!targetUrl.getProtocol().equals(originUrl.getProtocol())) { - setStatus(F_ORIGIN_PROTOCOL); + if ( !targetUrl.getProtocol().equals( originUrl.getProtocol() ) ) + { + setStatus( F_ORIGIN_PROTOCOL ); } - if (!targetHost.equals(originHost)) { - setStatus(F_ORIGIN_HOST); + if ( !targetHost.equals( originHost ) ) + { + setStatus( F_ORIGIN_HOST ); } - if (targetPort!=originPort) { - setStatus(F_ORIGIN_PORT); + if ( targetPort != originPort ) + { + setStatus( F_ORIGIN_PORT ); } } // Referer check only for Host, Port - public void checkReferer() { - if (this.getStatus()==UNKNOWN) { - this.status=OK; + public void checkReferer() + { + if ( this.getStatus() == UNKNOWN ) + { + this.status = OK; } - if (!targetHost.equals(refererHost)) { - setStatus(F_REFERER_HOST); + if ( !targetHost.equals( refererHost ) ) + { + setStatus( F_REFERER_HOST ); } - if (targetPort!=refererPort) { - setStatus(F_REFERER_PORT); + if ( targetPort != refererPort ) + { + setStatus( F_REFERER_PORT ); } } - public boolean hasOriginError() { - return (status & (F_ORIGIN_PROTOCOL | F_ORIGIN_HOST | F_ORIGIN_PORT)) > 0; + public boolean hasOriginError() + { + return ( status & ( F_ORIGIN_PROTOCOL | F_ORIGIN_HOST | F_ORIGIN_PORT ) ) > 0; } - public boolean hasRefererError() { - return (status & (F_REFERER_HOST | F_REFERER_PORT )) > 0; + public boolean hasRefererError() + { + return ( status & ( F_REFERER_HOST | F_REFERER_PORT ) ) > 0; } @Override - public String toString() { - return "Stat="+status+", target="+targetUrl+", origin="+originUrl+", referer="+refererUrl; + public String toString() + { + return "Stat=" + status + ", target=" + targetUrl + ", origin=" + originUrl + ", referer=" + refererUrl; } } @Inject - public RequestValidationInterceptor( - @Named(value = "userConfiguration#default") UserConfiguration config) { + public RequestValidationInterceptor( @Named( value = "userConfiguration#default" ) UserConfiguration config ) + { this.config = config; } @PostConstruct - public void init() { - List baseUrlList = config.getList(UserConfigurationKeys.REST_BASE_URL); - if (baseUrlList!=null) { - for (String baseUrlStr : baseUrlList) { - if (!"".equals(baseUrlStr.trim())) { - try { - baseUrl.add(new URL(baseUrlStr)); + public void init() + { + List baseUrlList = config.getList( UserConfigurationKeys.REST_BASE_URL ); + if ( baseUrlList != null ) + { + for ( String baseUrlStr : baseUrlList ) + { + if ( !"".equals( baseUrlStr.trim() ) ) + { + try + { + baseUrl.add( new URL( baseUrlStr ) ); useStaticUrl = true; - } catch (MalformedURLException ex) { - log.error("Configured baseUrl (rest.baseUrl={}) is invalid. Message: {}", baseUrlStr, ex.getMessage()); + } + catch ( MalformedURLException ex ) + { + log.error( "Configured baseUrl (rest.baseUrl={}) is invalid. Message: {}", baseUrlStr, + ex.getMessage() ); } } } } - denyAbsentHeaders = config.getBoolean(UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY,true); - enabled = config.getBoolean(UserConfigurationKeys.REST_CSRF_ENABLED,true); - if (!enabled) { - log.info("CSRF Filter is disabled by configuration"); - } else { - log.info("CSRF Filter is enable"); + denyAbsentHeaders = config.getBoolean( UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY, true ); + enabled = config.getBoolean( UserConfigurationKeys.REST_CSRF_ENABLED, true ); + if ( !enabled ) + { + log.info( "CSRF Filter is disabled by configuration" ); } - checkToken = !config.getBoolean(UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, false); - if (!checkToken) { - log.info("CSRF Token validation is disabled by configuration"); - } else { - log.info("CSRF Token validation is enable"); + else + { + log.info( "CSRF Filter is enable" ); + } + checkToken = !config.getBoolean( UserConfigurationKeys.REST_CSRF_DISABLE_TOKEN_VALIDATION, false ); + if ( !checkToken ) + { + log.info( "CSRF Token validation is disabled by configuration" ); + } + else + { + log.info( "CSRF Token validation is enable" ); } } @Override - public void filter(ContainerRequestContext containerRequestContext) throws IOException { - if (enabled) { + public void filter( ContainerRequestContext containerRequestContext ) + throws IOException + { + if ( enabled ) + { HttpServletRequest request = getRequest(); - List targetUrls = getTargetUrl(request); - if (targetUrls == null) { - log.error("Could not verify target URL."); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + List targetUrls = getTargetUrl( request ); + if ( targetUrls == null ) + { + log.error( "Could not verify target URL." ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } List validationInfos = new ArrayList(); - boolean targetMatch=false; + boolean targetMatch = false; boolean noHeader = true; - for(URL targetUrl : targetUrls) { - log.trace("Checking against target URL: {}", targetUrl); - HeaderValidationInfo info = checkSourceRequestHeader(new HeaderValidationInfo(targetUrl), request); + for ( URL targetUrl : targetUrls ) + { + log.trace( "Checking against target URL: {}", targetUrl ); + HeaderValidationInfo info = checkSourceRequestHeader( new HeaderValidationInfo( targetUrl ), request ); // We need only one match - noHeader = noHeader && info.getStatus()==info.UNKNOWN; - if (info.getStatus()==info.OK) { - targetMatch=true; + noHeader = noHeader && info.getStatus() == info.UNKNOWN; + if ( info.getStatus() == info.OK ) + { + targetMatch = true; break; - } else { - validationInfos.add(info); + } + else + { + validationInfos.add( info ); } } - if (noHeader && denyAbsentHeaders) { - log.warn("Request denied. No Origin or Referer header found and {}=true", UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + if ( noHeader && denyAbsentHeaders ) + { + log.warn( "Request denied. No Origin or Referer header found and {}=true", + UserConfigurationKeys.REST_CSRF_ABSENTORIGIN_DENY ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } - if (!targetMatch) { - log.warn("HTTP Header check failed. Assuming CSRF attack."); - for(HeaderValidationInfo info : validationInfos) { - if (info.hasOriginError()) { - log.warn("Origin Header does not match: originUrl={}, targetUrl={}. Matches: Host={}, Port={}, Protocol={}", - info.originUrl, info.targetUrl, (info.getStatus()&info.F_ORIGIN_HOST)==0, - (info.getStatus()&info.F_ORIGIN_PORT)==0, (info.getStatus()&info.F_ORIGIN_PROTOCOL)==0); + if ( !targetMatch ) + { + log.warn( "HTTP Header check failed. Assuming CSRF attack." ); + for ( HeaderValidationInfo info : validationInfos ) + { + if ( info.hasOriginError() ) + { + log.warn( + "Origin Header does not match: originUrl={}, targetUrl={}. Matches: Host={}, Port={}, Protocol={}", + info.originUrl, info.targetUrl, ( info.getStatus() & info.F_ORIGIN_HOST ) == 0, + ( info.getStatus() & info.F_ORIGIN_PORT ) == 0, + ( info.getStatus() & info.F_ORIGIN_PROTOCOL ) == 0 ); } - if (info.hasRefererError()) { - log.warn("Referer Header does not match: refererUrl={}, targetUrl={}. Matches: Host={}, Port={}", - info.refererUrl, info.targetUrl, (info.getStatus()&info.F_REFERER_HOST)==0, - (info.getStatus()&info.F_REFERER_PORT)==0); + if ( info.hasRefererError() ) + { + log.warn( + "Referer Header does not match: refererUrl={}, targetUrl={}. Matches: Host={}, Port={}", + info.refererUrl, info.targetUrl, ( info.getStatus() & info.F_REFERER_HOST ) == 0, + ( info.getStatus() & info.F_REFERER_PORT ) == 0 ); } } - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } - if (checkToken) { - checkValidationToken(containerRequestContext, request); + if ( checkToken ) + { + checkValidationToken( containerRequestContext, request ); } } } @@ -356,89 +447,118 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements * @param containerRequestContext * @param request */ - private void checkValidationToken(ContainerRequestContext containerRequestContext, HttpServletRequest request) { + private void checkValidationToken( ContainerRequestContext containerRequestContext, HttpServletRequest request ) + { Message message = JAXRSUtils.getCurrentMessage(); - RedbackAuthorization redbackAuthorization = getRedbackAuthorization(message); + RedbackAuthorization redbackAuthorization = getRedbackAuthorization( message ); // We check only services that are restricted - if (!redbackAuthorization.noRestriction()) { - String tokenString = request.getHeader(X_XSRF_TOKEN); - if (tokenString==null || tokenString.length()==0) { - log.warn("No validation token header found: {}",X_XSRF_TOKEN); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + if ( !redbackAuthorization.noRestriction() ) + { + String tokenString = request.getHeader( X_XSRF_TOKEN ); + if ( tokenString == null || tokenString.length() == 0 ) + { + log.warn( "No validation token header found: {}", X_XSRF_TOKEN ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } - try { - TokenData td = tokenManager.decryptToken(tokenString); - AuthenticationResult auth = getAuthenticationResult(message, request); - if (auth==null) { - log.error("Not authentication data found"); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + try + { + TokenData td = tokenManager.decryptToken( tokenString ); + AuthenticationResult auth = getAuthenticationResult( message, request ); + if ( auth == null ) + { + log.error( "Not authentication data found" ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } User loggedIn = auth.getUser(); - if (loggedIn==null) { - log.error("User not logged in"); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + if ( loggedIn == null ) + { + log.error( "User not logged in" ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); return; } String username = loggedIn.getUsername(); - if (!td.isValid() || !td.getUser().equals(username)) { - log.error("Invalid data in validation token header {} for user {}: isValid={}, username={}", - X_XSRF_TOKEN, username, td.isValid(), td.getUser()); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + if ( !td.isValid() || !td.getUser().equals( username ) ) + { + log.error( "Invalid data in validation token header {} for user {}: isValid={}, username={}", + X_XSRF_TOKEN, username, td.isValid(), td.getUser() ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); } - } catch (InvalidTokenException e) { - log.error("Token validation failed {}", e.getMessage()); - containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build()); + } + catch ( InvalidTokenException e ) + { + log.error( "Token validation failed {}", e.getMessage() ); + containerRequestContext.abortWith( Response.status( Response.Status.FORBIDDEN ).build() ); } } - log.debug("Token validated"); + log.debug( "Token validated" ); } - private HttpServletRequest getRequest() { - if (httpRequest!=null) { + private HttpServletRequest getRequest() + { + if ( httpRequest != null ) + { return httpRequest; - } else { + } + else + { Message message = JAXRSUtils.getCurrentMessage(); - return getHttpServletRequest(message); + return getHttpServletRequest( message ); } } - private List getTargetUrl(HttpServletRequest request) { - if (useStaticUrl) { + private List getTargetUrl( HttpServletRequest request ) + { + if ( useStaticUrl ) + { return baseUrl; - } else { + } + else + { List urls = new ArrayList(); URL requestUrl; - try { - requestUrl = new URL(request.getRequestURL().toString()); - urls.add(requestUrl); - } catch (MalformedURLException ex) { - log.error("Bad Request URL {}, Message: {}", request.getRequestURL(), ex.getMessage()); + try + { + requestUrl = new URL( request.getRequestURL().toString() ); + urls.add( requestUrl ); + } + catch ( MalformedURLException ex ) + { + log.error( "Bad Request URL {}, Message: {}", request.getRequestURL(), ex.getMessage() ); return null; } - String xforwarded = request.getHeader(X_FORWARDED_HOST); - String xforwardedProto = request.getHeader(X_FORWARDED_PROTO); - if (xforwardedProto==null) { - xforwardedProto=requestUrl.getProtocol(); + String xforwarded = request.getHeader( X_FORWARDED_HOST ); + String xforwardedProto = request.getHeader( X_FORWARDED_PROTO ); + if ( xforwardedProto == null ) + { + xforwardedProto = requestUrl.getProtocol(); } - if (xforwarded!=null) { - try { - urls.add(new URL(xforwardedProto+"://"+xforwarded)); - } catch (MalformedURLException ex) { - log.warn("X-Forwarded-Host Header is malformed: {}", ex.getMessage()); + if ( xforwarded != null ) + { + try + { + urls.add( new URL( xforwardedProto + "://" + xforwarded ) ); + } + catch ( MalformedURLException ex ) + { + log.warn( "X-Forwarded-Host Header is malformed: {}", ex.getMessage() ); } } return urls; } } - private int getPort(final URL url) { - return url.getPort() > 0 ? url.getPort() : ("https".equals(url.getProtocol()) ? DEFAULT_HTTPS : DEFAULT_HTTP); + private int getPort( final URL url ) + { + return url.getPort() > 0 + ? url.getPort() + : ( "https".equals( url.getProtocol() ) ? DEFAULT_HTTPS : DEFAULT_HTTP ); } - private String getHost(final URL url) { + private String getHost( final URL url ) + { return url.getHost().trim().toLowerCase(); } @@ -446,47 +566,60 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements * Checks the validation headers. First the Origin header is checked, if this fails * or is absent, the referer header is checked. * - * @param info The info object that must be populated with the targetURL + * @param info The info object that must be populated with the targetURL * @param request The HTTP request object * @return A info object with updated status information */ - private HeaderValidationInfo checkSourceRequestHeader(final HeaderValidationInfo info, final HttpServletRequest request) { - String origin = request.getHeader(ORIGIN); - if (origin!=null) { - try { - info.setOriginUrl(new URL(origin)); - } catch (MalformedURLException e) { - log.warn("Bad origin header found: {}", origin); + private HeaderValidationInfo checkSourceRequestHeader( final HeaderValidationInfo info, + final HttpServletRequest request ) + { + String origin = request.getHeader( ORIGIN ); + if ( origin != null ) + { + try + { + info.setOriginUrl( new URL( origin ) ); + } + catch ( MalformedURLException e ) + { + log.warn( "Bad origin header found: {}", origin ); } } // Check referer if Origin header dos not match or is not available - if (info.getStatus()!=info.OK) { - String referer = request.getHeader(REFERER); - if (referer != null) { - try { - info.setRefererUrl(new URL(referer)); - } catch (MalformedURLException ex) { - log.warn("Bad URL in Referer HTTP-Header: {}, Message: {}", referer, ex.getMessage()); + if ( info.getStatus() != info.OK ) + { + String referer = request.getHeader( REFERER ); + if ( referer != null ) + { + try + { + info.setRefererUrl( new URL( referer ) ); + } + catch ( MalformedURLException ex ) + { + log.warn( "Bad URL in Referer HTTP-Header: {}, Message: {}", referer, ex.getMessage() ); } } } return info; } - public void setHttpRequest(HttpServletRequest request) { + public void setHttpRequest( HttpServletRequest request ) + { this.httpRequest = request; } - private AuthenticationResult getAuthenticationResult(Message message, HttpServletRequest request) { - AuthenticationResult authenticationResult = message.get(AuthenticationResult.class); + private AuthenticationResult getAuthenticationResult( Message message, HttpServletRequest request ) + { + AuthenticationResult authenticationResult = message.get( AuthenticationResult.class ); - log.debug("authenticationResult from message: {}", authenticationResult); + log.debug( "authenticationResult from message: {}", authenticationResult ); if ( authenticationResult == null ) { try { authenticationResult = - httpAuthenticator.getAuthenticationResult( request, getHttpServletResponse( message ) ); + httpAuthenticator.getAuthenticationResult( request, getHttpServletResponse( message ) ); log.debug( "authenticationResult from request: {}", authenticationResult ); }