
제목
Ruby 코어팀 내부에서도 23년간 몰랐던 'Array#pack' 메모리 누수 취약점 긴급 분석
본문
솔직히 말해서, 처음 이 리포트를 보고 등골이 서늘했습니다.
스타트업에서 '돌아가기만 하면 장땡'이라는 마인드로 코드를 짤 때는 상상도 못 했던 일입니다.
하지만 대규모 트래픽을 감당하는 지금의 회사로 이직하고 나니, 코드 한 줄의 무게가 다르게 느껴지더군요.
오늘은 2025년 크리스마스 선물처럼(혹은 폭탄처럼) 떨어진, Ruby 언어 자체의 치명적인 취약점 이야기를 해보려 합니다.
놀랍게도 2002년부터 존재했던 버그입니다.
제 경력보다 훨씬 오래된, 그야말로 '고대 유물' 같은 코드에서 터진 문제죠.
사건의 발단은 Ruby의 Array#pack 메서드입니다.
백엔드 개발자라면 바이너리 데이터를 다룰 때 한 번쯤 써보셨을 겁니다.
배열의 요소를 템플릿 문자열에 맞춰 이진 데이터로 변환해 주는 아주 유용한 녀석이죠.
문제는 이 메서드가 반복 횟수(repeat count)를 처리하는 방식에 있었습니다.
내부 구현체인 ruby/pack.c 코드를 뜯어보면, 개발자라면 누구나 한 번쯤 겪어봤을 실수가 숨어 있습니다.
'부호 있는 정수(signed)'와 '부호 없는 정수(unsigned)'의 형변환 실수입니다.
C언어 레벨에서 반복 횟수를 파싱할 때 STRTOUL 매크로를 사용합니다.
이건 unsigned long을 반환하죠.
그런데 정작 이 값을 저장하는 len 변수는 signed long으로 선언되어 있었습니다.
감이 오시나요?
엄청나게 큰 양수(Unsigned)를 집어넣으면, 시스템은 이를 음수(Signed)로 오해하게 됩니다.
단순히 숫자가 깨지는 정도라면 "버그네" 하고 웃어넘겼을 겁니다.
하지만 이게 X 지시자(directive)와 만나면 이야기가 달라집니다.
pack 템플릿에서 X는 "1바이트 뒤로 이동하라(back up a byte)"는 명령입니다.
원래라면 이미 쓴 버퍼 내에서만 움직여야 합니다.
그런데 앞서 말한 오버플로우로 인해 시스템은 "음수만큼 반복하라"는 명령을 받게 됩니다.
이 과정에서 X 지시자는 논리적 오류를 일으키고, 결국 할당된 메모리 범위를 벗어난 데이터를 읽어오게 됩니다.
이게 무슨 뜻일까요?
네, 바로 메모리 누수(Memory Leak)입니다.
공격자가 교묘하게 조작된 문자열과 반복 횟수를 pack 메서드에 넘기면, 서버 메모리에 있는 엉뚱한 값들이 줄줄 새어 나올 수 있다는 뜻입니다.
irb 콘솔에서 간단히 재현해 봐도 결과는 충격적입니다.
의도적으로 오버플로우를 일으킨 숫자를 넣었더니, 원래 문자열 뒤에 알 수 없는 바이너리 값들이 줄줄이 붙어 나옵니다.
운이 나쁘다면 그 자리에 DB 비밀번호나 AWS 키, 혹은 고객의 민감한 세션 정보가 들어있을 수도 있습니다.
물론 현실적인 제약은 있습니다.
Array#pack의 인자를 공격자가 직접 제어할 수 있는 상황은 드뭅니다.
보통은 개발자가 하드코딩한 템플릿을 쓰니까요.
하지만 사용자 입력을 기반으로 동적으로 템플릿을 생성하거나, 외부 데이터를 파싱하는 로직이 있다면 위험할 수 있습니다.
제가 스타트업 시절 짰던 "유연한 데이터 파서" 같은 코드들이 머릿속을 스쳐 지나가더군요.
그때는 "확장성 있다"고 좋아했는데, 지금 보니 보안 구멍 그 자체였을지도 모릅니다.
이번 이슈를 보며 다시금 깨닫습니다.
우리가 믿고 쓰는 오픈소스, 프레임워크, 언어조차도 완벽하지 않습니다.
20년 넘게 전 세계 수많은 개발자가 사용해 온 Ruby조차 이런 구멍이 있었습니다.
최근 Cursor나 Claude 같은 AI 도구들이 코드를 척척 짜주면서 생산성이 비약적으로 늘었죠.
하지만 AI는 기존에 학습된 패턴, 즉 과거의 코드를 참고합니다.
만약 AI가 pack을 사용하는 레거시 패턴을 그대로 제안한다면?
그걸 검증 없이 프로덕션에 태운다면?
결국 마지막 문을 지키는 건, 우리 같은 엔지니어의 집요함이어야 합니다.
단순히 기능 구현에 그치지 않고, 라이브러리 내부를 들여다보고 원리를 의심하는 태도 말이죠.
지금 당장 우리 프로젝트에서 Array#pack을 동적으로 사용하는 곳은 없는지 grep 한번 돌려보시는 건 어떨까요?
안전한 서비스는 그런 사소한 의심에서 시작되니까요.


