This is CVE-2013-4566: The flaw is in the NSSVerifyClient (which is equivalent to mod_ssl's SSLVerifyClient) setting enforcement. If 'NSSVerifyClient none' is set in the server / vhost context (i.e. when server is configured to not request or require client certificate authentication on the initial connection), and client certificate authentication is expected to be required for a specific directory via 'NSSVerifyClient require' setting, mod_nss fails to properly require certificate authentication. Remote attacker can use this to access content of the restricted directories. Reported by Thomas Hoger . diff -rNU 150 ../mod_nss-1.0.8-o/nss_engine_kernel.c ./nss_engine_kernel.c --- ../mod_nss-1.0.8-o/nss_engine_kernel.c 2013-11-29 16:09:37.000000000 +0100 +++ ./nss_engine_kernel.c 2013-11-29 16:12:20.000000000 +0100 @@ -133,301 +133,301 @@ /* * Check to see if SSL protocol is enabled. If it's not then * no further access control checks are relevant. The test for * sc->enabled is probably strictly unnecessary */ if (!((sc->enabled == TRUE) || !ssl)) { return DECLINED; } /* * Support for per-directory reconfigured SSL connection parameters. * * This is implemented by forcing an SSL renegotiation with the * reconfigured parameter suite. But Apache's internal API processing * makes our life very hard here, because when internal sub-requests occur * we nevertheless should avoid multiple unnecessary SSL handshakes (they * require extra network I/O and especially time to perform). * * But the optimization for filtering out the unnecessary handshakes isn't * obvious and trivial. Especially because while Apache is in its * sub-request processing the client could force additional handshakes, * too. And these take place perhaps without our notice. So the only * possibility is to explicitly _ask_ OpenSSL whether the renegotiation * has to be performed or not. It has to performed when some parameters * which were previously known (by us) are not those we've now * reconfigured (as known by OpenSSL) or (in optimized way) at least when * the reconfigured parameter suite is stronger (more restrictions) than * the currently active one. */ /* * Override of NSSCipherSuite * * We provide two options here: * * o The paranoid and default approach where we force a renegotiation when * the cipher suite changed in _any_ way (which is straight-forward but * often forces renegotiations too often and is perhaps not what the * user actually wanted). * * o The optimized and still secure way where we force a renegotiation * only if the currently active cipher is no longer contained in the * reconfigured/new cipher suite. Any other changes are not important * because it's the servers choice to select a cipher from the ones the * client supports. So as long as the current cipher is still in the new * cipher suite we're happy. Because we can assume we would have * selected it again even when other (better) ciphers exists now in the * new cipher suite. This approach is fine because the user explicitly * has to enable this via ``NSSOptions +OptRenegotiate''. So we do no * implicit optimizations. */ if (dc->szCipherSuite) { /* remember old state */ for (i=0; i < ciphernum; i++) { SSL_CipherPrefGet(ssl, ciphers_def[i].num, &ciphers_old[i]); } if (dc->nOptions & SSL_OPT_OPTRENEGOTIATE) { int on, keySize, secretKeySize; char *issuer, *subject; SSL_SecurityStatus(ssl, &on, &cipher, &keySize, &secretKeySize, &issuer, &subject); } /* configure new state */ ciphers = strdup(dc->szCipherSuite); if (nss_parse_ciphers(r->server, ciphers, ciphers_new) < 0) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "Unable to reconfigure (per-directory) " "permitted SSL ciphers"); nss_log_nss_error(APLOG_MARK, APLOG_ERR, r->server); free(ciphers); return HTTP_FORBIDDEN; } free(ciphers); /* Actually enable the selected ciphers. Also check to see if the existing cipher is in the new list for a possible optimization later. */ for (i=0; inOptions & SSL_OPT_OPTRENEGOTIATE) { if (cipher_in_list != PR_TRUE) renegotiate = TRUE; } else { /* paranoid way */ for (i=0; iserver, "Reconfigured cipher suite will force renegotiation"); } } /* * override of SSLVerifyClient * * We force a renegotiation if the reconfigured/new verify type is * stronger than the currently active verify type. * * The order is: none << optional_no_ca << optional << require * * Additionally the following optimization is possible here: When the * currently active verify type is "none" but a client certificate is * already known/present, it's enough to manually force a client * verification but at least skip the I/O-intensive renegotation * handshake. */ if (dc->nVerifyClient != SSL_CVERIFY_UNSET) { PRInt32 on; /* remember old state */ SSL_OptionGet(ssl, SSL_REQUIRE_CERTIFICATE, &on); if (on == PR_TRUE) { verify_old = SSL_CVERIFY_REQUIRE; } else { SSL_OptionGet(ssl, SSL_REQUEST_CERTIFICATE, &on); if (on == PR_TRUE) verify_old = SSL_CVERIFY_OPTIONAL; else verify_old = SSL_CVERIFY_NONE; } /* configure new state */ verify = dc->nVerifyClient; if (verify == SSL_CVERIFY_REQUIRE) { SSL_OptionSet(ssl, SSL_REQUEST_CERTIFICATE, PR_TRUE); - SSL_OptionSet(ssl, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NO_ERROR); + SSL_OptionSet(ssl, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS); } else if (verify == SSL_CVERIFY_OPTIONAL) { SSL_OptionSet(ssl, SSL_REQUEST_CERTIFICATE, PR_TRUE); SSL_OptionSet(ssl, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER); } else { SSL_OptionSet(ssl, SSL_REQUEST_CERTIFICATE, PR_FALSE); SSL_OptionSet(ssl, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER); } /* determine whether we've to force a renegotiation */ if (!renegotiate && verify != verify_old) { if (((verify_old == SSL_CVERIFY_NONE) && (verify != SSL_CVERIFY_NONE)) || (!(verify_old & SSL_CVERIFY_OPTIONAL) && (verify & SSL_CVERIFY_OPTIONAL)) || (!(verify_old & SSL_CVERIFY_REQUIRE) && (verify & SSL_CVERIFY_REQUIRE))) { renegotiate = TRUE; /* optimization */ if ((dc->nOptions & SSL_OPT_OPTRENEGOTIATE) && (verify_old == SSL_CVERIFY_NONE) && ((peercert = SSL_PeerCertificate(ssl)) != NULL)) { renegotiate_quick = TRUE; CERT_DestroyCertificate(peercert); } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Changed client verification type will force " "%srenegotiation", renegotiate_quick ? "quick " : ""); } } } /* If a renegotiation is now required for this location, and the * request includes a message body (and the client has not * requested a "100 Continue" response), then the client will be * streaming the request body over the wire already. In that * case, it is not possible to stop and perform a new SSL * handshake immediately; once the SSL library moves to the * "accept" state, it will reject the SSL packets which the client * is sending for the request body. * * To allow authentication to complete in this auth hook, the * solution used here is to fill a (bounded) buffer with the * request body, and then to reinject that request body later. */ if (renegotiate && !renegotiate_quick && (apr_table_get(r->headers_in, "transfer-encoding") || (apr_table_get(r->headers_in, "content-length") && strcmp(apr_table_get(r->headers_in, "content-length"), "0"))) && !r->expecting_100) { int rv; /* Fill the I/O buffer with the request body if possible. */ rv = nss_io_buffer_fill(r); if (rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "could not buffer message body to allow " "SSL renegotiation to proceed"); return rv; } } /* * now do the renegotiation if anything was actually reconfigured */ if (renegotiate) { /* * Now we force the SSL renegotation by sending the Hello Request * message to the client. Here we have to do a workaround: Actually * OpenSSL returns immediately after sending the Hello Request (the * intent AFAIK is because the SSL/TLS protocol says it's not a must * that the client replies to a Hello Request). But because we insist * on a reply (anything else is an error for us) we have to go to the * ACCEPT state manually. Using SSL_set_accept_state() doesn't work * here because it resets too much of the connection. So we set the * state explicitly and continue the handshake manually. */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "Requesting connection re-negotiation"); if (renegotiate_quick) { SECStatus rv; CERTCertificate *peerCert; void *pinArg; /* perform just a manual re-verification of the peer */ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Performing quick renegotiation: " "just re-verifying the peer"); peerCert = SSL_PeerCertificate(sslconn->ssl); pinArg = SSL_RevealPinArg(sslconn->ssl); rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, PR_TRUE, certUsageSSLClient, pinArg); CERT_DestroyCertificate(peerCert); if (rv != SECSuccess) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "Re-negotiation handshake failed: " "Client verification failed"); return HTTP_FORBIDDEN; } /* The cert is ok, fall through to the check SSLRequires */ } else { int handshake_done = 0; int result = 0; /* do a full renegotiation */ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Performing full renegotiation: " "complete handshake protocol"); /* Do NOT call SSL_ResetHandshake as this will tear down the * existing connection. */ if (SSL_HandshakeCallback(ssl, HandshakeDone, (void *)&handshake_done) || SSL_ReHandshake(ssl, PR_TRUE)) { int errCode = PR_GetError(); if (errCode == SEC_ERROR_INVALID_ARGS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Re-negotation request failed: " "trying to do client authentication on a non-SSL3 connection"); } else { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Re-negotation request failed: " "returned error %d", errCode); } r->connection->aborted = 1; return HTTP_FORBIDDEN; } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Awaiting re-negotiation handshake");