CVE-2014-125071 (Java, published 2023-01-09T21:15Z)
Description: value: A vulnerability was found in lukehutch Gribbit. It has been classified as problematic. Affected is the function messageReceived of the file src/gribbit/request/HttpRequestHandler.java. The manipulation leads to missing origin validation in websockets. The name of the patch is 620418df247aebda3dd4be1dda10fe229ea505dd. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-217716.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-346 \u2014 Origin Validation Error
The product does not properly verify that the source of data or communication is valid.
Repository: https://github.com/lukehutch/gribbit
Files changed:
src/gribbit/auth/Cookie.java
src/gribbit/auth/User.java
src/gribbit/request/HttpRequestHandler.java
src/gribbit/request/Request.java
Commit message: Protect against CSWSH: (Cross-Site WebSocket Hijacking)

Vulnerable code:
            nettyCookie.setSecure(true);  // TODO
            save();
import static io.netty.handler.codec.http.HttpHeaderNames.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaderNames.SERVER;
            // TODO: Read WS routes from class annotations
                    && reqURI.endsWith("/websocket")) {
                // Record which user was authenticated (if any) when websocket upgrade request was made.
                // TODO: Reject WS upgrade request for websockets that require authentication.
                // TODO: Also provide a means for revoking WS login.
                wsAuthenticatedUser = User.getLoggedInUser(request);
     * Header for CORS.
     * See http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

Fixed code:
            // If SSL is enabled, force cookies to only be delivered over SSL, to prevent cookie hijacking
            // on public wifi networks
            nettyCookie.setSecure(true);
            
            // Create new random CSRF token every time user logs in
            
            // Save tokens in database
            save();

import static io.netty.handler.codec.http.HttpHeaderNames.CACHE_CONTROL;
import static io.netty.handler.codec.http.HttpHeaderNames.SERVER;
import gribbit.auth.CSRF;
import java.net.URI;
            // FIXME: Make these into class annotations
            String websocketPath = "/websocket";
            boolean isAuthenticatedWebsocket = true;

            // TODO: Read WS routes from class annotations, rather than using hardcoded "/websocket"
                    && reqURI.endsWith(websocketPath)) {
                // Protect against CSWSH: (Cross-Site WebSocket Hijacking)
                // http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
                // http://tools.ietf.org/html/rfc6455#page-7
                CharSequence origin = request.getOrigin();
                URI originUri = null;
                if (origin != null && origin.length() > 0) {
                    try {
                        // Try parsing origin URI
                        originUri = new URI(origin.toString());
                    } catch (Exception e) {
                    }
                }
                // If port number is set but it is the default for the URI scheme, revert the port number
                // back to -1 (which means unspecified), so that it matches the server port number, 
                // which is unspecified when serving http on port 80 and https on port 443
                int originPort = originUri == null ? -1 //
                        : originUri.getPort() == 80 && "http".equals(originUri.getScheme()) ? -1 //
                                : originUri.getPort() == 443 && "https".equals(originUri.getScheme()) ? -1 //
                                        : originUri.getPort();
                // Scheme, host and port all must match to forbid cross-origin requests
                if (originUri == null //
                        || !GribbitServer.uri.getScheme().equals(originUri.getScheme()) //
                        || !GribbitServer.uri.getHost().equals(originUri.getHost()) //
                        || GribbitServer.uri.getPort() != originPort) { //
                    // Reject scripted requests to open this websocket from a different domain
                    sendHttpErrorResponse(ctx, null, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                            HttpResponseStatus.FORBIDDEN));
                    return;
                }
                // Log.info("Origin: " + origin.toString());

                if (isAuthenticatedWebsocket) {
                    // For authenticated websockets, check if the user is logged in
                    User loggedInUser = User.getLoggedInUser(request);
                    if (loggedInUser == null) {
                        // Not logged in, so can't connect to this websocket
                        sendHttpErrorResponse(ctx, null, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                                HttpResponseStatus.FORBIDDEN));
                        return;
                    }

                    // To further mitigate CSWSH attacks: check for the CSRF token in the URL parameter "_csrf";
                    // the passed token must match the user's CSRF token. This means the websocket URL has to
                    // be dynamically generated and inserted into the webpage that opened the websocket.
                    // TODO: generate this URL an insert into the page somehow
                    String csrfTok = loggedInUser.csrfTok;
                    if (csrfTok == null || csrfTok.isEmpty() || csrfTok.equals(CSRF.CSRF_TOKEN_UNKNOWN)
                            || csrfTok.equals(CSRF.CSRF_TOKEN_PLACEHOLDER)) {
                        // No valid CSRF token in User object
                        sendHttpErrorResponse(ctx, null, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                                HttpResponseStatus.FORBIDDEN));
                        return;
                    }
                    String csrfParam = request.getQueryParam("_csrf");
                    if (csrfParam == null || csrfParam.isEmpty() || !csrfParam.equals(csrfTok)) {
                        // The CSRF URL query parameter is missing, or doesn't match the user's token 
                        sendHttpErrorResponse(ctx, null, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                                HttpResponseStatus.FORBIDDEN));
                        return;                        
                    }

                    // Record which user was authenticated when the websocket upgrade request was made.
                    // TODO: Also provide a means for revoking user's session while WS is still open,
                    // e.g. poll the user table every few seconds to see if user's session token has
                    // changed in the database? (Although this would mean that logging in on a new
                    // device would log you out of all other sessions...)
                    wsAuthenticatedUser = loggedInUser;
                }
     * Header for CORS, and for protecting against CSWSH. See:
     * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
     * 
     * http://www.christian-schneider.net/CrossSiteWebSocketHijacking.html