AI 코더들이 계속해서 배포하는 verify=False 패턴
요약
AI 어시스턴트가 SSL 에러를 해결하기 위해 보안에 취약한 `verify=False` 패턴을 빈번하게 생성하는 문제를 다룹니다. 이 패턴은 중간자 공격(MITM)에 노출될 위험이 크며, AI가 근본적인 해결책 대신 가장 빠른 해결책을 제시하기 때문에 발생합니다.
핵심 포인트
- verify=False는 암호화는 유지하지만 서버의 신원 확인을 제거함
- 인증서 검증 비활성화는 중간자 공격(MITM)에 매우 취약함
- AI는 에러를 즉시 멈추기 위해 가장 짧은 경로인 보안 무시 패턴을 선택함
- 개발자는 AI가 생성한 보안 취약 코드를 반드시 검토해야 함
당신의 AI 어시스턴트가 방금 SSL 에러를 침묵시켰습니다. 이제 코드는 작동합니다.
그것이 비활성화한 SSL 체크는 당신이 의도한 서버에 연결되었는지 확인하는 기능입니다. 이 기능이 꺼지면, 클라이언트와 엔드포인트 사이에 위치한 공격자가 제어하는 서버를 포함하여 어떤 서버라도 인증서를 제시할 수 있으며, 클라이언트는 아무런 의문 없이 이를 수락하게 됩니다.
BrassCoders는 Bandit B501을 통해 verify=False를 HIGH 심각도로 분류합니다. 이것이 해당 패턴이며, 왜 AI 어시스턴트가 이를 생성하는지, 그리고 실제 해결책은 어떤 모습인지 알아봅니다.
verify=False가 실제로 하는 일
BrassCoders가 verify=False를 HIGH 심각도의 결과로 분류하는 이유는, 인증서 검증을 비활성화하는 것이 TLS 연결에서 암호화가 아닌 신원 확인(identity check)을 제거하기 때문입니다. 연결은 여전히 암호화되어 있습니다. 즉, 당신의 클라이언트와 응답하는 서버 사이의 트래픽은 전송 중에 여전히 보호됩니다. 사라진 것은 응답하는 서버가 당신이 도달하고자 했던 바로 그 서버라는 보장입니다.
TLS 인증서 검증은 두 단계로 작동합니다. 첫째, 서버는 신뢰할 수 있는 인증 기관 (CA, Certificate Authority)에 의해 서명된 인증서를 제시합니다. 둘째, 클라이언트는 해당 인증서의 도메인이 연결한 도메인과 일치하는지 확인합니다. verify=False는 이 두 단계를 모두 건너뜁니다. 사기 인증서를 제시하거나 완전히 다른 도메인의 인증서를 제시하는 서버도 조용히 통과됩니다.
Python requests 문서의 보안 섹션은 그 결과를 직접적으로 설명합니다: "verify=False는 서버 SSL 인증서를 검증하지 않음을 의미하며, 이는 중간자 공격 (man-in-the-middle attacks)에 취약하게 만듭니다."
AI 어시스턴트가 이를 생성하는 이유
개발 중에 SSL 인증서 에러는 흔히 발생합니다. 자체 서명된 인증서 (Self-signed certificates), HTTPS 트래픽을 가로채는 기업용 프록시, 그리고 잘못 설정된 로컬 환경 모두 ssl.SSLCertVerificationError 또는 이와 유사한 에러를 발생시킵니다. 에러를 AI 어시스턴트와 공유하는 개발자는 에러를 멈추게 만드는 단 하나의 변경 사항, 즉 verify=False를 돌려받게 됩니다.
어시스턴트는 이것이 일회성 스크립트인지 아니면 프로덕션 서비스 호출인지 알지 못합니다. 엔드포인트가 자격 증명 (credentials)을 처리하는지 아니면 공개 데이터를 처리하는지도 알지 못합니다. 어시스턴트는 눈에 보이는 문제에 대해 가장 짧은 해결책을 만들어냅니다. 에러는 사라집니다. 코드는 배포됩니다.
이 패턴은 인간이 작성한 코드보다 AI가 생성한 코드에서 훨씬 더 자주 나타나는데, 여기에는 특정한 이유가 있습니다. SSL 에러를 마주한 인간은 보통 verify=False가 무엇을 의미하는지 이해하고 있기 때문에 인증서를 수정하거나 CA 번들 (CA bundle)을 조사합니다. 반면 AI 어시스턴트는 에러를 멈추게 만드는 변경 사항을 기본값으로 선택하며, verify=False는 그곳으로 가는 가장 짧은 경로입니다.
공격 경로 (The Attack Path)
OWASP Transport Layer Security Cheat Sheet는 인증서 검증을 비활성화했을 때 발생하는 중간자 공격 (man-in-the-middle, MITM) 시나리오를 다룹니다. verify=False를 사용하는 서비스 호출의 공격 경로는 다음과 같습니다:
- 공격자가 Python 클라이언트와 의도한 엔드포인트 사이 — 동일 네트워크 상, ARP 스푸핑 (ARP spoofing), DNS 포이즈닝 (DNS poisoning), 또는 침해된 프록시 (compromised proxy)를 통해 — 위치를 잡습니다.
- 공격자가 연결을 위해 자신의 TLS 인증서를 제시합니다.
verify=False설정이 된 클라이언트는 누가 인증서를 발급했는지 또는 도메인이 일치하는지 확인하지 않고 해당 인증서를 수락합니다.- 공격자는 트래픽을 전달하기 전에 이를 복호화하여 읽고, 잠재적으로 수정하며, 다시 암호화합니다.
헤더나 요청 본문 (request body)에 API 키, 세션 토큰, 또는 사용자 자격 증명을 전송하는 서비스 호출의 경우, 공격자는 이제 해당 자격 증명을 갖게 됩니다. 애플리케이션 입장에서는 연결이 암호화된 것처럼 보였을 것입니다. 로그에서도 아무런 문제도 나타나지 않았습니다.
이 시나리오는 이론적인 것이 아닙니다. 기업 네트워크, 클라우드 제공업체 네트워크, 그리고 공유 호스팅 환경 모두 MITM 공격이 가능한 조건을 형성합니다. verify=False는 이 중 어떤 환경에서도 공격을 매우 용이하게 만듭니다.
BrassCoders가 탐지하는 사항 (What BrassCoders Flags)
BrassCoders는 Bandit의 B501 규칙을 통해 verify=False를 탐지합니다. 이 규칙은 requests.get, requests.post, requests.put, requests.delete, requests.patch, requests.head 및 session.get, session.request와 같은 세션 기반 호출을 모두 포함합니다. 해당 탐지 결과는 호출이 개발용 유틸리티처럼 보이든 운영 서비스 통합처럼 보이든 관계없이 verify=False가 나타나는 모든 곳에서 발생합니다.
# 이 모든 항목이 B501을 발생시킵니다
requests.get(api_url, verify=False)
requests.post(endpoint, json=payload, verify=False)
...
이 탐지 결과는 HIGH (높음) 심각도입니다. Claude Code는 .brass/ai_instructions.yaml 탐지 파일을 읽고 컨텍스트를 적용합니다: 엔드포인트가 외부인가 내부인가, 호출 경로가 자격 증명 (credentials)을 처리하는가, 이것이 CLI 유틸리티인가 웹 서비스인가? 스캐너는 모든 호출 지점에서 위험을 명시하며, AI 분류 (triage) 레이어는 어떤 인스턴스가 실제 위협인지, 그리고 어떤 인스턴스가 특정 컨텍스트 내에서 허용 가능한지를 식별합니다.
실제 해결 방법 (The Actual Fix)
verify=False는 SSL 에러에 대한 해결책이 아니라 우회책 (bypass)입니다. 인증서 에러에는 원인이 있으며, 그 원인은 검증을 비활성화하지 않고도 해결할 수 있습니다.
공용 HTTPS 엔드포인트의 경우: 공용 API에 대한 SSL 에러의 가장 흔한 원인은 오래된 CA 번들 (CA bundle)입니다. 이를 업데이트하세요:
pip install --upgrade certifi
requests 라이브러리는 기본적으로 certifi의 CA 번들을 사용합니다. 오래된 certifi에는 최근에 추가된 루트 인증서 (root certificates)가 포함되어 있지 않을 수 있습니다.
내부 환경의 자가 서명 인증서 (self-signed certificates)의 경우: 검증을 완전히 비활성화하는 대신 CA 인증서 파일을 명시적으로 전달하세요:
# 검증을 비활성화하는 대신 특정 CA 인증서로 검증합니다
requests.get(internal_url, verify='/path/to/internal-ca.crt')
이렇게 하면 임의의 발급자로부터 인증서를 수락하지 않으면서도, 클라이언트가 해당 인증서가 내부 CA에 의해 서명되었는지 확인하는 신원 검증 (identity verification)을 유지할 수 있습니다.
HTTPS를 가로채는 기업용 프록시(corporate proxies)의 경우: 프록시의 루트 인증서(root certificate)를 시스템 신뢰 저장소(system trust store)에 설치하십시오. 대부분의 프록시는 정확히 이 목적을 위해 루트 인증서를 배포합니다. 설치가 완료되면, REQUESTS_CA_BUNDLE 환경 변수를 설정하여 해당 인증서가 포함된 번들(bundle)을 요청(requests)이 가리키도록 지정하십시오:
export REQUESTS_CA_BUNDLE=/path/to/combined-ca-bundle.crt
위의 모든 옵션은 신원 확인(identity check)을 유지합니다. 이 중 어떤 것도 verify=False를 배포할 필요를 요구하지 않습니다.
코드베이스 스캐닝 (Scanning the Codebase)
BrassCoders는 메인 애플리케이션의 일부가 아닌 파일들 — 테스트 유틸리티(test utilities), 일회성 스크립트(one-off scripts), 내부 도구(internal tooling), 마이그레이션 헬퍼(migration helpers) — 을 포함하여 프로젝트 내의 모든 Python 파일을 B501 항목에 대해 스캔합니다. 실제 엔드포인트와 토큰을 공유하는 테스트 헬퍼 내의 verify=False 역시 여전히 verify=False입니다. 스캔은 오프라인으로 실행됩니다:
pip install brasscoders
brasscoders scan /path/to/your/project
탐지 결과는 파일 경로, 줄 번호, 심각도(severity)와 함께 .brass/ai_instructions.yaml에 나타납니다. Claude Code에서 해당 파일을 열고, 어떤 B501 탐지 결과가 프로덕션 서비스 호출 경로(production service call paths)에 있는지 분류(triage)하도록 요청하십시오. 그것들이 가장 먼저 수정해야 할 대상입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기