공대생의 이것저것☀︎

[Security] eMudhra 인증서 사고 정리 - 개인키 값이 '4'였던 인증기관 본문

IT/Security

[Security] eMudhra 인증서 사고 정리 - 개인키 값이 '4'였던 인증기관

공대생92 2026. 10. 27. 11:00

 

인도의 인증기관(CA) eMudhra가 만든 중간 인증서 8개의 개인키가 사실상 공개된 상태였다는 사실이 10월 17일 드러났다. 해킹으로 빼돌린 게 아니다.

 

키 자체가 너무 작은 숫자라서, 누구나 공개된 인증서만 보고 몇 번 계산하면 개인키를 알아낼 수 있었다. 그중 하나는 개인키 값이 그냥 4였다.

 

이 키가 있으면 브라우저와 운영체제가 믿는 인증서를 아무 도메인으로나 찍어낼 수 있다. 은행 사이트 인증서를 위조해 중간에서 통신을 엿보는 공격이 가능했다는 뜻이다. 문제의 인증서는 2025년 4월에 만들어졌으니, 약 18개월 동안 이 상태였다.

 

다행히 아직 실제로 악용됐다는 증거는 없고, 크롬과 파이어폭스는 신고 당일 차단했다. 다만 브라우저 바깥은 사정이 다르다. 이 글은 그 차이에 초점을 맞췄다.

 

 


 

개인키가 4라는 말의 의미

HTTPS 자물쇠는 "신뢰 체인"으로 동작한다. 브라우저에는 믿을 만한 루트 인증서 목록이 들어 있고, 루트가 중간 인증서에 서명하고, 중간 인증서가 다시 각 웹사이트 인증서에 서명한다.

 

그래서 중간 인증서의 개인키를 가진 사람은 루트가 보증하는 인증서를 마음대로 발급할 수 있다.

 

이번 키는 P-384 타원곡선 방식이다. 이 방식에서 개인키는 하나의 숫자이고, 공개키는 그 숫자만큼 정해진 점(G)을 더한 결과다.

 

정상이라면 개인키는 384비트, 자릿수로 100자리가 넘는 무작위 숫자라서 거꾸로 찾아내는 게 불가능하다. 그런데 eMudhra의 8개 키는 4부터 237 사이의 작은 수였다.

 

1×G, 2×G, 3×G…를 차례로 계산해 공개키와 같아지는 순간을 찾으면 끝난다. 최대 255번이면 된다.

 

얼마나 쉬운지 직접 확인해 봤다. 실제 인증서가 아니라, 개인키를 일부러 4로 만든 테스트 키를 만들고 파이썬으로 1부터 대입했다.

 

from cryptography.hazmat.primitives.asymmetric import ec
curve = ec.SECP384R1()
target = ec.derive_private_key(4, curve).public_key().public_numbers()  # 테스트 키
for d in range(1, 256):
    if ec.derive_private_key(d, curve).public_key().public_numbers() == target:
        print("개인키:", d); break

 

4를 찾는 데 2밀리초도 걸리지 않았고, 255개를 전부 계산해도 0.1초가 안 됐다. 암호 알고리즘이 약한 게 아니라, 알고리즘에 넣은 숫자가 잘못된 것이다.

 

키가 왜 이렇게 만들어졌나

eMudhra의 설명은 이렇다. 2025년 4월 키 생성 작업(키 세리머니) 때 쓴 스크립트에 오류가 있어서 장비(HSM) 안에 정상 키 말고 약한 키 세트가 하나 더 생겼다.

 

두 세트의 이름이 비슷했고, 담당자가 인증서 요청을 만들 때 약한 쪽을 골랐다. 이 인증서들은 2025년 5월 해외 대형 인증기관 Sectigo의 교차 서명까지 받았다.

 

영향받은 건 웹사이트용(TLS) 중간 인증서 6개와 코드 서명용 2개다. eMudhra는 8개 중 실제 운영에 쓰인 건 하나뿐이고 일반 사용자용 인증서를 발급한 적은 없다고 밝혔다.

 

