Denial of Service in org.springframework:spring-core
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
Details
Spring Framework, versions 5.0.x prior to 5.0.6, versions 4.3.x prior to 4.3.17, and older unsupported versions allows applications to expose STOMP over WebSocket endpoints with a simple, in-memory STOMP broker through the spring-messaging module. A malicious user (or attacker) can craft a message to the broker that can lead to a regular expression, denial of service attack.
The fix
Selector header name is exposed for configuration
spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java+32 −24
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -43,6 +43,7 @@import org.springframework.util.LinkedMultiValueMap;import org.springframework.util.MultiValueMap;import org.springframework.util.PathMatcher;+import org.springframework.util.StringUtils;/*** Implementation of {@link SubscriptionRegistry} that stores subscriptions@@ -113,24 +114,25 @@ public int getCacheLimit() {}/**-* Configure the name of a selector header that a subscription message can-* have in order to filter messages based on their headers. The value of the-* header can use Spring EL expressions against message headers.-* <p>For example the following expression expects a header called "foo" to-* have the value "bar":+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:* <pre>* headers.foo == 'bar'* </pre>-* <p>By default this is set to "selector".+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header* @since 4.2*/public void setSelectorHeaderName(String selectorHeaderName) {-Assert.notNull(selectorHeaderName, "'selectorHeaderName' must not be null");-this.selectorHeaderName = selectorHeaderName;+this.selectorHeaderName = StringUtils.hasText(selectorHeaderName) ? selectorHeaderName : null;}/**-* Return the name for the selector header.+* Return the name for the selector header name.* @since 4.2*/public String getSelectorHeaderName() {@@ -142,25 +144,31 @@ public String getSelectorHeaderName() {protected void addSubscriptionInternal(String sessionId, String subsId, String destination, Message<?> message) {+Expression expression = getSelectorExpression(message.getHeaders());+this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);+this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);+}++private Expression getSelectorExpression(MessageHeaders headers) {Expression expression = null;-MessageHeaders headers = message.getHeaders();-String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);-if (selector != null) {-try {-expression = this.expressionParser.parseExpression(selector);-this.selectorHeaderInUse = true;-if (logger.isTraceEnabled()) {-logger.trace("Subscription selector: [" + selector + "]");+if (getSelectorHeaderName() != null) {+String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);+if (selector != null) {+try {+expression = this.expressionParser.parseExpression(selector);+this.selectorHeaderInUse = true;+if (logger.isTraceEnabled()) {+logger.trace("Subscription selector: [" + selector + "]");+}}-}-catch (Throwable ex) {-if (logger.isDebugEnabled()) {-logger.debug("Failed to parse selector: " + selector, ex);+catch (Throwable ex) {+if (logger.isDebugEnabled()) {+logger.debug("Failed to parse selector: " + selector, ex);+}}}}-this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);-this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);+return expression;}@Override
spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java+38 −6
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -51,23 +51,27 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {private static final byte[] EMPTY_PAYLOAD = new byte[0];-private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<String, SessionInfo>();--private SubscriptionRegistry subscriptionRegistry;private PathMatcher pathMatcher;private Integer cacheLimit;+private String selectorHeaderName = "selector";+private TaskScheduler taskScheduler;private long[] heartbeatValue;-private ScheduledFuture<?> heartbeatFuture;-private MessageHeaderInitializer headerInitializer;+private SubscriptionRegistry subscriptionRegistry;++private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<String, SessionInfo>();++private ScheduledFuture<?> heartbeatFuture;++/*** Create a SimpleBrokerMessageHandler instance with the given message channels* and destination prefixes.@@ -96,12 +100,40 @@ public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {this.subscriptionRegistry = subscriptionRegistry;initPathMatcherToUse();initCacheLimitToUse();+initSelectorHeaderNameToUse();}public SubscriptionRegistry getSubscriptionRegistry() {return this.subscriptionRegistry;}+/**+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:+* <pre>+* headers.foo == 'bar'+* </pre>+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header+* @since 4.3.17+* @see #setSubscriptionRegistry+* @see DefaultSubscriptionRegistry#setSelectorHeaderName(String)+*/+public void setSelectorHeaderName(String selectorHeaderName) {+this.selectorHeaderName = selectorHeaderName;+initSelectorHeaderNameToUse();+}++private void initSelectorHeaderNameToUse() {+if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {+((DefaultSubscriptionRegistry) this.subscriptionRegistry).setSelectorHeaderName(this.selectorHeaderName);+}+}+/*** When configured, the given PathMatcher is passed down to the underlying* SubscriptionRegistry to use for matching destination to subscriptions.
spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java+22 −1
@@ -1,5 +1,5 @@/*-* Copyright 2002-2014 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -33,6 +33,8 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration {private long[] heartbeat;+private String selectorHeaderName = "selector";+public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) {super(inChannel, outChannel, prefixes);@@ -65,6 +67,24 @@ public SimpleBrokerRegistration setHeartbeatValue(long[] heartbeat) {return this;}+/**+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:+* <pre>+* headers.foo == 'bar'+* </pre>+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header+* @since 4.3.17+*/+public void setSelectorHeaderName(String selectorHeaderName) {+this.selectorHeaderName = selectorHeaderName;+}+@Overrideprotected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {@@ -76,6 +96,7 @@ protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokeif (this.heartbeat != null) {handler.setHeartbeatValue(this.heartbeat);}+handler.setSelectorHeaderName(this.selectorHeaderName);return handler;}
spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java+25 −0
@@ -264,6 +264,8 @@ public void registerSubscriptionWithSelector() throws Exception {String destination = "/foo";String selector = "headers.foo == 'bar'";+// First, try with selector header+this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector));SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();@@ -276,11 +278,34 @@ public void registerSubscriptionWithSelector() throws Exception {assertEquals(1, actual.size());assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId));+// Then without+actual = this.registry.findSubscriptions(createMessage(destination));assertNotNull(actual);assertEquals(0, actual.size());}+@Test+public void registerSubscriptionWithSelectorNotSupported() {+String sessionId = "sess01";+String subscriptionId = "subs01";+String destination = "/foo";+String selector = "headers.foo == 'bar'";++this.registry.setSelectorHeaderName(null);+this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector));++SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();+accessor.setDestination(destination);+accessor.setNativeHeader("foo", "bazz");+Message<?> message = MessageBuilder.createMessage("", accessor.getMessageHeaders());++MultiValueMap<String, String> actual = this.registry.findSubscriptions(message);+assertNotNull(actual);+assertEquals(1, actual.size());+assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId));+}+@Test // SPR-11931public void registerSubscriptionTwiceAndUnregister() {this.registry.registerSubscription(subscribeMessage("sess01", "subs01", "/foo"));
spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java+5 −1
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -381,6 +381,10 @@ private RootBeanDefinition registerMessageBroker(Element brokerElement,String heartbeatValue = simpleBrokerElem.getAttribute("heartbeat");brokerDef.getPropertyValues().add("heartbeatValue", heartbeatValue);}+if (simpleBrokerElem.hasAttribute("selector-header")) {+String headerName = simpleBrokerElem.getAttribute("selector-header");+brokerDef.getPropertyValues().add("selectorHeaderName", headerName);+}}else if (brokerRelayElem != null) {String prefix = brokerRelayElem.getAttribute("prefix");
spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket-4.3.xsd+16 −0
@@ -384,6 +384,22 @@]]></xsd:documentation></xsd:annotation></xsd:attribute>+<xsd:attribute name="selector-header" type="xsd:string">+<xsd:annotation>+<xsd:documentation source="java:org.springframework.messaging.simp.stomp.SimpleBrokerMessageHandler"><![CDATA[+Configure the name of a header that a subscription message can have for+the purpose of filtering messages matched to the subscription. The header+value is expected to be a Spring EL boolean expression to be applied to+the headers of messages matched to the subscription.++For example:+headers.foo == 'bar'++By default this is set to "selector". You can set it to a different+name, or to "" to turn off support for a selector header.+]]></xsd:documentation>+</xsd:annotation>+</xsd:attribute></xsd:complexType><xsd:complexType name="channel">
spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java+4 −1
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -45,6 +45,7 @@import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;import org.springframework.messaging.simp.SimpMessagingTemplate;import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;+import org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry;import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;@@ -199,6 +200,8 @@ public void simpleBroker() throws Exception {assertNotNull(brokerMessageHandler);Collection<String> prefixes = brokerMessageHandler.getDestinationPrefixes();assertEquals(Arrays.asList("/topic", "/queue"), new ArrayList<String>(prefixes));+DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) brokerMessageHandler.getSubscriptionRegistry();+assertEquals("my-selector", registry.getSelectorHeaderName());assertNotNull(brokerMessageHandler.getTaskScheduler());assertArrayEquals(new long[] {15000, 15000}, brokerMessageHandler.getHeartbeatValue());
spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml+2 −1
@@ -35,7 +35,8 @@<websocket:stomp-error-handler ref="errorHandler" />-<websocket:simple-broker prefix="/topic, /queue" heartbeat="15000,15000" scheduler="scheduler" />+<websocket:simple-broker prefix="/topic, /queue" selector-header="my-selector"+heartbeat="15000,15000" scheduler="scheduler" /></websocket:message-broker>
Selector header name is exposed for configuration
spring-messaging/src/main/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java+35 −24
@@ -44,6 +44,7 @@import org.springframework.util.LinkedMultiValueMap;import org.springframework.util.MultiValueMap;import org.springframework.util.PathMatcher;+import org.springframework.util.StringUtils;/*** Implementation of {@link SubscriptionRegistry} that stores subscriptions@@ -73,6 +74,7 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;+@Nullableprivate String selectorHeaderName = "selector";private volatile boolean selectorHeaderInUse = false;@@ -114,26 +116,28 @@ public int getCacheLimit() {}/**-* Configure the name of a selector header that a subscription message can-* have in order to filter messages based on their headers. The value of the-* header can use Spring EL expressions against message headers.-* <p>For example the following expression expects a header called "foo" to-* have the value "bar":+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:* <pre>* headers.foo == 'bar'* </pre>-* <p>By default this is set to "selector".+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header* @since 4.2*/-public void setSelectorHeaderName(String selectorHeaderName) {-Assert.notNull(selectorHeaderName, "'selectorHeaderName' must not be null");-this.selectorHeaderName = selectorHeaderName;+public void setSelectorHeaderName(@Nullable String selectorHeaderName) {+this.selectorHeaderName = StringUtils.hasText(selectorHeaderName) ? selectorHeaderName : null;}/**-* Return the name for the selector header.+* Return the name for the selector header name.* @since 4.2*/+@Nullablepublic String getSelectorHeaderName() {return this.selectorHeaderName;}@@ -143,25 +147,32 @@ public String getSelectorHeaderName() {protected void addSubscriptionInternal(String sessionId, String subsId, String destination, Message<?> message) {+Expression expression = getSelectorExpression(message.getHeaders());+this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);+this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);+}++@Nullable+private Expression getSelectorExpression(MessageHeaders headers) {Expression expression = null;-MessageHeaders headers = message.getHeaders();-String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);-if (selector != null) {-try {-expression = this.expressionParser.parseExpression(selector);-this.selectorHeaderInUse = true;-if (logger.isTraceEnabled()) {-logger.trace("Subscription selector: [" + selector + "]");+if (getSelectorHeaderName() != null) {+String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);+if (selector != null) {+try {+expression = this.expressionParser.parseExpression(selector);+this.selectorHeaderInUse = true;+if (logger.isTraceEnabled()) {+logger.trace("Subscription selector: [" + selector + "]");+}}-}-catch (Throwable ex) {-if (logger.isDebugEnabled()) {-logger.debug("Failed to parse selector: " + selector, ex);+catch (Throwable ex) {+if (logger.isDebugEnabled()) {+logger.debug("Failed to parse selector: " + selector, ex);+}}}}-this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);-this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);+return expression;}@Override
spring-messaging/src/main/java/org/springframework/messaging/simp/broker/SimpleBrokerMessageHandler.java+38 −5
@@ -51,9 +51,6 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {private static final byte[] EMPTY_PAYLOAD = new byte[0];-private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();--private SubscriptionRegistry subscriptionRegistry;@Nullableprivate PathMatcher pathMatcher;@@ -61,6 +58,9 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {@Nullableprivate Integer cacheLimit;+@Nullable+private String selectorHeaderName = "selector";+@Nullableprivate TaskScheduler taskScheduler;@@ -68,10 +68,15 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {private long[] heartbeatValue;@Nullable-private ScheduledFuture<?> heartbeatFuture;+private MessageHeaderInitializer headerInitializer;+++private SubscriptionRegistry subscriptionRegistry;++private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();@Nullable-private MessageHeaderInitializer headerInitializer;+private ScheduledFuture<?> heartbeatFuture;/**@@ -102,6 +107,7 @@ public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {this.subscriptionRegistry = subscriptionRegistry;initPathMatcherToUse();initCacheLimitToUse();+initSelectorHeaderNameToUse();}public SubscriptionRegistry getSubscriptionRegistry() {@@ -149,6 +155,33 @@ private void initCacheLimitToUse() {}}+/**+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:+* <pre>+* headers.foo == 'bar'+* </pre>+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header+* @since 4.3.17+* @see #setSubscriptionRegistry+* @see DefaultSubscriptionRegistry#setSelectorHeaderName(String)+*/+public void setSelectorHeaderName(@Nullable String selectorHeaderName) {+this.selectorHeaderName = selectorHeaderName;+initSelectorHeaderNameToUse();+}++private void initSelectorHeaderNameToUse() {+if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {+((DefaultSubscriptionRegistry) this.subscriptionRegistry).setSelectorHeaderName(this.selectorHeaderName);+}+}+/*** Configure the {@link org.springframework.scheduling.TaskScheduler} to* use for providing heartbeat support. Setting this property also sets the
spring-messaging/src/main/java/org/springframework/messaging/simp/config/SimpleBrokerRegistration.java+23 −1
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -36,6 +36,9 @@ public class SimpleBrokerRegistration extends AbstractBrokerRegistration {@Nullableprivate long[] heartbeat;+@Nullable+private String selectorHeaderName = "selector";+public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) {super(inChannel, outChannel, prefixes);@@ -68,6 +71,24 @@ public SimpleBrokerRegistration setHeartbeatValue(long[] heartbeat) {return this;}+/**+* Configure the name of a header that a subscription message can have for+* the purpose of filtering messages matched to the subscription. The header+* value is expected to be a Spring EL boolean expression to be applied to+* the headers of messages matched to the subscription.+* <p>For example:+* <pre>+* headers.foo == 'bar'+* </pre>+* <p>By default this is set to "selector". You can set it to a different+* name, or to {@code null} to turn off support for a selector header.+* @param selectorHeaderName the name to use for a selector header+* @since 4.3.17+*/+public void setSelectorHeaderName(@Nullable String selectorHeaderName) {+this.selectorHeaderName = selectorHeaderName;+}+@Overrideprotected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {@@ -79,6 +100,7 @@ protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokeif (this.heartbeat != null) {handler.setHeartbeatValue(this.heartbeat);}+handler.setSelectorHeaderName(this.selectorHeaderName);return handler;}
spring-messaging/src/test/java/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistryTests.java+26 −1
@@ -258,7 +258,7 @@ public void registerSubscriptionWithDestinationPatternRegex() {}@Test-public void registerSubscriptionWithSelector() throws Exception {+public void registerSubscriptionWithSelector() {String sessionId = "sess01";String subscriptionId = "subs01";String destination = "/foo";@@ -266,6 +266,8 @@ public void registerSubscriptionWithSelector() throws Exception {this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector));+// First, try with selector header+SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();accessor.setDestination(destination);accessor.setNativeHeader("foo", "bar");@@ -276,11 +278,34 @@ public void registerSubscriptionWithSelector() throws Exception {assertEquals(1, actual.size());assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId));+// Then without+actual = this.registry.findSubscriptions(createMessage(destination));assertNotNull(actual);assertEquals(0, actual.size());}+@Test+public void registerSubscriptionWithSelectorNotSupported() {+String sessionId = "sess01";+String subscriptionId = "subs01";+String destination = "/foo";+String selector = "headers.foo == 'bar'";++this.registry.setSelectorHeaderName(null);+this.registry.registerSubscription(subscribeMessage(sessionId, subscriptionId, destination, selector));++SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();+accessor.setDestination(destination);+accessor.setNativeHeader("foo", "bazz");+Message<?> message = MessageBuilder.createMessage("", accessor.getMessageHeaders());++MultiValueMap<String, String> actual = this.registry.findSubscriptions(message);+assertNotNull(actual);+assertEquals(1, actual.size());+assertEquals(Collections.singletonList(subscriptionId), actual.get(sessionId));+}+@Test // SPR-11931public void registerSubscriptionTwiceAndUnregister() {this.registry.registerSubscription(subscribeMessage("sess01", "subs01", "/foo"));
spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java+4 −0
@@ -383,6 +383,10 @@ private RootBeanDefinition registerMessageBroker(Element brokerElement,String heartbeatValue = simpleBrokerElem.getAttribute("heartbeat");brokerDef.getPropertyValues().add("heartbeatValue", heartbeatValue);}+if (simpleBrokerElem.hasAttribute("selector-header")) {+String headerName = simpleBrokerElem.getAttribute("selector-header");+brokerDef.getPropertyValues().add("selectorHeaderName", headerName);+}}else if (brokerRelayElem != null) {String prefix = brokerRelayElem.getAttribute("prefix");
spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.xsd+17 −0
@@ -384,6 +384,23 @@]]></xsd:documentation></xsd:annotation></xsd:attribute>+<xsd:attribute name="selector-header" type="xsd:string">+<xsd:annotation>+<xsd:documentation source="java:org.springframework.messaging.simp.stomp.SimpleBrokerMessageHandler"><![CDATA[+Configure the name of a header that a subscription message can have for+the purpose of filtering messages matched to the subscription. The header+value is expected to be a Spring EL boolean expression to be applied to+the headers of messages matched to the subscription.++For example:+headers.foo == 'bar'++By default this is set to "selector". You can set it to a different+name, or to "" to turn off support for a selector header.+]]></xsd:documentation>+</xsd:annotation>+</xsd:attribute>+</xsd:complexType><xsd:complexType name="channel">
spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java+5 −2
@@ -1,5 +1,5 @@/*-* Copyright 2002-2017 the original author or authors.+* Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.@@ -47,6 +47,7 @@import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;import org.springframework.messaging.simp.SimpMessagingTemplate;import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;+import org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry;import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;@@ -202,6 +203,8 @@ public void simpleBroker() throws Exception {assertNotNull(brokerMessageHandler);Collection<String> prefixes = brokerMessageHandler.getDestinationPrefixes();assertEquals(Arrays.asList("/topic", "/queue"), new ArrayList<>(prefixes));+DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) brokerMessageHandler.getSubscriptionRegistry();+assertEquals("my-selector", registry.getSelectorHeaderName());assertNotNull(brokerMessageHandler.getTaskScheduler());assertArrayEquals(new long[] {15000, 15000}, brokerMessageHandler.getHeartbeatValue());@@ -228,7 +231,7 @@ public void simpleBroker() throws Exception {assertNotNull(this.appContext.getBean("webSocketScopeConfigurer", CustomScopeConfigurer.class));-DirectFieldAccessor accessor = new DirectFieldAccessor(brokerMessageHandler.getSubscriptionRegistry());+DirectFieldAccessor accessor = new DirectFieldAccessor(registry);Object pathMatcher = accessor.getPropertyValue("pathMatcher");String pathSeparator = (String) new DirectFieldAccessor(pathMatcher).getPropertyValue("pathSeparator");assertEquals(".", pathSeparator);
spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml+2 −1
@@ -35,7 +35,8 @@<websocket:stomp-error-handler ref="errorHandler" />-<websocket:simple-broker prefix="/topic, /queue" heartbeat="15000,15000" scheduler="scheduler" />+<websocket:simple-broker prefix="/topic, /queue" selector-header="my-selector"+heartbeat="15000,15000" scheduler="scheduler" /></websocket:message-broker>
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2018-1257
- WEBhttps://github.com/spring-projects/spring-framework/commit/246a6db1cad205ca9b6fca00c544ab7443ba202
- WEBhttps://github.com/spring-projects/spring-framework/commit/ff2228fdaf131d57b5c8c5918ee8d07c6dd9bba
- WEBhttps://access.redhat.com/errata/RHSA-2018:1809
- WEBhttps://access.redhat.com/errata/RHSA-2018:3768
- ADVISORYhttps://github.com/advisories/GHSA-rcpf-vj53-7h2m
- PACKAGEhttps://github.com/spring-projects/spring-framework
- WEBhttps://pivotal.io/security/cve-2018-1257
- WEBhttps://www.oracle.com/security-alerts/cpujan2020.html
- WEBhttps://www.oracle.com/security-alerts/cpujul2020.html
- WEBhttps://www.oracle.com/security-alerts/cpuoct2021.html
- WEBhttps://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html