Bug 61857 - Security: Apache 2.4 not verifying URL hostname against certificate in SSL handshake for WebSockets
Summary: Security: Apache 2.4 not verifying URL hostname against certificate in SSL ha...
Status: RESOLVED FIXED
Alias: None
Product: Apache httpd-2
Classification: Unclassified
Component: mod_proxy_wstunnel (show other bugs)
Version: 2.4.10
Hardware: PC Linux
: P2 major (vote)
Target Milestone: ---
Assignee: Apache HTTPD Bugs Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-12-05 12:27 UTC by Markus Gausling
Modified: 2019-05-24 08:06 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Markus Gausling 2017-12-05 12:27:01 UTC
Issue was found in Apache 2.4.10 and is still available in trunk. The Apache 2.4.10 is not verifying URL hostname against certificate in SSL handshake for WebSockets even when SSLProxyCheckPeerCN and/or SSLProxyCheckPeerName are enabled.

Configure Apache as a "WebSocket Relay" that allows local clients to connect to (local) ​Apache using "ws://" and Apache then maps this to "wss://" and passes the request on to the actual serving backend.

Define a Virtual Host for this:
    <VirtualHost 127.0.0.1:8888>
        SSLProxyEngine On
        ProxyRequests Off

        <Proxy "*">
            Order deny,allow
            Deny from all
            Allow from 127.0.0.1
        </Proxy>

        ProxyPass /websocket/ wss://mywebsocket.org/
    </VirtualHost>

A local request to Apache for ws://​127.0.0.1:8888/​websocket/would end up in a request to wss://mywebsocket.org/.

Define the following security option (amongst others):
    SSLProxyCheckPeerCN on
    SSLProxyCheckPeerName on
    SSLProxyCheckPeerExpire on
    SSLProxyCACertificateFile "/opt/apache2/mycert.pem"
    SSLProxyVerify require
    SSLProxyVerifyDepth 1

While Apache properly checks if the server provided certificate is not expired and also matches mycert.pem it does not validate the subject name or the subject alternative names.

This is a security hole and means e.g. when mapping the IP address of mywebsocket.org in /etc/hosts on Linux to e.g. to myotherwebsocket.org, then Apache establishes a secure connection to mywebsocket.org however it does not complain about the mismatch of the hostname in the request ("myotherwebsocket.org") vs. the one in the certificate provided during TLS session establishment ("mywebsocket.org").

Doing a similar thing for HTTP (define Reverse Proxy which does http-to-https mapping) works, i.e. Apache corectly refuses the connection as it realizes that name in certificate provided by server and hostname in request URL do not match.

========================================
Source of Apache 2.4.10 
-----------------------
The function ssl_io_filter_handshake() (in modules/ssl/ssl_engine_io.c) checks the peer hostname or the URL (depending on the Apache configuration) against the CN, agsinst the name or the subjectAltNames in the peer provided certificate.
It does this only if hostname_note is not empty:
  const char *hostname_note = apr_table_get(c->notes,
                                            "proxy-request-hostname");

                                              
In proxy_http_handler() of module/proxy/mod_proxy_http.c the connection establishment with the backend stores "proxy-request-hostname":

    /* Step Three: Create conn_rec */
    backconn = backend->connection;
    if (!backconn) {
        if ((status = ap_proxy_connection_create_ex(proxy_function,
                                                    backend, r)) != OK)
            break;
        backconn = backend->connection;

        /*
         * On SSL connections set a note on the connection what CN is
         * requested, such that mod_ssl can check if it is requested to do
         * so.
         */
        if (backend->ssl_hostname) {
            apr_table_setn(backend->connection->notes,
                           "proxy-request-hostname",
                           backend->ssl_hostname);
        }
    }
        
For the WebSocket tunneling in function proxy_wstunnel_handler() (in file: modules/proxy/mod_proxy_wstunnel.c) adding "proxy-request-hostname" is missing and as a result later in the TLS/SSL handshake the check is not done.

========================================

My proposed fix is to add the same code used in the HTTP case for the WebSockets  in function proxy_wstunnel_handler() (file: modules/proxy/mod_proxy_wstunnel.c) as well:

  /* Step Three: Create conn_rec */
  if (!backend->connection) {
      if ((status = ap_proxy_connection_create(scheme, backend,
                                               c, r->server)) != OK)
          break;

+      /*
+       * On SSL connections set a note on the connection what CN is
+       * requested, such that mod_ssl can check if it is requested to do
+       * so.
+       */
+       if (backend->ssl_hostname) {
+           apr_table_setn(backend->connection->notes,
+                          "proxy-request-hostname",
+                          backend->ssl_hostname);
      }
  }
    
This is 2.4.10 code but the code in trunk looks slightly different but the fix should be similar.
Comment 1 Yann Ylavic 2017-12-19 22:49:30 UTC
Thanks for the report and patch.

Committed in r1818726, such that SSLProxyCheckPeer* directives can be used for any proxy module (with SSL backend connections).

Does that work for you?
Comment 2 Markus Gausling 2017-12-22 10:31:53 UTC
Yes looks good. 

Thanks
Markus
Comment 3 Yann Ylavic 2019-05-24 08:06:45 UTC
Backported to 2.4.x (>= 2.4.40) in r1859844.