부적절한 인증서 검증

Improper Certificate Validation

설명

부적절한 인증서 검증(Improper Certificate Validation)은 애플리케이션이 SSL/TLS 인증서를 올바르게 검증하지 못할 때 발생하는 취약성입니다. 이는 중간자 공격(Man-in-the-Middle, MITM)을 허용하여 공격자가 통신을 가로채거나 변조할 수 있게 합니다.

잠재적 영향

  • 중간자 공격: 공격자가 클라이언트와 서버 간의 통신을 가로채거나 변조할 수 있습니다.
  • 데이터 유출: 민감한 데이터가 공격자에게 노출될 수 있습니다.
  • 서비스 무결성 손상: 통신의 무결성과 기밀성이 손상될 수 있습니다.

해결 방법

  • 인증서 검증 활성화: SSL/TLS 인증서 검증을 반드시 활성화합니다.
  • 보안 라이브러리 사용: Python의 requests 라이브러리 등과 같은 보안 라이브러리를 사용하여 올바른 인증서 검증을 수행합니다.
  • 최신 인증서 사용: 유효하고 최신의 인증서를 사용하여 신뢰할 수 있는 CA(Certificate Authority)로부터 발급받습니다.

예시

다음은 인증서 설정을 보여 주는 발췌입니다. CA 파일과 클라이언트 인증서 경로는 운영 환경에 맞게 준비하세요. 서버 인증에는 신뢰 체인과 호스트명 검증이 모두 필요하며, 클라이언트 인증서 제시는 이를 대신하지 않습니다.

Standard Library - SSL

변경 전

python
# Unsafe ssl code
import ssl

unsafe_ctx_1 = ssl._create_stdlib_context()
unsafe_ctx_2 = ssl._create_unverified_context()

unsafe_ctx_3 = ssl.create_default_context()
unsafe_ctx_3.check_hostname = False
unsafe_ctx_3.verify_mode = ssl.CERT_NONE

변경 후

# Safe ssl code
import ssl

safe_ctx_1 = ssl.create_default_context()
safe_ctx_1.verify_mode = ssl.CERT_REQUIRED

safe_ctx_2 = ssl._create_default_https_context()

설명:

  • 변경 전: ssl._create_stdlib_context(),ssl._create_unverified_context() 및 verify_mode에 ssl.CERT_NONE을 사용시 SSL 인증서 비활성화되어 중간자 공격에 취약합니다.
  • 변경 후: 기본 클라이언트 컨텍스트는 인증서 체인과 호스트명을 검증합니다. 소켓을 연결할 때 올바른 server_hostname도 전달하세요.

OpenSSL

변경 전

python
# Unsafe OpenSSL code
from OpenSSL import SSL

unsafe_ctx_1 = SSL.Context(SSL.TLSv1_2_METHOD)

unsafe_ctx_2 = SSL.Context(SSL.TLSv1_2_METHOD)
unsafe_ctx_2.set_verify(SSL.VERIFY_NONE)

변경 후

python
# Safe OpenSSL code
from OpenSSL import SSL

ctx = SSL.Context(SSL.TLSv1_2_METHOD)
ctx.set_default_verify_paths()
ctx.set_verify(SSL.VERIFY_PEER)

설명:

  • 변경 전: set_verify 설정 없이 사용하거나 SSL.VERIFY_NONE을 사용하여 SSL 인증서 검증을 비활성화하여 중간자 공격에 취약합니다.
  • 변경 후: 기본 신뢰 저장소를 로드하고 SSL.VERIFY_PEER로 인증서 체인을 검증합니다. 연결 대상의 호스트명 검증도 별도로 구성해야 합니다. 사용자 정의 콜백을 쓰면 검증 실패를 무시하지 마세요. VERIFY_FAIL_IF_NO_PEER_CERT 같은 클라이언트 인증 요구 옵션은 서버 용도에 맞게 선택하세요.

Requests

