Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 476 – 표준 라이브러리 HTTP 클라이언트에서 인증서 검증을 기본으로 활성화하기

Author:
Alex Gaynor <alex.gaynor at gmail.com>
Status:
Final
Type:
Standards Track
Created:
28-Aug-2014
Python-Version:
2.7.9, 3.4.3, 3.5
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

현재 표준 라이브러리 HTTP 클라이언트(urllib, urllib2, http, httplib 모듈)는 https:// URL을 만나면 해당 서버와 통신하는 데 필요한 대로 네트워크 HTTP 트래픽을 TLS 스트림으로 감쌉니다. 그러나 TLS 핸드셰이크 중에 서버의 X509 인증서가 신뢰 루트에 있는 CA의 서명을 받았는지 실제로 확인하지 않으며, 제시된 인증서의 Common Name(또는 Subject Alternate Name)이 요청한 호스트와 일치하는지도 검증하지 않습니다.

이러한 검사를 하지 않는다는 것은 네트워크상 특권적인 위치에 있는 사람이라면 누구나 이 HTTP 클라이언트 중 하나를 사용하는 Python 애플리케이션을 상대로 중간자 공격을 손쉽게 실행하여 트래픽을 마음대로 변경할 수 있다는 뜻입니다.

이 PEP는 Python의 HTTP 클라이언트에서 X509 인증서 서명 검증과 호스트 이름 검증을 기본으로 활성화하되, 호출 단위로 이를 끌 수 있게 할 것을 제안합니다. 이 변경은 Python 2.7, Python 3.4, Python 3.5에 적용됩니다.

근거

“HTTPS”의 “S”는 secure(안전한)를 뜻합니다. Python 사용자가 “HTTPS”를 입력할 때는 안전한 연결을 기대하므로, Python은 이를 제공하는 데 있어 합당한 수준의 주의 기준을 지켜야 합니다. 현재 우리는 이를 지키지 못하고 있으며, 그 결과 단순해 보이는 API가 사용자를 오도하고 있습니다.

물어보면 많은 Python 사용자가 Python이 이러한 검증을 수행하지 않는다는 사실을 몰랐다고 답하며 충격을 받습니다.

기본으로 이러한 검사를 활성화하는 requests의 인기는 이러한 검사가 결코 지나친 부담이 아님을 보여 주며, requests가 표준 라이브러리 클라이언트보다 보안을 크게 개선한 대안으로 널리 추천된다는 사실은 많은 사람이 자신이 쓰는 도구에 더 높은 수준의 “기본 보안”을 기대한다는 것을 보여 줍니다.

여러 애플리케이션이 이 문제에 대한 Python의 허술함을 인지하지 못한 탓에 정기적으로 CVE가 할당되고 있습니다 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11].

기술적 세부 사항

Python은 모든 플랫폼에서 시스템이 제공하는 인증서 데이터베이스를 사용합니다. 그러한 데이터베이스를 찾지 못하면 오류가 발생하며, 이를 해결하려면 사용자가 위치를 명시적으로 지정해야 합니다.

이는 ssl.create_default_context와 동일한 새 함수 ssl._create_default_https_context를 추가하여 구현합니다.

그러면 http.client는 ssl._create_stdlib_context 대신 ssl._create_default_https_context를 사용하도록 바꿀 수 있습니다.

또한 ssl._create_stdlib_context의 이름은 ssl._create_unverified_context로 바뀝니다(하위 호환성을 위해 기존 이름은 별칭으로 남겨 둡니다).

신뢰 데이터베이스

이 PEP는 시스템이 제공하는 인증서 데이터베이스를 사용할 것을 제안합니다. 이전 논의에서는 Mozilla의 인증서 데이터베이스를 번들로 포함하여 기본으로 사용하자는 제안이 있었습니다. 이 방안은 다음과 같은 여러 이유로 채택되지 않았습니다:

  • 플랫폼 신뢰 데이터베이스를 사용하면 Python 개발자의 유지 보수 부담이 줄어듭니다 – 자체 신뢰 데이터베이스를 함께 배포한다면 인증서가 폐기될 때마다 새 릴리스를 내야 합니다.
  • Linux 벤더와 그 밖의 다운스트림은 Mozilla 인증서를 번들에서 분리할 것이고, 그 결과 동작이 더욱 파편화될 것입니다.
  • 플랫폼 저장소를 사용하면 기업 내부 CA와 같은 상황을 더 쉽게 처리할 수 있습니다.

