Python 요청에서 SSL 보안 인증서 확인 무시

Jay Shaw 2023년10월10일
  1. SSL 보안 검사의 이유와 실패 이유 이해
  2. Python에서 SSL 보안 검사 무시
  3. 결론
Python 요청에서 SSL 보안 인증서 확인 무시

보안 SSL 인증서 없이 URL에 액세스하면 HTTP 요청이 전송될 때 예외 경고가 발생합니다. 많은 경우 이러한 URL의 SSL 인증서가 만료되어 모든 종류의 보안 문제가 발생합니다.

정보가 민감하지 않은 경우 프로그램이 Python에서 requests를 사용할 때 이러한 경고가 가라앉을 수 있습니다. 이 문서에서는 요청을 사용하여 보안 인증서 검사를 비활성화하는 여러 가지 방법을 제공합니다.

SSL 보안 검사의 이유와 실패 이유 이해

프로그램이 Python 요청을 사용하여 SSL 인증서가 만료된 URL에서 요청을 받는 경우 두 가지 예외가 발생합니다. 아래 프로그램은 이러한 예외가 무엇인지 표시합니다.

시나리오 1

이 프로그램은 테스트 목적으로 보안 인증서가 만료된 SSL 커뮤니티에서 제공한 URL을 사용합니다. SSL 보안 예외는 SSL 인증서가 만료된 URL에서만 발생한다는 점에 유의해야 합니다.

Python 요청은 유효한 SSL 인증서 또는 취소된 인증서가 있는 URL로 예외를 발생시키지 않습니다. 따라서 이 문서에서는 보안 인증서가 만료된 URL에 주로 초점을 맞춥니다.

아래 예는 첫 번째 줄에서 requests를 가져오는 간단한 프로그램을 보여줍니다. 프로그램의 두 번째 라인은 post 요청을 URL로 전송하여 'bar''baz'로 수정합니다.

URL에 포스트 요청을 보내기 위해 수행되며 프로그램 내에서 다른 의미는 없습니다.

import requests

requests.post(url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"})

이 Python 스크립트를 실행하면 SSLError 예외가 발생합니다.

....
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='expired-rsa-dv.ssl.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:841)'),))

이것은 요청을 사용하여 보안 인증서 확인을 비활성화하는 방법을 학습할 때 고려해야 할 첫 번째 예외입니다.

시나리오 2

이 프로그램은 requests를 사용하여 보안 인증서 확인을 비활성화하기 위해 verify=False를 사용하여 SSL 인증서 확인을 끕니다.

import requests

