CVE-2018-8039 (Java, published 2018-07-02T13:29Z)
Description: value: It is possible to configure Apache CXF to use the com.sun.net.ssl implementation via 'System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");'. When this system property is set, CXF uses some reflection to try to make the HostnameVerifier work with the old com.sun.net.ssl.HostnameVerifier interface. However, the default HostnameVerifier implementation in CXF does not implement the method in this interface, and an exception is thrown. However, in Apache CXF prior to 3.2.5 and 3.1.16 the exception is caught in the reflection code and not properly propagated. What this means is that if you are using the com.sun.net.ssl stack with CXF, an error with TLS hostname verification will not be thrown, leaving a CXF client subject to man-in-the-middle attacks.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-755 \u2014 Improper Handling of Exceptional Conditions
The product does not handle or incorrectly handles an exceptional condition.
Repository: https://github.com/apache/cxf
Files changed:
rt/transports/http/src/main/java/org/apache/cxf/transport/https/AllowAllHostnameVerifier.java
rt/transports/http/src/main/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java
rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
systests/transports/src/test/java/org/apache/cxf/systest/https/hostname/HostnameVerificationDeprecatedServer.java
systests/transports/src/test/java/org/apache/cxf/systest/https/hostname/HostnameVerificationDeprecatedTest.java
systests/transports/src/test/resources/org/apache/cxf/systest/https/hostname/hostname-client-bethal.xml
systests/transports/src/test/resources/org/apache/cxf/systest/https/hostname/hostname-server-bethal.xml
Commit message: Fix hostname verification using the deprecated SSL stack


Vulnerable code:
                            return true;

Fixed code:

    public boolean verify(final String host, final String certHostname) {
        return certHostname != null && !certHostname.isEmpty();
    }
                            return false;
    public boolean verify(final String host, final String certHostname) {
        try {
            matchCN(host, certHostname, this.publicSuffixMatcher);
            return true;
        } catch (SSLException ex) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.log(Level.FINE, ex.getMessage(), ex);
            }
            return false;
        }
    }

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.cxf.systest.https.hostname;

import java.net.URL;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.testutil.common.AbstractBusTestServerBase;

public class HostnameVerificationDeprecatedServer extends AbstractBusTestServerBase {

    public HostnameVerificationDeprecatedServer() {

    }

