Moderate severity vulnerability that affects 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
4.1.0 → fixed in 4.1.5
Details
The Java SockJS client in Pivotal Spring Framework 4.1.x before 4.1.5 generates predictable session ids, which allows remote attackers to send messages to other sessions via unspecified vectors.
The fix
Add JdkIdGenerator and use it in SockJS client
spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java+5 −3
@@ -22,9 +22,11 @@import java.util.UUID;/**-* A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for the-* initial seed and {@link Random} thereafter. This provides better performance in-* exchange for less securely random id's.+* An {@link org.springframework.util.IdGenerator IdGenerator} that uses+* {@link SecureRandom} for the initial seed and {@link Random} thereafter+* instead of calling {@link UUID#randomUUID()} every time as+* {@link org.springframework.util.JdkIdGenerator JdkIdGenerator} does.+* This provides a better balance between securely random id's and performance.** @author Rossen Stoyanchev* @author Rob Winch
spring-core/src/main/java/org/springframework/util/JdkIdGenerator.java+34 −0
@@ -0,0 +1,34 @@+/*+* Copyright 2002-2013 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.+* You may obtain a copy of the License at+*+* http://www.apache.org/licenses/LICENSE-2.0+*+* Unless required by applicable law or agreed to in writing, software+* distributed under the License is distributed on an "AS IS" BASIS,+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+* See the License for the specific language governing permissions and+* limitations under the License.+*/++package org.springframework.util;++import java.util.UUID;++/**+* An IdGenerator that calls {@link java.util.UUID#randomUUID()}.+*+* @author Rossen Stoyanchev+* @since 4.2+*/+public class JdkIdGenerator implements IdGenerator {+++public UUID generateId() {+return UUID.randomUUID();+}++}
spring-core/src/main/java/org/springframework/util/SimpleIdGenerator.java+44 −0
@@ -0,0 +1,44 @@+/*+* Copyright 2002-2013 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.+* You may obtain a copy of the License at+*+* http://www.apache.org/licenses/LICENSE-2.0+*+* Unless required by applicable law or agreed to in writing, software+* distributed under the License is distributed on an "AS IS" BASIS,+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+* See the License for the specific language governing permissions and+* limitations under the License.+*/++package org.springframework.util;++import java.util.UUID;+import java.util.concurrent.atomic.AtomicLong;++/**+* An simple IdGenerator that starts at 1 and increments by 1 with each call.+*+* @author Rossen Stoyanchev+* @since 4.2+*/+public class SimpleIdGenerator implements IdGenerator {++private final AtomicLong mostSigBits = new AtomicLong(0);++private final AtomicLong leastSigBits = new AtomicLong(0);+++@Override+public UUID generateId() {+long leastSigBits = this.leastSigBits.incrementAndGet();+if (leastSigBits == 0) {+this.mostSigBits.incrementAndGet();+}+return new UUID(this.mostSigBits.get(), leastSigBits);+}++}
spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/SockJsUrlInfo.java+3 −3
@@ -1,5 +1,5 @@/*-* Copyright 2002-2014 the original author or authors.+* Copyright 2002-2015 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.@@ -19,8 +19,8 @@import java.net.URI;import java.util.UUID;-import org.springframework.util.AlternativeJdkIdGenerator;import org.springframework.util.IdGenerator;+import org.springframework.util.JdkIdGenerator;import org.springframework.web.socket.sockjs.transport.TransportType;import org.springframework.web.util.UriComponentsBuilder;@@ -33,7 +33,7 @@*/public class SockJsUrlInfo {-private static final IdGenerator idGenerator = new AlternativeJdkIdGenerator();+private static final IdGenerator idGenerator = new JdkIdGenerator();private final URI sockJsUrl;
Check the user of a SockJS request
spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java+11 −0
@@ -17,6 +17,8 @@package org.springframework.web.socket.sockjs.transport;import java.io.IOException;+import java.net.InetSocketAddress;+import java.security.Principal;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;@@ -245,6 +247,15 @@ else if (transportType.supportsCors()) {return;}}+else {+if (session.getPrincipal() != null) {+if (!session.getPrincipal().equals(request.getPrincipal())) {+logger.debug("The user for the session does not match the user for the request.");+response.setStatusCode(HttpStatus.NOT_FOUND);+return;+}+}+}if (transportType.sendsNoCacheInstruction()) {addNoCacheHeaders(response);
spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java+23 −0
@@ -29,6 +29,7 @@import org.springframework.scheduling.TaskScheduler;import org.springframework.web.socket.AbstractHttpRequestTests;import org.springframework.web.socket.WebSocketHandler;+import org.springframework.web.socket.handler.TestPrincipal;import org.springframework.web.socket.server.HandshakeHandler;import org.springframework.web.socket.server.support.OriginHandshakeInterceptor;import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory;@@ -243,6 +244,28 @@ public void handleTransportRequestXhrSend() throws Exception {verify(this.xhrSendHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);}+@Test+public void handleTransportRequestXhrSendWithDifferentUser() throws Exception {+String sockJsPath = sessionUrlPrefix + "xhr";+setRequest("POST", sockJsPrefix + sockJsPath);+this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);++assertEquals(200, this.servletResponse.getStatus()); // session created+verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);++this.session.setPrincipal(new TestPrincipal("little red riding hood"));+this.servletRequest.setUserPrincipal(new TestPrincipal("wolf"));++resetResponse();+reset(this.xhrSendHandler);+sockJsPath = sessionUrlPrefix + "xhr_send";+setRequest("POST", sockJsPrefix + sockJsPath);+this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);++assertEquals(404, this.servletResponse.getStatus());+verifyNoMoreInteractions(this.xhrSendHandler);+}+@Testpublic void handleTransportRequestJsonp() throws Exception {TransportHandlingSockJsService jsonpService = new TransportHandlingSockJsService(this.taskScheduler, this.jsonpHandler, this.jsonpSendHandler);
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2015-0201
- WEBhttps://github.com/spring-projects/spring-framework/commit/d63cfc8eebc396be009e733a81ebb4c984811f6e
- WEBhttps://github.com/spring-projects/spring-framework/commit/dc5b5ca8ee09c890352f89b2dae58bc0132d6545
- ADVISORYhttps://github.com/advisories/GHSA-45vg-2v73-vm62
- PACKAGEhttps://github.com/spring-projects/spring-framework
- WEBhttps://pivotal.io/security/cve-2015-0201