Commit message: Prevent requires_preauth bypass [CVE-2015-2694]

In the OTP kdcpreauth module, don't set the TKT_FLG_PRE_AUTH bit until
the request is successfully verified.  In the PKINIT kdcpreauth
module, don't respond with code 0 on empty input or an unconfigured
realm.  Together these bugs could cause the KDC preauth framework to
erroneously treat a request as pre-authenticated.

CVE-2015-2694:

In MIT krb5 1.12 and later, when the KDC is configured with PKINIT
support, an unauthenticated remote attacker can bypass the
requires_preauth flag on a client principal and obtain a ciphertext
encrypted in the principal's long-term key.  This ciphertext could be
used to conduct an off-line dictionary attack against the user's
password.

    CVSSv2 Vector: AV:N/AC:M/Au:N/C:P/I:P/A:N/E:POC/RL:OF/RC:C

ticket: 8160 (new)
target_version: 1.13.2
tags: pullup
subject: requires_preauth bypass in PKINIT-enabled KDC [CVE-2015-2694]
CVE: CVE-2015-2694
CWE: CWE-264
Vulnerability description: The kdcpreauth modules in MIT Kerberos 5 (aka krb5) 1.12.x and 1.13.x before 1.13.2 do not properly track whether a client's request has been validated, which allows remote attackers to bypass an intended preauthentication requirement by providing (1) zero bytes of data or (2) an arbitrary realm name, related to plugins/preauth/otp/main.c and plugins/preauth/pkinit/pkinit_srv.c.

Project: krb5
File: main.c

otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
           krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa,
           krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
           krb5_kdcpreauth_moddata moddata,
           krb5_kdcpreauth_verify_respond_fn respond, void *arg)
{
    krb5_keyblock *armor_key = NULL;
    krb5_pa_otp_req *req = NULL;
    struct request_state *rs;
    krb5_error_code retval;
     krb5_data d, plaintext;
     char *config;
 
     /* Get the FAST armor key. */
     armor_key = cb->fast_armor(context, rock);
     if (armor_key == NULL) {
        retval = KRB5KDC_ERR_PREAUTH_FAILED;
        com_err("otp", retval, "No armor key found when verifying padata");
        goto error;
    }

    /* Decode the request. */
    d = make_data(pa->contents, pa->length);
    retval = decode_krb5_pa_otp_req(&d, &req);
    if (retval != 0) {
        com_err("otp", retval, "Unable to decode OTP request");
        goto error;
    }

    /* Decrypt the nonce from the request. */
    retval = decrypt_encdata(context, armor_key, req, &plaintext);
    if (retval != 0) {
        com_err("otp", retval, "Unable to decrypt nonce");
        goto error;
    }

    /* Verify the nonce or timestamp. */
    retval = nonce_verify(context, armor_key, &plaintext);
    if (retval != 0)
        retval = timestamp_verify(context, &plaintext);
    krb5_free_data_contents(context, &plaintext);
    if (retval != 0) {
        com_err("otp", retval, "Unable to verify nonce or timestamp");
         goto error;
     }
 
    /* Create the request state.  Save the response callback, and the
     * enc_tkt_reply pointer so we can set the TKT_FLG_PRE_AUTH flag later. */
     rs = k5alloc(sizeof(struct request_state), &retval);
     if (rs == NULL)
         goto error;
     rs->arg = arg;
     rs->respond = respond;
    rs->enc_tkt_reply = enc_tkt_reply;
 
     /* Get the principal's OTP configuration string. */
     retval = cb->get_string(context, rock, "otp", &config);
    if (retval == 0 && config == NULL)
        retval = KRB5_PREAUTH_FAILED;
    if (retval != 0) {
        free(rs);
        goto error;
    }

    /* Send the request. */
    otp_state_verify((otp_state *)moddata, cb->event_context(context, rock),
                     request->client, config, req, on_response, rs);
    cb->free_string(context, rock, config);

    k5_free_pa_otp_req(context, req);
    return;

error:
    k5_free_pa_otp_req(context, req);
    (*respond)(arg, retval, NULL, NULL, NULL);
}