변경 전

python
# Unsafe Requests code
import requests

requests.get('https://example.com', verify=False)

변경 후

python
# Safe Requests code
import requests

requests.get('https://example.com')

신뢰할 수 있는 사설 CA 인증서를 사용한 검증

python
# Safe Request code with self-signed certificate
import requests

requests.get('https://private.com', verify="/path/to/private_cert")

설명

  • 변경 전: verify=False를 사용하여 SSL 인증서 검증을 비활성화하여 중간자 공격에 취약합니다.
  • 변경 후:
    • verify=False와 같은 코드를 제거하거나verify=True를 사용하여 SSL 인증서 검증을 활성화하여 통신의 무결성과 기밀성을 유지합니다.
    • 사설 CA를 사용하면 verify에 신뢰할 CA 인증서 묶음의 경로를 지정하세요. 임의의 인증서를 신뢰하거나 검증을 끄는 방식으로 오류를 해결하지 마세요.

HTTPX

변경 전

python
# Unsafe HTTPX code
import httpx

httpx.get('https://example.com', verify=False)

변경 후

python
# Safe HTTPX code
import httpx

httpx.get('https://example.com')

신뢰할 수 있는 사설 CA 인증서를 사용한 검증

python
# Safe HTTPX code with a trusted private CA
import ssl
import httpx

ctx = ssl.create_default_context(cafile="/path/to/private_cert")
httpx.get('https://private.com', verify=ctx)

서버가 클라이언트 인증을 요구하면 아래처럼 클라이언트 인증서를 추가합니다. 이는 서버 인증서를 신뢰하는 CA 설정을 대신하지 않습니다.

python
import ssl
import httpx

ctx = ssl.create_default_context()
ctx.load_cert_chain(certfile="path/to/client.pem")
client = httpx.Client(verify=ctx)
response = client.get("https://example.org")

설명

  • 변경 전: verify=False를 사용하여 SSL 인증서 검증을 비활성화하여 중간자 공격에 취약합니다.
  • 변경 후:
    • verify=False와 같은 코드를 제거하여 SSL 인증서 검증을 활성화하여 통신의 무결성과 기밀성을 유지합니다.
    • 사설 CA를 신뢰해야 한다면 해당 CA를 로드한 SSLContext를 verify에 전달하세요. 클라이언트 인증서와 개인키는 클라이언트의 신원을 서버에 증명하기 위한 별도 설정입니다.

aiohttp

변경 전

python
# Unsafe aiohttp code
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://example.com", ssl=False):
            pass
        async with session.get("https://legacy.example.com", verify_ssl=False):
            pass

변경 후

python
# Safe aiohttp code
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://example.com"):
            pass

설명

  • 변경 전: ssl=False 또는 verify_ssl=False를 사용하여 SSL 인증서 검증을 비활성화하면 중간자 공격에 취약합니다.
  • 변경 후:
    • ssl=False 또는 verify_ssl=False와 같은 코드를 제거하거나, 필요한 경우 신뢰할 수 있는 ssl.SSLContext를 전달하여 SSL 인증서 검증을 활성화합니다.

urllib3

변경 전

python
# Unsafe urllib3 code
import urllib3

http = urllib3.PoolManager(cert_reqs='CERT_NONE')
http.request('GET', 'https://example.com')

변경 후

python
# Safe urllib3 code
import urllib3

http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs='/path/to/ca-bundle.crt')
http.request('GET', 'https://example.com')

설명:

  • 변경 전: cert_reqs='CERT_NONE'을 사용하여 SSL 인증서 검증을 비활성화하여 중간자 공격에 취약합니다.
  • 변경 후: cert_reqs='CERT_REQUIRED'을 사용하도록 적용하고 개인 인증서 사용이 필요할 경우ca_certs에 인증서 경로를 추가하여 SSL 인증서 검증을 활성화하여 통신의 무결성과 기밀성을 유지합니다.

참조