requests.post(url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"}, verify=False)

requests 라이브러리는 SSL 인증서에 대한 확인을 끌 수 있는 방식으로 구축되었지만 프로그램은 SSL 인증서가 만료된 링크에 대해 또 다른 예외를 발생시킵니다.

InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  InsecureRequestWarning,

requests를 사용하여 보안 인증서 확인을 비활성화하려면 이 두 가지 예외를 처리해야 합니다.

Python에서 SSL 보안 검사 무시

이 섹션에서는 요청을 사용하여 보안 인증서 확인을 비활성화하거나 문제를 해결하는 다양한 방법을 설명합니다. 모든 방법에는 목적이 있습니다.

requests 라이브러리에 대한 원숭이 패치 만들기

타사 라이브러리에서 보안 검사를 비활성화해야 하는 경우 requests 라이브러리를 monkey patched할 수 있습니다. 컨텍스트 관리자는 requests를 사용하여 보안 인증서 검사를 비활성화하는 패치에 사용됩니다.

requests가 패치된 후 verify 필드에 기본적으로 False 값이 지정되어 경고가 표시되지 않습니다. 이전 섹션의 시나리오 2에서 설명한 대로 verify=false를 사용하면 또 다른 경고가 발생합니다.

그런 다음 패치는 requests를 사용하여 보안 인증서 확인을 비활성화하고 경고를 억제하는 예외 처리 블록을 추가합니다. 다음 예제를 통해 이해하기 쉬울 것입니다.

아래 프로그램은 requests 라이브러리를 패치합니다. 이 코드가 무엇을 하는지 이해해 봅시다.

수입:

  1. 경고: 이 라이브러리 패키지는 예외 라이브러리의 하위 패키지입니다.
  2. contextlib: 이 Python 라이브러리는 requests 라이브러리 패치에 사용됩니다.
  3. requests: Python의 요청 라이브러리 패키지.
  4. urllib3: Python에서 HTTP 요청 및 URL을 처리하는 모듈입니다. 이 라이브러리는 만료된 SSL 인증서에 대한 예외를 발생시키는 InsecureRequestWarning 하위 모듈을 가져오는 데 사용됩니다.

반점:

처음에 프로그램은 requests 라이브러리의 기본 환경 설정을 old_merge_environment_settings 변수에 저장합니다. 이 변수는 열린 어댑터가 닫힌 후 요청을 기본 상태로 되돌리는 데 사용됩니다.

old_merge_environment_settings = requests.Session.merge_environment_settings

no_ssl_verification 메서드가 생성되고 @contextlib.contextmanager로 장식됩니다. 새 변수 opened_adapters가 생성되고 여기에 set()이 할당됩니다.

세트는 항목을 정렬되지 않은 형식으로 저장하는 데이터 유형입니다.

@contextlib.contextmanager
def no_ssl_verification():
    opened_adapters = set()

no_ssl_verification 메서드 내에서 또 다른 중첩 메서드 merge_environment_settings가 생성됩니다. 이 메서드에는 requests 모듈의 merge_environment_settings와 유사한 6개의 매개변수가 있으며 원래 모듈의 패치 역할을 합니다.

def merge_environment_settings(self, url, proxies, stream, verify, cert):

메서드 내에서 opened_adapters 변수는 url 매개 변수에서 일치하는 어댑터 쌍으로 업데이트됩니다. 모든 연결은 이 단계에서 반환되는 일부 일치하는 어댑터 쌍으로 이루어집니다.

확인은 연결당 한 번만 발생하므로 확인이 완료되면 열려 있는 모든 어댑터를 종료해야 합니다. 그렇지 않은 경우 verify=False의 효과는 이 컨텍스트 관리자가 종료된 후에 지속됩니다.

def merge_environment_settings(self, url, proxies, stream, verify, cert):

    opened_adapters.add(self.get_adapter(url))

코드의 다음 줄을 이해하려면 기사의 첫 번째 섹션을 기억하는 것이 중요합니다. 만료된 SSL 인증서가 있는 URL에서 requests.post 기능을 사용하면 두 가지 예외가 발생했습니다.

첫 번째 예외는 True 값으로 설정된 verify에 의해 발생했습니다. verify 필드는 전환 가능하지만 False 값을 지정할 수 있습니다.

두 번째 예외는 프로그램의 연결을 중지시키는 변경 불가능한 엔터티였습니다.

아래 코드는 이 문제를 해결하기 위해 기본적으로 Valse 값을 갖도록 verify 필드를 수정합니다. 이 단계를 실행하기 위해 새 변수 settings가 생성되고 old_merge_environment_settings 변수의 데이터가 할당됩니다.

데이터가 settings 변수에 저장되면 verify 필드가 False로 바뀝니다.

이는 verify의 기본값을 변경합니다. 마지막으로 이 변수가 반환됩니다.

settings = old_merge_environment_settings(self, url, proxies, stream, verify, cert)
settings["verify"] = False

return settings

업데이트된 merge_environment_settings 메서드의 데이터는 requests.Session.merge_environment_settings 기능에 할당됩니다. 그러면 verify 필드에 기본적으로 False 값이 지정됩니다.

requests.Session.merge_environment_settings = merge_environment_settings

첫 번째 예외를 처리했습니다. 변경할 수 없는 두 번째 예외는 예외 처리 블록을 사용하여 해결됩니다.

코드가 여기서 무엇을 하는지 이해합시다.

처음에는 try 블록 내부에 제기된 모든 경고를 포착하는 with 블록이 생성됩니다. 이 with 블록 내에서 warnings.simplefilter 기능은 urllib3 하위 모듈 InsecureRequestWarningignore 값을 제공하는 데 사용됩니다.

가져오기 섹션에서 배운 것처럼 InsecureRequestWarning 하위 모듈은 SSL 인증서 만료에 대한 예외를 발생시킵니다. ignore 값을 할당하면 발생한 두 번째 예외를 우회합니다.

try:
    with warnings.catch_warnings():
        warnings.simplefilter('ignore', InsecureRequestWarning)
        yield

finally 블록 내에서 old_merge_environment_settings에 저장된 원래 설정은 requests.Session.merge_environment_settings 기능에 다시 할당됩니다.

프로그램이 종료되기 전에 열려 있는 모든 어댑터를 닫아야 하므로 열린 어댑터가 프로그램 내에 있는 횟수만큼 반복되는 for 루프가 생성됩니다.

모든 어댑터는 try-except 블록을 사용하는 adapter.close() 함수를 사용하여 닫힙니다. except 블록 내부에서 반복이 전달됩니다.

for adapter in opened_adapters:
    try:
        adapter.close()
    except:
        pass

전체 코드는 다음과 같습니다.

import warnings
import contextlib

import requests
from urllib3.exceptions import InsecureRequestWarning

old_merge_environment_settings = requests.Session.merge_environment_settings


@contextlib.contextmanager
def no_ssl_verification():
    opened_adapters = set()

    def merge_environment_settings(self, url, proxies, stream, verify, cert):

        opened_adapters.add(self.get_adapter(url))

        settings = old_merge_environment_settings(
            self, url, proxies, stream, verify, cert
        )
        settings["verify"] = False

        return settings

    requests.Session.merge_environment_settings = merge_environment_settings

    try:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", InsecureRequestWarning)
            yield
    finally:
        requests.Session.merge_environment_settings = old_merge_environment_settings

        for adapter in opened_adapters:
            try:
                adapter.close()
            except:
                pass

Python에서 원숭이 패치 사용

원숭이 패치는 no_ssl_verification() 메서드를 사용하여 호출할 수 있습니다. 이 방법으로 전송된 모든 요청은 monkey patch의 지시문을 따르고 requests를 사용하여 보안 인증서 검사를 비활성화합니다.

예를 들어 보겠습니다.

no_ssl_verification 메서드는 with 블록 아래에서 호출됩니다. 이렇게 하면 블록 내부에서 메서드가 실행되고 컴파일러가 블록 밖으로 나올 때 자체적으로 닫힙니다.

블록 내에서 requests.get 호출이 만료된 SSL 인증서가 있는 URL로 전송됩니다. 기본 설정을 사용하면 예외 발생이 발생하지만 이번에는 requests.get 호출이 성공적으로 전송됩니다.

또 다른 요청은 requests와 함께 전송되며 verify 필드는 True로 설정됩니다. 기본 시나리오와 달리 이번에는 오류 예외가 발생하지 않습니다.

with no_ssl_verification():
    requests.get("https://expired-rsa-dv.ssl.com/")
    print("Modifications working Properly")

    requests.get("https://expired-rsa-dv.ssl.com/", verify=True)
    print("`Verify=true` has its meaning changed")

출력:

Modifications working Properly

`Verify=true` has its meaning changed

Verify=False에는 기본 설정으로 패치를 재설정하라는 지시문이 있는 것으로 나타났습니다. 이 필드가 requests.get() 내에서 설정되면 원숭이 패치가 기본 설정으로 재설정되어 오류 예외를 발생시킬 수 있습니다.

requests.get("https://expired-rsa-dv.ssl.com/", verify=False)
print("It resets back")

출력:

connectionpool.py:1052: InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'.
  InsecureRequestWarning,
It resets back

요청 하위 모듈 세션은 원숭이 패치와 함께 사용할 수도 있습니다.

session이라는 변수에서 requests.Session() 함수가 로드됩니다. session.verify 값은 True로 설정됩니다.

with 블록 내에서 session.get() 함수는 get 요청을 URL로 보내는 데 사용되며 verify 필드는 True로 설정됩니다. print문은 연결이 이루어진 경우 메시지를 인쇄합니다.

session = requests.Session()
session.verify = True

with no_ssl_verification():
    session.get("https://expired-rsa-dv.ssl.com/", verify=True)
    print("Works in Session")

출력:

Works in Session

컨텍스트 관리자가 종료되면 이 코드는 패치된 요청을 처리하는 모든 열린 어댑터를 닫습니다. requests는 각 세션에 대한 연결 풀을 유지하고 인증서 유효성 검사는 연결당 한 번만 발생하므로 다음과 같은 예기치 않은 이벤트가 발생합니다.

try:
    requests.get("https://expired-rsa-dv.ssl.com/", verify=False)
except requests.exceptions.SSLError:
    print("It breaks")

try:
    session.get("https://expired-rsa-dv.ssl.com/")
except requests.exceptions.SSLError:
    print("It breaks here again")

출력:

C:\Users\Win 10\venv\lib\site-packages\urllib3\connectionpool.py:1052: InsecureRequestWarning: Unverified HTTPS request is being made to host 'expired-rsa-dv.ssl.com'.
  InsecureRequestWarning,
It breaks here again

urllib3을 사용하여 Python에서 경고 비활성화

프로그램에서 requests 또는 Monkey Patching을 사용하지 않고 보안 인증서 확인을 비활성화해야 하는 경우 urllib3을 사용하면 간단한 솔루션을 제공합니다.

requestsurllib3을 가져오고 urllib3에서 InsecureRequestWarning 하위 모듈을 가져옵니다. urllib3.disable_warnings 기능을 사용하여 경고를 비활성화합니다.

requests.post() 문은 try 블록 아래에 배치되고 verify 필드는 False로 설정됩니다. 이렇게 하면 만료된 보안 인증서에 대한 보안 검사가 비활성화됩니다.

except 블록 내에서 SSLError가 오류 메시지와 함께 발생합니다.

import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

try:
    requests.post(
        url="https://expired-rsa-dv.ssl.com/", data={"bar": "baz"}, verify=False
    )
    print("Connection made successfully")

except requests.exceptions.SSLError:
    print("Expired Certificate")

출력:

Connection made successfully

결론

이 문서에서는 Python에서 requests를 사용하여 보안 인증서 확인을 비활성화하는 다양한 방법을 설명합니다. 독자는 기사를 통해 보안 검사를 쉽게 비활성화할 수 있습니다.

관련 문장 - Python Requests