Project: krb5
File: pkinit_srv.c

pkinit_server_verify_padata(krb5_context context,
                            krb5_data *req_pkt,
                            krb5_kdc_req * request,
                            krb5_enc_tkt_part * enc_tkt_reply,
                            krb5_pa_data * data,
                            krb5_kdcpreauth_callbacks cb,
                            krb5_kdcpreauth_rock rock,
                            krb5_kdcpreauth_moddata moddata,
                            krb5_kdcpreauth_verify_respond_fn respond,
                            void *arg)
{
    krb5_error_code retval = 0;
    krb5_data authp_data = {0, 0, NULL}, krb5_authz = {0, 0, NULL};
    krb5_pa_pk_as_req *reqp = NULL;
    krb5_pa_pk_as_req_draft9 *reqp9 = NULL;
    krb5_auth_pack *auth_pack = NULL;
    krb5_auth_pack_draft9 *auth_pack9 = NULL;
    pkinit_kdc_context plgctx = NULL;
    pkinit_kdc_req_context reqctx = NULL;
    krb5_checksum cksum = {0, 0, 0, NULL};
    krb5_data *der_req = NULL;
    int valid_eku = 0, valid_san = 0;
    krb5_data k5data;
    int is_signed = 1;
    krb5_pa_data **e_data = NULL;
    krb5_kdcpreauth_modreq modreq = NULL;
 
     pkiDebug("pkinit_verify_padata: entered!\n");
     if (data == NULL || data->length <= 0 || data->contents == NULL) {
        (*respond)(arg, EINVAL, NULL, NULL, NULL);
         return;
     }
 

    if (moddata == NULL) {
        (*respond)(arg, EINVAL, NULL, NULL, NULL);
        return;
    }
 
     plgctx = pkinit_find_realm_context(context, moddata, request->server);
     if (plgctx == NULL) {
        (*respond)(arg, EINVAL, NULL, NULL, NULL);
         return;
     }
 
#ifdef DEBUG_ASN1
    print_buffer_bin(data->contents, data->length, "/tmp/kdc_as_req");
#endif
    /* create a per-request context */
    retval = pkinit_init_kdc_req_context(context, &reqctx);
    if (retval)
        goto cleanup;
    reqctx->pa_type = data->pa_type;

    PADATA_TO_KRB5DATA(data, &k5data);

    switch ((int)data->pa_type) {
    case KRB5_PADATA_PK_AS_REQ:
        pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");
        retval = k5int_decode_krb5_pa_pk_as_req(&k5data, &reqp);
        if (retval) {
            pkiDebug("decode_krb5_pa_pk_as_req failed\n");
            goto cleanup;
        }
#ifdef DEBUG_ASN1
        print_buffer_bin(reqp->signedAuthPack.data,
                         reqp->signedAuthPack.length,
                         "/tmp/kdc_signed_data");
#endif
        retval = cms_signeddata_verify(context, plgctx->cryptoctx,
                                       reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_CLIENT,
                                       plgctx->opts->require_crl_checking,
                                       (unsigned char *)
                                       reqp->signedAuthPack.data, reqp->signedAuthPack.length,
                                       (unsigned char **)&authp_data.data,
                                       &authp_data.length,
                                       (unsigned char **)&krb5_authz.data,
                                       &krb5_authz.length, &is_signed);
        break;
    case KRB5_PADATA_PK_AS_REP_OLD:
    case KRB5_PADATA_PK_AS_REQ_OLD:
        pkiDebug("processing KRB5_PADATA_PK_AS_REQ_OLD\n");
        retval = k5int_decode_krb5_pa_pk_as_req_draft9(&k5data, &reqp9);
        if (retval) {
            pkiDebug("decode_krb5_pa_pk_as_req_draft9 failed\n");
            goto cleanup;
        }
#ifdef DEBUG_ASN1
        print_buffer_bin(reqp9->signedAuthPack.data,
                         reqp9->signedAuthPack.length,
                         "/tmp/kdc_signed_data_draft9");
#endif

        retval = cms_signeddata_verify(context, plgctx->cryptoctx,
                                       reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_DRAFT9,
                                       plgctx->opts->require_crl_checking,
                                       (unsigned char *)
                                       reqp9->signedAuthPack.data, reqp9->signedAuthPack.length,
                                       (unsigned char **)&authp_data.data,
                                       &authp_data.length,
                                       (unsigned char **)&krb5_authz.data,
                                       &krb5_authz.length, NULL);
        break;
    default:
        pkiDebug("unrecognized pa_type = %d\n", data->pa_type);
        retval = EINVAL;
        goto cleanup;
    }
    if (retval) {
        pkiDebug("pkcs7_signeddata_verify failed\n");
        goto cleanup;
    }
    if (is_signed) {

        retval = verify_client_san(context, plgctx, reqctx, request->client,
                                   &valid_san);
        if (retval)
            goto cleanup;
        if (!valid_san) {
            pkiDebug("%s: did not find an acceptable SAN in user "
                     "certificate\n", __FUNCTION__);
            retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
            goto cleanup;
        }
        retval = verify_client_eku(context, plgctx, reqctx, &valid_eku);
        if (retval)
            goto cleanup;

        if (!valid_eku) {
            pkiDebug("%s: did not find an acceptable EKU in user "
                     "certificate\n", __FUNCTION__);
            retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;
            goto cleanup;
        }
    } else { /* !is_signed */
        if (!krb5_principal_compare(context, request->client,
                                    krb5_anonymous_principal())) {
            retval = KRB5KDC_ERR_PREAUTH_FAILED;
            krb5_set_error_message(context, retval,
                                   _("Pkinit request not signed, but client "
                                     "not anonymous."));
            goto cleanup;
        }
    }
#ifdef DEBUG_ASN1
    print_buffer_bin(authp_data.data, authp_data.length, "/tmp/kdc_auth_pack");
#endif

    OCTETDATA_TO_KRB5DATA(&authp_data, &k5data);
    switch ((int)data->pa_type) {
    case KRB5_PADATA_PK_AS_REQ:
        retval = k5int_decode_krb5_auth_pack(&k5data, &auth_pack);
        if (retval) {
            pkiDebug("failed to decode krb5_auth_pack\n");
            goto cleanup;
        }

        retval = krb5_check_clockskew(context,
                                      auth_pack->pkAuthenticator.ctime);
        if (retval)
            goto cleanup;

        /* check dh parameters */
        if (auth_pack->clientPublicValue != NULL) {
            retval = server_check_dh(context, plgctx->cryptoctx,
                                     reqctx->cryptoctx, plgctx->idctx,
                                     &auth_pack->clientPublicValue->algorithm.parameters,
                                     plgctx->opts->dh_min_bits);

            if (retval) {
                pkiDebug("bad dh parameters\n");
                goto cleanup;
            }
        } else if (!is_signed) {
            /*Anonymous pkinit requires DH*/
            retval = KRB5KDC_ERR_PREAUTH_FAILED;
            krb5_set_error_message(context, retval,
                                   _("Anonymous pkinit without DH public "
                                     "value not supported."));
            goto cleanup;
        }
        der_req = cb->request_body(context, rock);
        retval = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL,
                                      0, der_req, &cksum);
        if (retval) {
            pkiDebug("unable to calculate AS REQ checksum\n");
            goto cleanup;
        }
        if (cksum.length != auth_pack->pkAuthenticator.paChecksum.length ||
            k5_bcmp(cksum.contents,
                    auth_pack->pkAuthenticator.paChecksum.contents,
                    cksum.length) != 0) {
            pkiDebug("failed to match the checksum\n");
#ifdef DEBUG_CKSUM
            pkiDebug("calculating checksum on buf size (%d)\n",
                     req_pkt->length);
            print_buffer(req_pkt->data, req_pkt->length);
            pkiDebug("received checksum type=%d size=%d ",
                     auth_pack->pkAuthenticator.paChecksum.checksum_type,
                     auth_pack->pkAuthenticator.paChecksum.length);
            print_buffer(auth_pack->pkAuthenticator.paChecksum.contents,
                         auth_pack->pkAuthenticator.paChecksum.length);
            pkiDebug("expected checksum type=%d size=%d ",
                     cksum.checksum_type, cksum.length);
            print_buffer(cksum.contents, cksum.length);
#endif

            retval = KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED;
            goto cleanup;
        }

        /* check if kdcPkId present and match KDC's subjectIdentifier */
        if (reqp->kdcPkId.data != NULL) {
            int valid_kdcPkId = 0;
            retval = pkinit_check_kdc_pkid(context, plgctx->cryptoctx,
                                           reqctx->cryptoctx, plgctx->idctx,
                                           (unsigned char *)reqp->kdcPkId.data,
                                           reqp->kdcPkId.length, &valid_kdcPkId);
            if (retval)
                goto cleanup;
            if (!valid_kdcPkId)
                pkiDebug("kdcPkId in AS_REQ does not match KDC's cert"
                         "RFC says to ignore and proceed\n");

        }
        /* remember the decoded auth_pack for verify_padata routine */
        reqctx->rcv_auth_pack = auth_pack;
        auth_pack = NULL;
        break;
    case KRB5_PADATA_PK_AS_REP_OLD:
    case KRB5_PADATA_PK_AS_REQ_OLD:
        retval = k5int_decode_krb5_auth_pack_draft9(&k5data, &auth_pack9);
        if (retval) {
            pkiDebug("failed to decode krb5_auth_pack_draft9\n");
            goto cleanup;
        }
        if (auth_pack9->clientPublicValue != NULL) {
            retval = server_check_dh(context, plgctx->cryptoctx,
                                     reqctx->cryptoctx, plgctx->idctx,
                                     &auth_pack9->clientPublicValue->algorithm.parameters,
                                     plgctx->opts->dh_min_bits);

            if (retval) {
                pkiDebug("bad dh parameters\n");
                goto cleanup;
            }
        }
        /* remember the decoded auth_pack for verify_padata routine */
        reqctx->rcv_auth_pack9 = auth_pack9;
        auth_pack9 = NULL;
        break;
    }

    /* remember to set the PREAUTH flag in the reply */
    enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
    modreq = (krb5_kdcpreauth_modreq)reqctx;
    reqctx = NULL;

cleanup:
    if (retval && data->pa_type == KRB5_PADATA_PK_AS_REQ) {
        pkiDebug("pkinit_verify_padata failed: creating e-data\n");
        if (pkinit_create_edata(context, plgctx->cryptoctx, reqctx->cryptoctx,
                                plgctx->idctx, plgctx->opts, retval, &e_data))
            pkiDebug("pkinit_create_edata failed\n");
    }

    switch ((int)data->pa_type) {
    case KRB5_PADATA_PK_AS_REQ:
        free_krb5_pa_pk_as_req(&reqp);
        free(cksum.contents);
        break;
    case KRB5_PADATA_PK_AS_REP_OLD:
    case KRB5_PADATA_PK_AS_REQ_OLD:
        free_krb5_pa_pk_as_req_draft9(&reqp9);
    }
    free(authp_data.data);
    free(krb5_authz.data);
    if (reqctx != NULL)
        pkinit_fini_kdc_req_context(context, reqctx);
    free_krb5_auth_pack(&auth_pack);
    free_krb5_auth_pack_draft9(context, &auth_pack9);

    (*respond)(arg, retval, modreq, e_data, NULL);
}