OpenSSL에는 SSL_CERT_DIR과 SSL_CERT_FILE이라는 한 쌍의 환경 변수도 있으며, 이를 사용해 Python이 다른 인증서 데이터베이스를 가리키도록 할 수 있습니다.

하위 호환성

이 변경으로 인해 일부 HTTPS 연결이 이제 핸드셰이크 중에 예외를 발생시키므로, 마치 연결이 “깨지는” 것처럼 보일 것입니다.

그러나 이는 오해의 소지가 있습니다. 실제로 이러한 연결은 현재 조용히 실패하고 있으며, HTTPS URL은 기밀성과 인증에 대한 기대를 나타냅니다. Python이 사용자의 요청이 실제로 이루어졌는지 검증하지 않는다는 사실은 버그이며, 더 나아가 “오류는 절대 조용히 넘어가서는 안 됩니다.”

그럼에도 자체 서명된 인증서나 잘못된 인증서를 사용하는 서버에 접근해야 하는 사용자는 사용자 정의 신뢰 루트를 담거나 검증을 비활성화하는 컨텍스트를 제공하여 접근할 수 있습니다(문서에서는 가능한 경우 전자를 강력히 권장해야 합니다). 또한 사용자는 필요한 인증서를 시스템 신뢰 저장소에 추가하여 전역적으로 신뢰하도록 할 수도 있습니다.

Twisted의 14.0 릴리스에서도 이와 같은 변경을 적용했으며, 반대 의견은 거의 없었습니다.

옵트아웃

단일 연결에서 인증서 검증을 옵트아웃하려는 사용자는 urllib.urlopen에 context 인자를 전달하여 그렇게 할 수 있습니다:

import ssl

# This restores the same behavior as before.
context = ssl._create_unverified_context()
urllib.urlopen("https://no-valid-cert", context=context)

이 PEP를 구현한 Python 버전에서 ssl 모듈을 몽키패치하여 검증을 전역적으로 비활성화할 수도 있지만, 이는 매우 권장하지 않습니다:

import ssl

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    # Legacy Python that doesn't verify HTTPS certificates by default
    pass
else:
    # Handle target environment that doesn't support HTTPS verification
    ssl._create_default_https_context = _create_unverified_https_context

이 지침은 주로 HTTPS 연결에서 인증서 검증을 아직 지원하지 않는 레거시 환경에 이 PEP를 구현한 새로운 Python 버전을 도입하려는 시스템 관리자를 대상으로 합니다. 예를 들어, 관리자는 Python용 표준 운영 환경(Standard Operating Environment)의 sitecustomize.py에 위의 몽키패치를 추가하여 옵트아웃할 수 있습니다. 애플리케이션과 라이브러리는 이 변경을 프로세스 전체에 적용해서는 안 됩니다(SHOULD NOT)(시스템 관리자가 제어하는 구성 설정에 따르는 경우는 예외일 수 있습니다).

특히 보안에 민감한 애플리케이션은 기반 Python 구현의 기본 동작에 의존하지 말고 항상 애플리케이션이 정의한 SSL 컨텍스트를 명시적으로 제공해야 합니다.

기타 프로토콜

이 PEP는 HTTP 클라이언트에 대해서만 이 수준의 검증을 요구할 것을 제안하며, SMTP와 같은 다른 프로토콜에 대해서는 요구하지 않습니다.

이는 브라우저가 수행하는 검증 덕분에 HTTPS 서버는 높은 비율로 올바른 인증서를 갖추고 있지만, 다른 프로토콜에서는 자체 서명되었거나 그 밖의 이유로 잘못된 인증서가 훨씬 더 흔하기 때문입니다. 다만 적어도 SMTP의 경우에는 이러한 상황이 바뀌고 있는 것으로 보이며, 향후 유사한 PEP를 위해 검토해야 합니다:

Python 버전

이 PEP는 3.4.x, 3.5, 2.7.X 브랜치 모두에서 이루어질 변경 사항을 설명합니다. 2.7.X의 경우 PEP 466에서 이미 백포트된 기능 외에도 httplib에 context (SSLContext) 인자를 백포트해야 합니다.

구현

  • LANDED: Issue 22366은 urlib.request.urlopen에 context 인자를 추가합니다.
  • Issue 22417에서 이 PEP의 핵심 내용을 구현합니다.