    protected void run()  {
        URL busFile = HostnameVerificationDeprecatedServer.class.getResource("hostname-server-bethal.xml");
        Bus busLocal = new SpringBusFactory().createBus(busFile);
        BusFactory.setDefaultBus(busLocal);
        setBus(busLocal);

        try {
            new HostnameVerificationDeprecatedServer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.apache.cxf.systest.https.hostname;

import java.net.URL;

import javax.xml.ws.BindingProvider;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.hello_world.Greeter;
import org.apache.hello_world.services.SOAPService;

import org.junit.AfterClass;
import org.junit.BeforeClass;

/**
 * A test for hostname verification when the Java system property "java.protocol.handler.pkgs" is set to 
 * "com.sun.net.ssl.internal.www.protocol". This means that com.sun.net.ssl.HostnameVerifier is used 
 * instead of the javax version.
 */
public class HostnameVerificationDeprecatedTest extends AbstractBusClientServerTestBase {
    static final String PORT = allocatePort(HostnameVerificationDeprecatedServer.class);
    static final String PORT2 = allocatePort(HostnameVerificationDeprecatedServer.class, 2);

    @BeforeClass
    public static void startServers() throws Exception {
        System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
        assertTrue(
            "Server failed to launch",
            // run the server in the same process
            // set this to false to fork
            launchServer(HostnameVerificationDeprecatedServer.class, true)
        );
    }

    @AfterClass
    public static void cleanup() throws Exception {
        System.clearProperty("java.protocol.handler.pkgs");
        stopAllServers();
    }

    // Here we expect an exception, as the default hostname verifier contributed by CXF will object to the
    // fact that the server certificate does not have "CN=localhost".
    @org.junit.Test
    public void testLocalhostNotMatching() throws Exception {
        SpringBusFactory bf = new SpringBusFactory();
        URL busFile = HostnameVerificationDeprecatedTest.class.getResource("hostname-client-bethal.xml");

        Bus bus = bf.createBus(busFile.toString());
        BusFactory.setDefaultBus(bus);
        BusFactory.setThreadDefaultBus(bus);

        URL url = SOAPService.WSDL_LOCATION;
        SOAPService service = new SOAPService(url, SOAPService.SERVICE);
        assertNotNull("Service is null", service);
        final Greeter port = service.getHttpsPort();
        assertNotNull("Port is null", port);

        updateAddressPort(port, PORT);
        
        try {
            port.greetMe("Kitty");
            fail("Failure expected on the hostname verification");
        } catch (Exception ex) {
            // expected
        }

        ((java.io.Closeable)port).close();
        bus.shutdown(true);
    }
    
    // No Subject Alternative Name, but the CN matches ("localhost"), so the default HostnameVerifier
    // should work fine
    @org.junit.Test
    public void testNoSubjectAlternativeNameCNMatch() throws Exception {
        SpringBusFactory bf = new SpringBusFactory();
        URL busFile = HostnameVerificationDeprecatedTest.class.getResource("hostname-client.xml");

        Bus bus = bf.createBus(busFile.toString());
        BusFactory.setDefaultBus(bus);
        BusFactory.setThreadDefaultBus(bus);

        URL url = SOAPService.WSDL_LOCATION;
        SOAPService service = new SOAPService(url, SOAPService.SERVICE);
        assertNotNull("Service is null", service);
        final Greeter port = service.getHttpsPort();
        assertNotNull("Port is null", port);

        updateAddressPort(port, PORT2);

        assertEquals(port.greetMe("Kitty"), "Hello Kitty");

        // Enable Async
        ((BindingProvider)port).getRequestContext().put("use.async.http.conduit", true);

        assertEquals(port.greetMe("Kitty"), "Hello Kitty");

        ((java.io.Closeable)port).close();
        bus.shutdown(true);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements. See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership. The ASF licenses this file
 to you 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:http="http://cxf.apache.org/transports/http/configuration" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core" xmlns:p="http://cxf.apache.org/policy" xmlns:sec="http://cxf.apache.org/configuration/security" xsi:schemaLocation="           http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd           http://cxf.apache.org/jaxws                           http://cxf.apache.org/schemas/jaxws.xsd           http://cxf.apache.org/transports/http/configuration   http://cxf.apache.org/schemas/configuration/http-conf.xsd           http://cxf.apache.org/configuration/security          http://cxf.apache.org/schemas/configuration/security.xsd           http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd           http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd">
    
    <cxf:bus>
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
    <http:conduit name="https://localhost:.*">
        <http:tlsClientParameters>
            <sec:trustManagers>
                <sec:keyStore type="jks" password="password" resource="keys/Bethal.jks"/>
            </sec:trustManagers>
        </http:tlsClientParameters>
    </http:conduit>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements. See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership. The ASF licenses this file
 to you 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:http="http://cxf.apache.org/transports/http/configuration" xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" xmlns:sec="http://cxf.apache.org/configuration/security" xmlns:cxf="http://cxf.apache.org/core" xmlns:p="http://cxf.apache.org/policy" xsi:schemaLocation="         http://www.springframework.org/schema/beans                     http://www.springframework.org/schema/beans/spring-beans-4.2.xsd         http://cxf.apache.org/jaxws                                     http://cxf.apache.org/schemas/jaxws.xsd         http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd         http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd         http://cxf.apache.org/transports/http/configuration             http://cxf.apache.org/schemas/configuration/http-conf.xsd         http://cxf.apache.org/transports/http-jetty/configuration       http://cxf.apache.org/schemas/configuration/http-jetty.xsd         http://cxf.apache.org/configuration/security                    http://cxf.apache.org/schemas/configuration/security.xsd     ">
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
    <cxf:bus>
        <cxf:features>
            <cxf:logging/>
        </cxf:features>
    </cxf:bus>
   
    <httpj:engine-factory id="non-localhost-match-settings">
        <httpj:engine port="${testutil.ports.HostnameVerificationDeprecatedServer}">
            <httpj:tlsServerParameters>
                <sec:keyManagers keyPassword="password">
                    <sec:keyStore type="jks" password="password" resource="keys/Bethal.jks"/>
                </sec:keyManagers>
                <sec:clientAuthentication want="false" required="false"/>
            </httpj:tlsServerParameters>
        </httpj:engine>
    </httpj:engine-factory>
    
    <jaxws:endpoint xmlns:e="http://apache.org/hello_world/services" 
                     xmlns:s="http://apache.org/hello_world/services" 
                     id="NonLocalhostMatch"
                     implementor="org.apache.cxf.systest.http.GreeterImpl" 
                     address="https://localhost:${testutil.ports.HostnameVerificationDeprecatedServer}/SoapContext/HttpsPort" 
                     serviceName="s:SOAPService" 
                     endpointName="e:HttpsPort" depends-on="non-localhost-match-settings"/>
                     
    <httpj:engine-factory id="no-subject-alt-cn-match-settings">
        <httpj:engine port="${testutil.ports.HostnameVerificationDeprecatedServer.2}">
            <httpj:tlsServerParameters>
                <sec:keyManagers keyPassword="security">
                    <sec:keyStore type="jks" password="security" resource="keys/subjalt.jks"/>
                </sec:keyManagers>
                <sec:clientAuthentication want="false" required="false"/>
                <sec:certAlias>nosubjaltcnmatch</sec:certAlias>
            </httpj:tlsServerParameters>
        </httpj:engine>
    </httpj:engine-factory>
    
    <jaxws:endpoint xmlns:e="http://apache.org/hello_world/services" 
                     xmlns:s="http://apache.org/hello_world/services" 
                     id="NoSubjectAltCNMatch" 
                     implementor="org.apache.cxf.systest.http.GreeterImpl" 
                     address="https://localhost:${testutil.ports.HostnameVerificationDeprecatedServer.2}/SoapContext/HttpsPort" 
                     serviceName="s:SOAPService" 
                     endpointName="e:HttpsPort" depends-on="no-subject-alt-cn-match-settings"/>
</beans>