구글도 공개 인증서 기록(CT 로그)을 뒤져 봤지만 이 중간 인증서로 발급된 웹사이트 인증서는 찾지 못했다.

 

개인적으로 가장 이해하기 어려운 부분은 이 키들이 감사를 통과했다는 점이다. 인증기관의 키 생성은 보통 감사인이 지켜보는 가운데 절차대로 진행한다.

 

하지만 절차가 지켜졌는지를 보는 것과 만들어진 키가 정상인지를 보는 것은 다른 문제다. 공개키가 작은 정수배인지 확인하는 검사는 몇 줄이면 되는데, 그 검사를 하지 않았다.

 

 

브라우저는 막았지만 서버는 다르다

브라우저 쪽 대응은 빨랐다. 크롬은 신고받고 두 시간이 안 돼 자체 차단 목록(CRLSet)으로 막았고, 파이어폭스도 같은 날 밤 OneCRL로 차단했다.

 

마이크로소프트는 20일, 애플은 22일 차단했다. 크롬은 10월 27일(미국 시간) 루트 저장소에서 eMudhra의 emSign ECC Root CA - G3 자체를 빼기로 했다.

 

게다가 크롬, 파이어폭스, 사파리는 CT 로그에 올라가지 않은 인증서를 믿지 않는다. 공격자가 몰래 인증서를 찍어내도 CT에 기록하지 않으면 브라우저에서는 쓸 수 없고, 기록하면 들킨다. 브라우저 사용자는 비교적 안전했다고 봐도 된다.

 

문제는 브라우저가 아닌 프로그램이다. 서버에서 돌아가는 curl, 파이썬, Node.js, Go, Java, git, 패키지 매니저는 대부분 디스크에 있는 인증서 묶음 파일을 믿는다.

 

인증서 폐기 여부도, CT 기록도 기본으로 확인하지 않는다. 보안 연구자 Scott Helme가 이번 중간 인증서로 api.stripe.com 인증서를 위조해 시험해 보니, 리눅스와 macOS에서 curl, 파이썬, Node.js가 기본 설정 그대로 받아들였다.

 

 

여기서도 하나 직접 확인했다. 테스트용 우분투 24.04 환경을 봤더니, 운영체제의 ca-certificates는 이미 패치된 버전이라 emSign ECC Root CA - G3가 빠져 있었다.

 

그런데 같은 서버의 Node.js 22.22.0이 내장한 루트 목록에는 그 루트가 그대로 남아 있었다. Node.js는 기본적으로 운영체제 인증서 저장소가 아니라 자기 안에 든 목록을 쓴다. 운영체제를 업데이트해도 서버 위의 모든 프로그램이 안전해지지는 않는다.

 

공교롭게도 이 문제를 고친 Node.js 버전(26.12.1, 24.22.1, 22.23.4)은 지난번 Node.js 보안 업데이트 글에서 다룬 Brotli 패치 버전과 같다. 그 업데이트를 이미 했다면 이번 건도 같이 해결된 셈이고, 아직이라면 미룰 이유가 하나 더 생겼다.

 

주요 환경별 조치는 아래와 같다. 10월 27일 기준이고, 이 밖의 도구는 참고 자료의 정리 사이트에서 확인할 수 있다.

 

환경 조치 비고
크롬·파이어폭스·엣지 자동 차단 별도 조치 없음 (파이어폭스는 원격 설정을 꺼 둔 경우 158.0.1 이상)
윈도우·안드로이드·애플 기기 자동 차단 macOS 기본 curl은 예외 (아래 참고)
우분투·데비안 등 리눅스 ca-certificates 업데이트 배포판 보안 공지 확인, 컨테이너는 이미지 재빌드
Node.js 26.12.1 / 24.22.1 / 22.23.4 내장 루트 목록을 써서 OS 업데이트와 별개
Go 1.27.3 / 1.26.10로 다시 빌드 1.25 이하는 패치 없음
파이썬 certifi 2026.10.19 이상 pip, uv 같은 도구도 각자 업데이트

 

