TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 30 READS

str.lower()가 보안 취약점이 되는 순간: 파이썬 IDNA와 유니코드 버전 고정 문제

str.lower()가 보안 취약점이 되는 순간: 파이썬 IDNA와 유니코드 버전 고정 문제
SOURCE IMAGE · HACKER NEWS

도메인 이름은 원래 ASCII 문자만으로 표현하도록 설계된 여러 인터넷 표준 위에 놓여 있다. 그러나 실제 세계는 라틴 알파벳만 쓰지 않기 때문에, 유니코드로 표현된 문자열을 도메인에서 쓸 수 있는 ASCII로 변환하는 매핑이 필요했다. 이 변환 절차의 한 축이 바로 IDNA(Internationalizing Domain Names in Applications)이며, 그 안에서 문자열을 정규화하는 NamePrep이 핵심 역할을 한다. NamePrep은 RFC 3491에 정의된 StringPrep(RFC 3454)의 프로파일로, 이른바 'IDNA 2003'의 구성 요소다. IDNA 2003은 이후 RFC 5890·5891·5892·5893으로 이뤄진 IDNA 2008로 대체됐다.

파이썬 생태계에서 이 두 세대는 서로 다른 경로로 지원된다. IDNA 2003은 표준 라이브러리의 idna 코덱(str.encode('idna'))과 stringprep 모듈로 구현돼 있고, IDNA 2008은 PyPI의 별도 idna 패키지로 제공된다. 일반적인 권고는 최신 표준인 idna 패키지를 쓰라는 것이지만, 하위 호환이나 기존 시스템과의 정합성 때문에 구형 동작이 필요한 경우도 여전히 존재한다. 이번 이야기의 무대가 바로 이 구형 경로다.

문제의 한 줄: str.lower()

StringPrep은 3.2절에서 '케이스 폴딩(case folding)' 단계를 정의한다. 대소문자를 무시한 비교가 가능하도록 모든 문자를 소문자화 계열로 매핑하는 과정으로, RFC 3454의 매핑 표 B.2와 B.3을 거친다. B.2는 사실상 유니코드 규칙에 따라 모든 문자를 소문자로 바꾸는 str.lower()에 해당하고, B.3은 그 예외 목록을 담는다. 그래서 이 단계를 파이썬으로 옮기면 str.lower()를 호출하는 코드가 자연스럽게 나온다. 겉보기에는 아무 문제가 없어 보이지만, 바로 이 str.lower() 호출이 취약점이었다.

원인은 버전 불일치다. 파이썬의 str.lower()는 그 인터프리터가 함께 배포하는 유니코드 데이터를 사용한다. 자신이 쓰는 유니코드 버전은 unicodedata.unidata_version으로 확인할 수 있다. 그런데 StringPrep은 특정 버전, 즉 유니코드 3.2.0의 케이스 폴딩 규칙에 의존해 일관되게 동작하도록 설계됐다. RFC 3454의 B.2·B.3 표 자체가 유니코드 3.2.0 규칙을 표로 굳혀 놓은 것이기 때문이다. 실제로 파이썬은 이 알고리즘을 위해 모든 버전에서 유니코드 3.2.0 데이터베이스(unicodedata.ucd_3_2_0)를 별도로 제공한다.

명세와 구현이 어긋날 때

결국 str.lower()를 그대로 부르면 인터프리터에 실린 최신 유니코드 규칙으로 소문자화가 이뤄진다. 이는 명세가 요구하는 3.2.0 규칙과 달라질 수 있고, 구현이 명세에서 벗어나는 그 지점이 바로 보안 취약점이 된다. 도메인 정규화처럼 '두 문자열이 같은가'를 판정하는 곳에서 규칙이 미묘하게 어긋나면, 서로 다르게 취급돼야 할 이름이 같게, 혹은 그 반대로 처리될 여지가 생긴다. 신뢰 경계에서 문자열 동등성 판단은 곧 신원 판단이므로, 사소해 보이는 버전 차이가 실질적 위험으로 번진다.

수정 방식은 알고리즘을 갈아엎는 대신 결정론성을 되살리는 쪽이었다. 각 유니코드 코드포인트를 하나씩 훑어, 인터프리터에 실린 유니코드 버전의 str.lower() 결과와 유니코드 3.2.0의 결과가 달라지는 경우를 기록해 새로운 예외 목록으로 만들었다. 그 결과 해당 함수에 한해 str.lower()가 마치 유니코드 3.2.0을 쓰는 것처럼 동작하게 되고, IDNA 2003 구현이 다시 명세와 일치하게 됐다. 이 취약점은 Bitshift가 제보하고 Stan Ulbrych가 대응을 공동 개발했으며 Marc-Andre Lemburg와 Petr Viktorin이 검토했다. 식별자는 CVE-2026-17084다.

실무자가 새겨둘 지점

이 사례가 주는 교훈은 특정 CVE의 심각도 자체보다 그 구조에 있다. 명세가 '특정 버전의 유니코드'를 못박고 있다면, 언어가 기본 제공하는 str.lower()·정규화 함수는 시간이 지나며 데이터가 바뀌는 움직이는 표적이라는 사실을 잊지 말아야 한다. 표준을 구현하는 코드는 자신이 어떤 유니코드 버전 위에서 도는지 명시적으로 통제해야 하며, 파이썬이라면 ucd_3_2_0 같은 버전 고정 데이터를 의도적으로 선택해야 한다. 편의를 위해 무심코 부른 str.lower() 한 줄이 명세 준수 여부를 바꿔 놓을 수 있다는 점이 핵심이다.

다만 한계도 분명히 해둘 필요가 있다. 이 문제는 IDNA 2003 계열의 구형 경로에 국한되며, 애초에 권장되는 IDNA 2008(idna 패키지)을 쓴다면 직접적인 영향권 밖이다. 국제화 도메인이나 대소문자 무시 비교를 다루는 서비스라면 자신이 어느 경로를 쓰는지부터 점검하고, 구형 코덱에 의존하고 있다면 파이썬 보안 업데이트를 반영하거나 최신 표준으로 옮기는 편이 안전하다. 참고로 이 작업은 파이썬 소프트웨어 재단의 보안 상주 개발자 활동의 일부로, Alpha-Omega의 후원으로 이뤄졌다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://sethmlarson.dev/when-str-lower-is-a-security-vulnera...
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...