소프트웨어의 세계에서 가장 무서운 버그는 종종 가장 조용하게 다가온다. 화려한 취약점처럼 언론의 헤드라인을 장식하지도, 대규모 데이터 유출처럼 즉각적인 피해를 입히지도 않는다. 그저 시스템의 심장부에 조용히 스며들어, 어느 순간 예기치 못한 방식으로 폭발할 기회를 엿볼 뿐이다. 프로토타입 오염(Prototype Pollution)은 그런 종류의 위협이다. 마치 공기 중의 산소처럼 존재하지만, 대부분의 개발자는 그 위험성을 인식하지 못한다. 이번에 공개된 Langfuse의 원격 코드 실행(RCE) 취약점은 프로토타입 오염이 어떻게 단순한 설정 오류에서 시작해 치명적인 보안 위협으로 변모할 수 있는지를 여실히 보여준다.
Langfuse는 LLM 애플리케이션의 관찰 가능성을 위한 오픈소스 도구다. OpenTelemetry(OTel) 트레이스를 수집하고 분석하는 과정에서, 이 도구는 사용자로부터 입력된 데이터를 안전하게 처리해야 한다. 하지만 문제는 그 ‘안전하게’라는 단어에 있었다. 개발자들은 종종 데이터의 형태가 명확하고 통제된 환경에서만 동작할 것이라고 가정한다. 그러나 현실의 네트워크는 언제나 악의적인 의도를 가진 누군가가 존재하며, 그들은 시스템의 가장 약한 고리를 찾아내기 위해 끊임없이 탐색한다.
이번 취약점의 핵심은 OTel 트레이스 요청에 포함된 JSON 데이터의 프로토타입을 오염시키는 데 있다. JavaScript의 프로토타입 기반 상속 구조는 강력하면서도 위험하다. 모든 객체는 Object.prototype을 상속받기 때문에, 이 프로토타입을 오염시키면 애플리케이션 전체에 걸쳐 예기치 못한 동작을 유발할 수 있다. 예를 들어, 공격자가 Object.prototype에 악의적인 메서드를 추가하면, 그 메서드는 이후 생성되는 모든 객체에서 호출될 수 있다. 이는 마치 도시의 수도관에 독을 타는 것과 같다. 처음에는 아무도 눈치채지 못하지만, 시간이 지나면서 그 영향은 걷잡을 수 없이 퍼져나간다.
Langfuse의 경우, 이 프로토타입 오염이 eval() 함수 호출로 이어졌다. eval()은 그 자체로 이미 보안의 적으로 간주되는 함수다. 문자열로 전달된 코드를 실행하는 이 함수는, 공격자에게 시스템에 대한 완전한 제어권을 제공할 수 있는 문을 열어준다. 문제는 이 eval() 호출이 의도된 기능이 아니었다는 점이다. 개발자들은 아마도 “이 데이터는 신뢰할 수 있는 출처에서만 올 거야”라고 가정했을 것이다. 하지만 소프트웨어는 언제나 그 가정을 깨뜨릴 준비가 되어 있다.
보안은 취약점을 찾고 고치는 것이 아니라, 취약점이 존재할 수 있음을 가정하고 시스템을 설계하는 것이다.
이번 사건은 몇 가지 중요한 교훈을 던진다. 첫째, 프로토타입 오염은 더 이상 이론상의 위협이 아니다. 실제 제품에서 치명적인 RCE로 이어질 수 있으며, 이는 개발자들이 이 취약점에 대해 더 많은 관심을 가져야 함을 의미한다. 둘째, 관찰 가능성 도구는 보안의 사각지대가 될 수 있다. 이러한 도구들은 종종 시스템의 가장 깊은 곳에 접근하기 때문에, 이들이 안전하지 않다면 전체 인프라가 위험에 노출될 수 있다. 마지막으로, eval()과 같은 위험한 함수의 사용은 최대한 피해야 한다. 불가피한 경우라도, 입력 데이터의 검증과 무해화는 필수적이다.
프로토타입 오염은 마치 소프트웨어의 DNA를 변형시키는 것과 같다. 작은 변화가 전체 시스템에 파문을 일으키며, 그 결과는 예측 불가능하다. 이번 Langfuse의 사례는 그런 위험성이 현실로 나타났음을 보여준다. 개발자들은 이제 더 이상 “이런 일은 나에게 일어나지 않을 거야”라고 생각할 수 없다. 보안은 모든 소프트웨어의 기본 요소여야 하며, 특히 프로토타입 오염과 같은 은밀한 위협에 대해서는 더욱 철저한 대비가 필요하다.
이 취약점에 대한 자세한 분석은 여기에서 확인할 수 있다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.