macOS는 조금 특이하다. 시스템 자체는 차단했지만, 기본으로 들어 있는 /usr/bin/curl은 /etc/ssl/cert.pem 파일을 보는데 이 파일은 아직 갱신되지 않았다.

 

애플은 다음 업데이트 때 고치겠다고 했다. 그 전까지는 정리 사이트의 안내대로 해당 루트를 파일에서 직접 지우거나, Homebrew로 설치한 curl을 쓰는 방법이 있다.

 

가장 오래 남을 곳은 펌웨어를 업데이트하지 않는 장비다. 공유기, 프린터, 스마트 TV, 산업용 장비처럼 인증서 목록이 펌웨어에 박혀 있는 기기는 이 인증서가 만료되는 2039년까지 계속 믿을 수 있다.

 

내 생각: 유출이 아니라 처음부터 공개였다

사고 대응에서 이번 건이 까다로운 이유는 "언제 누가 가져갔나"를 따질 수 없다는 점이다. 서버가 해킹당해 키가 유출됐다면 침입 시점과 경로를 조사해서 피해 범위를 좁혀 갈 수 있다.

 

이번에는 키가 만들어진 순간부터 공개 인증서 안에 사실상 적혀 있었다. 18개월 동안 누가 계산해 봤는지 알 방법이 없다.

 

CT 로그에서 위조 인증서가 안 나왔다는 것도 브라우저 기준의 안심이다. 브라우저가 아닌 프로그램만 노리는 공격자라면 CT에 기록할 이유가 없다.

 

내부 API 통신, 업데이트 서버 접속, 클라우드 SDK 호출처럼 사람이 화면으로 보지 않는 통신이 바로 그런 대상이다. 개인적으로는 "악용 증거 없음"을 "피해 없음"으로 읽지 않는 게 맞다고 본다.

 

코드 서명용 중간 인증서 2개도 가볍게 넘길 일은 아니다. 윈도우는 eMudhra의 코드 서명 루트를 직접 신뢰하고 있었기 때문에, 이 키로 서명한 악성코드는 정상 회사가 만든 프로그램처럼 보일 수 있었다.

 

마이크로소프트가 차단한 뒤라도, 오프라인 환경이나 업데이트가 막힌 PC에서는 한 번 더 확인할 필요가 있다.

 

신뢰하는 루트 인증서 목록도 관리해야 할 자산이다.

 

우리 서버가 어떤 인증서 묶음을 쓰는지, 그게 운영체제 것인지 런타임 내장인지 애플리케이션 폴더에 따로 들어 있는지 대부분의 조직은 정확히 모른다. 이번 같은 사고가 다시 나면 그 목록이 있어야 반나절 안에 대응할 수 있다.

 

서버 점검 순서

  • 서버 운영체제의 ca-certificates 패키지를 업데이트한다
  • Node.js, Go, 파이썬(certifi) 등 런타임을 위 버전 이상으로 올리고, Go 프로그램은 다시 빌드한다
  • 컨테이너는 업데이트된 베이스 이미지로 다시 빌드해 배포한다
  • 확인용 주소로 점검한다. curl -sI https://dv-g3b-emudhra.we-have-a-problem.com/ 실행 시 인증서 오류가 나야 정상이다. 접속에 성공하면 아직 문제의 인증서를 믿고 있는 것이다
  • macOS에서 기본 curl, git, pip를 쓰는 환경은 /etc/ssl/cert.pem을 조치하거나 Homebrew 도구를 쓴다
  • 우리 회사 사이트나 API가 emSign ECC Root CA - G3 계열 인증서를 쓰고 있다면 다른 인증기관 인증서로 바꾼다
  • 펌웨어 업데이트가 안 되는 장비(공유기, 프린터, OT 장비) 목록에 이번 건을 기록한다

 


 

참고 자료

반응형
Comments