의존성 업그레이드 위험과 ‘그냥 업그레이드하기’ 방식이 실패하는 이유

지원 종료(EOL)된 PHP용 php2shell 패치(CVE-2026-109618 및 CVE-2026-109619)가 출시되었습니다. 수요가 많아 평소보다 영업팀의 답변이 늦어지고 있으므로, 패치된 패키지를 가장 빠르게 확보하려면 온라인에서 Runtimes용 ELS를 구매하시기 바랍니다 (새 탭에서 열림). 패치 FAQ 읽어보기

이 블로그 글의 AI 요약으로 바로 가기

  • Perplexity를 사용하여 이 게시물을 요약해 주세요
  • Gemini로 이 게시물을 요약해 주세요

주요 내용

  • 버전 번호 변경은 패치가 아니며, 종속성 업그레이드로 인해 호환성을 깨는 변경 사항이 발생할 수 있습니다.
  • 강제 업그레이드는 빌드 단계에서 명백한 오류를 일으키거나, 테스트에서는 포착되지 않는 미묘한 런타임 성능 저하를 초래할 수 있습니다.
  • 전이성 취약점은 영향을 받는 패키지가 직접적인 종속성보다 여러 계층 아래에 위치해 있을 수 있기 때문에 해결을 복잡하게 만듭니다.
  • 지금 당장 업그레이드하는 것이 안전하지 않은 경우, 보완 조치를 취하거나 기존 시스템에 패치를 적용하는 것도 여전히 유효한 대안입니다.

“그냥 업그레이드해.” 또는 이와 비슷한 표현인 “그냥 의존성을 업데이트해.”

소프트웨어 보안 관련 분야에서 일하고 있다면, 아마도 이와 비슷한 이야기를 셀 수 없을 만큼 많이 들어보셨을 겁니다. 취약점이 발견되고, 최신 버전에 수정 사항이 반영되면 해결책은 뻔해 보입니다. 버전을 올리고 넘어가면 되는 것이죠.

실전 환경의 소프트웨어는 대개 그런 식으로 작동하지 않습니다. 업그레이드는 종종 올바른 장기적 해결책이며, 의존성을 최신 상태로 유지하면 기술적 부채를 줄이고, 팀이 업스트림 보안 수정 사항을 활용할 수 있게 하며, 향후 마이그레이션을 더 수월하게 만듭니다. 하지만 모든 취약점을 즉각적인 의존성 업그레이드의 필수 조건으로 간주하는 것은, 사람들이 실제로 의존하고 있는 소프트웨어를 유지 관리하는 데 따르는 복잡한 현실을 간과하는 것입니다. 프로덕션 애플리케이션은 API, 런타임, 라이브러리, 구성, 빌드 도구, 통합 기능, 그리고 가정들이 테스트를 거쳐 현재 서로 잘 작동하고 있는 조합입니다. 한 부분을 변경하면 대개 여러 부분이 함께 변경됩니다.

이로 인해 개발자들은 두 가지 타당한 위험, 즉 알려진 취약점을 그대로 방치하는 것과 이를 제거하려다 운영 환경에 장애를 일으키는 것 사이에서 균형을 맞춰야 합니다. “그냥 업그레이드하면 되지”라는 말은 오직 첫 번째 위험만 존재하는 것처럼 여기는 것입니다.

‘브레이킹 체인지’의 함정

호환성 변경 사항은 서둘러 의존성 업그레이드를 진행할 때 발생하는 가장 큰 위험 요소 중 하나이며, 특히 보안 수정 사항이 메이저 버전 경계를 넘을 때 더욱 그러합니다.

npm audit를 예로 들어 보겠습니다. 이 도구는 의존성 트리 전반에 걸쳐 취약점을 식별하고, 이를 해결하기 위한 업데이트를 권장합니다. 많은 경우, npm audit fix 호환되는 수정 사항을 자동으로 적용합니다. 추가 --force하지만, npm은 사용자가 선언한 의존성 범위를 벗어난 패키지까지 설치하게 되며, 여기에는 최상위 의존성의 SemVer 주요 버전 업데이트도 포함됩니다. npm은 이 플래그를 일반적으로 원치 않는 부수적 효과를 방지하는 안전 장치를 비활성화하는 방법으로 설명하고 있으며, 이 플래그가 정확히 어떤 동작을 하는지 확실히 알지 못하는 한 사용하지 말 것을 경고합니다.

이러한 구분은 중요합니다. 보안 권고 사항은 rollup과 같은 전이적 의존성에서 나타날 수 있는데, 이는 사용자가 직접 선택한 것이 아니라 빌드 툴을 통해 도입된 것입니다. 그러면 제안된 해결 방안은 “@angular-devkit/build-angular@X를 설치할 예정이며, 이는 호환성 변경 사항입니다”와 같은 내용이 될 수 있고, 처음에는 한 줄짜리 의존성 업데이트로 시작했던 일이 어느새 빌드 툴 마이그레이션으로 변해버리게 됩니다.

Angular는 그러한 지름길이 왜 해가 되는지를 잘 보여주는 사례입니다. Angular의 주요 업그레이드는 반드시 ng update, 여기에는 관련 업데이트 및 마이그레이션 개요가 포함되어 있으며, Angular는 다중 메이저 업그레이드를 진행할 때 한 번에 하나의 메이저 버전씩만 진행해야 합니다. 또한 전용 업데이트 가이드에는 업그레이드에 수반될 수 있는 코드 변환, 구성 변경, 종속성 업데이트 및 수동 작업에 대한 내용도 포함되어 있습니다. 패키지 버전을 강제로 다음 버전으로 올리는 것은 npm audit fix --force 이러한 마이그레이션 워크플로를 건너뛰게 되면, 해당 마이그레이션이 없는 상태에서 호환성 문제를 직접 해결해야 할 수도 있습니다.

연구 결과도 같은 방향을 가리키고 있습니다. Endor Labs는 ‘의존성 관리 보고서(Dependency Management Report)’를 통해, 분석 대상 버전 업그레이드 중 95%가 적어도 한 건의 잠재적 호환성 문제를 포함하고 있었으며, 의존성 취약점의 24%는 메이저 버전 업데이트가 필요하다는 사실을 밝혀냈습니다. 패치 수준의 업데이트조차도 클라이언트에 오류를 일으킬 가능성이 있는 변경 사항이 포함될 확률이 75%에 달했습니다. 이러한 수치는 물리 법칙에서 나온 것이 아니라 엔도르의 데이터셋에서 도출된 것이지만, 이러한 문제를 경험해 본 사람이라면 누구나 그 양상이 낯익게 느껴질 것입니다.

그렇다고 해서 업그레이드를 피해야 한다는 뜻은 아닙니다. 이는 보안 패치와 주요 애플리케이션 업그레이드가 자동으로 같은 작업이 아니라는 의미입니다. 스캐너가 적색 경보를 표시했다는 이유만으로 이 두 가지를 결합하면, 본래 집중적으로 진행해야 할 문제 해결 작업이 긴급한 현대화 프로젝트로 변질되어 버립니다.

API 및 구성 오류

모든 성숙한 애플리케이션은 계약의 그물망에 의존하는데, 그중에는 명백한 것도 있고 거의 문서화되지 않은 것도 있습니다. 컴포넌트 간에 상태를 전달하는 작업은 특정 라이브러리 API에 의존합니다. 자산의 번들링, 컴파일, 트랜스파일링 또는 미니파이 작업은 빌드 도구와 해당 구성 파일에 의존합니다. 인증, 데이터베이스 액세스, 직렬화, 가시성, 캐싱, 메시징 등: 의존성이 늘어날수록 공개 API도 늘어나고, 가정해야 할 사항도 늘어납니다.

주요 종속성 업그레이드는 업스트림 프로젝트의 관점에서는 지극히 타당한 방식이지만, 사용자의 입장에서는 큰 불편을 초래할 수 있는 방식으로 이러한 가정들을 뒤흔들 수 있습니다. 내보낸 메서드의 이름이 변경되거나, 함수가 다른 인수를 받기 시작하거나, 더 이상 사용되지 않는 API가 마침내 사라지거나, 기본값이 변경되거나, 구성 옵션이 더 이상 인식되지 않거나, 플러그인이 더 새로운 피어 종속성을 요구할 수 있습니다. 또한 애플리케이션이 여전히 실행되고 있는 Node 버전이 새 릴리스의 지원 범위 밖으로 벗어날 수도 있습니다.

이제 보안 업데이트에 컴파일 오류, 빌드 실패, 테스트 오류, 그리고 조사가 필요한 런타임 동작 문제가 발생했습니다. 때로는 자동화된 마이그레이션 도구가 이러한 문제의 대부분을 해결해 주기도 하지만, 때로는 애플리케이션을 상당히 대대적으로 재작성해야 할 때도 있습니다.

어쨌든, 그 작업은 다른 어떤 생산 변경 사항과 마찬가지로 엄격한 공학적 기준을 적용받아 처리되어야 합니다. 단순히 CVE로 시작되었다는 이유만으로 무턱대고 승인되어서는 안 됩니다.

회귀 위험

그리고 예쁜 빨간색 빌드 메시지로 미리 알려주지 않는 오류들도 있습니다. 종속성 업그레이드를 해도 컴파일은 문제없이 통과하고, 명백한 테스트도 통과할 수 있지만, 여전히 눈치채지 못한 곳에서 동작이 달라질 수 있습니다. 파서가 유효성 검사를 더 엄격하게 적용하거나, HTTP 클라이언트가 기본 타임아웃을 변경하거나, 라이브러리가 기술적으로는 API 호환성을 유지하지만 특정 워크로드에서는 다르게 동작할 수도 있습니다. 기존 동작에 대한 어설션을 작성한 사람이 아무도 없기 때문에 테스트 스위트는 여전히 녹색을 유지합니다.

바로 이 지점에서 종속성 보안은 단순한 패키지 관리 문제가 아닌 운영상의 문제로 대두됩니다. 취미 프로젝트의 경우, 이로 인한 파급 효과가 번거로울 뿐입니다. 하지만 은행 플랫폼, 결제 시스템, 의료 애플리케이션, 또는 제조 환경의 경우, 문제는 단순히 수정된 버전으로 업데이트할 수 있는지 여부가 아닙니다. 업데이트 시 그 외에도 어떤 부분이 변경되는지가 중요한 문제입니다.

스테이징 환경, 통합 테스트, 변경 창, 카나리아 배포, 롤백 절차 등이 바로 이러한 이유로 존재합니다. 보안 문제라고 해서 이러한 통제 수단의 중요성이 줄어들지는 않습니다. 오히려 긴급한 상황일수록 그 중요성은 더욱 커집니다.

운영상의 마찰

이 지점에서 흔히 ‘보안 대 엔지니어링’ 논쟁이 시작되곤 합니다. 보안 팀은 심각도가 높은 취약점을 발견하면 즉시 조치를 취하기를 원합니다. 엔지니어링 팀은 수십 개의 구성 요소에 영향을 미치는 의존성 업그레이드를 보고, 이를 테스트할 시간을 요구합니다. 운영 팀은 가동 시간 요건이 있는 프로덕션 시스템을 관리하며, 변경 사항으로 인해 시스템이 중단되지 않을 것이라는 증거를 원합니다. 모두가 각기 다른 유형의 위험에 대해 합리적으로 대응하고 있는 것입니다.

조직이 논의를 ‘즉시 업그레이드’와 ‘취약점을 수용’이라는 두 가지 선택지로만 좁힐 때 문제가 발생합니다. 실제 환경에서의 의존성 관리에는 그보다 더 많은 선택지가 필요합니다. 보안 팀에게는 노출 위험을 줄일 수 있는 신뢰할 수 있는 방법이 필요합니다. 개발자들은 호환성 문제를 파악할 시간이 필요합니다. 운영 팀에게는 예측 가능한 변경이 필요합니다. 제품 팀에게는 새로운 CVE가 등록될 때마다 사라지지 않는 로드맵과 고객에 대한 약속이 여전히 존재합니다.

예외 사항을 문서화하는 것이 대개 그 문제에 대해 또 다른 회의를 여는 것보다 낫습니다. 보완 통제 조치를 명시하고, 담당자를 지정하며, 만료일을 정해 두십시오. 이렇게 하면 교착 상태가 누군가가 책임을 지는 문서화된 결정으로 바뀌게 되는데, 이는 시간을 끌기 위한 수단이 아니라 진정한 보안 관리 관행입니다.

전이적 의존 관계에 숨겨진 노출

전이적 의존성은 이 모든 과정을 더욱 복잡하게 만듭니다. 개발자가 직접 이 패키지들을 선택한 것은 아닙니다. 의도적으로 설치한 패키지가 이 패키지들에 의존하고, 그 패키지들은 또 다른 것에 의존하기 때문에 결과적으로 이 패키지들이 설치된 것입니다. 그러다 보니 얼마 지나지 않아 소수의 직접적인 의존성만으로도 훨씬 더 방대한 의존성 그래프가 생성됩니다. Endor Labs의 ‘의존성 관리 현황(State of Dependency Management)’ 연구에 따르면, 해당 데이터셋에 포함된 취약점의 약 95%는 개발자가 의도적으로 선택한 패키지가 아닌 전이적 구성 요소에서 발견되었습니다.

락파일(lockfiles)은 부분적으로 해당 해결된 트리를 재현 가능하게 하기 위해 존재합니다. npm의 package-lock.json 정확한 의존성 트리를 기록해 두면, 이후 설치 시 다른 버전 조합이 자동으로 해결되는 대신 동일한 의존성 구조를 재현할 수 있으며, Gradle의 의존성 잠금 기능도 해결된 모듈 버전을 영구적으로 저장함으로써 거의 동일한 역할을 수행합니다. 이러한 안정성은 유용하지만, 동시에 취약점이 있는 간접 패키지가 개발자가 일상적으로 다루는 코드보다 여러 계층 아래에 위치할 수 있음을 의미하기도 합니다.

이 문제를 해결하는 것이 단순히 직접 버전을 변경하는 것만큼 간단하지는 않습니다. 취약점이 있는 패키지는 이를 불러온 직접적인 종속성에 의해 제약받을 수 있으며, 해당 상위 패키지를 업그레이드하면 여러 다른 전이적 패키지도 함께 업데이트될 수 있고, 각 패키지마다 고유한 호환성 문제가 따를 수 있습니다. 먼저 다음부터 시작하세요. npm ls <package> 또는 npm explain <package> 어떤 종속성 경로를 통해 취약점이 있는 버전이 소스 트리에 유입되었는지 추적하기 위함입니다. 해당 상위 구성 요소가 수정된 릴리스를 공개한 경우, 이를 업그레이드하는 것이 가장 깔끔한 해결책입니다. 공개하지 않은 경우, overrides 이 항목은 사용자가 기다리는 동안 패치된 버전을 트리 구조의 더 깊은 단계로 밀어 넣을 수는 있지만, 이를 무상 패치가 아닌 테스트가 필요한 호환성 변경 사항으로 간주해야 합니다. 오버라이드는 npm이 버전을 해결하는 방식을 변경할 뿐, 상위 버전이 해당 버전과 호환된다는 것을 보장하지는 않습니다.

따라서 실제 변경 과정은 취약한 패키지에서 수정된 패키지로 이어지는 단순한 직선 과정인 경우가 거의 없습니다. 오히려 상위 패키지 업그레이드, 새로운 의존성 그래프, 호환성 테스트, 애플리케이션 변경 등의 과정을 거치는 경우가 더 많습니다. 이는 완전히 다른 엔지니어링적 결정이며, 바로 이 때문에 의존성 업그레이드는 단순한 버전 번호 수정이 아니라 그래프의 변경 사항으로서 검토되어야 합니다.

더 정직한 프레임워크

비상 상황은 자사의 취약점 관리 전략이 전적으로 필요에 따라 진행되는 현대화 작업에 의존하고 있다는 사실을 깨닫기에는 최악의 시기입니다.

현대화는 반드시 라이프사이클 계획에 포함되어야 합니다. 팀은 어떤 프레임워크, 런타임, 라이브러리가 노후화되고 있는지 파악해야 하며, 수년에 걸쳐 기술적 부채가 쌓이도록 방치하기보다는 정기적으로 업데이트해야 합니다. 소규모의 일상적인 업그레이드는 압박 속에서 진행되는 대규모 마이그레이션보다 훨씬 수월하게 받아들일 수 있습니다. 하지만 “이 버전을 더 이상 사용하지 말아야 한다”는 말과 “취약점을 패치하기 위해 오늘 밤까지 이 버전을 반드시 교체해야 한다”는 말 사이에는 중요한 차이가 있습니다. 이 두 가지는 같은 말이 아닙니다.

때로는 즉시 업그레이드하는 것이 정말로 가장 안전한 해결책입니다. 호환성이 확실한 업데이트가 있고, 테스트 결과가 탄탄하며, 운영에 미치는 영향이 적다면 주저 없이 적용하십시오. 때로는 취약한 코드가 애플리케이션의 실제 배포 환경에서는 노출되지 않아 그에 따라 우선순위를 조정할 수 있습니다. 때로는 보완적인 통제 조치를 통해 업그레이드 테스트가 제대로 진행되는 동안 노출 위험을 줄일 수 있습니다. 또 때로는 주요 릴리스로 인한 광범위한 동작 변경을 강요하지 않으면서도 취약점을 제거할 수 있는 현장 보안 패치가 단기적으로는 더 나은 해결책이 되기도 합니다. 목표는 현대화를 회피하는 것이 아닙니다. 상황이 요구할 때 보안 조치 기한과 마이그레이션 기한을 분리하는 것입니다.

이는 의존성이 지원 종료 단계에 도달하면 특히 중요해지는데, 업스트림 지원이 중단됨에 따라 개발 팀은 알려진 취약점을 그대로 감수할 것인지, 아니면 해당 애플리케이션이 실행되도록 설계되지 않은 릴리스로 서둘러 전환할 것인지 선택해야 하는 상황에 처할 수 있기 때문입니다. TuxCare의 ‘Endless Lifecycle Support(ELS) ’는 바로 이러한 상황을 위해 설계되었습니다. 이 서비스는 지원 대상인 수명 종료 운영 체제, 런타임, 라이브러리 및 애플리케이션에 대해 지속적인 CVE 패치를 제공하며, 여기에는 장기적인 마이그레이션을 계획하는 동안 조직이 소프트웨어가 의존하는 버전을 계속 실행할 수 있도록 해주는 백포트 수정 사항도 포함됩니다. 그렇다고 해서 업그레이드가 불필요해지는 것은 아닙니다. 이 서비스는 팀에게 일반적으로 부족한 것, 즉 제대로 업그레이드할 수 있는 시간을 제공해 줍니다.

SecureChain은 라이프사이클의 초기 단계에서 이 문제를 해결합니다. 공개 레지스트리에 게시된 패키지에 맹목적으로 의존하는 대신, 대상 구성 요소는 검증된 소스 코드로부터 재컴파일되며, 악성 패턴 및 공급망 위험에 대한 검사를 거치고, 취약점이 발견될 때마다 지속적으로 모니터링 및 패치됩니다. 이 과정에서 SBOM, VEX, SLSA 레벨 3 출처 정보가 함께 제공됩니다. 업스트림 유지보수가 종료되면, ELS는 지원 대상 구성 요소에 대한 EOL(제품 수명 주기 종료) 이후 패치 경로를 제공합니다.

일반적인 생각은 꽤 간단합니다. 보안 문제 해결을 위해 타인의 소프트웨어 수명 주기가 끝날 때마다 개발자들이 불필요한 애플리케이션 재작업을 강요받아서는 안 됩니다.

Endor Labs의 대체 솔루션에 대한 당사의 가이드를 읽어보시면 다른 공급망 도구들과 어떻게 비교되는지 살펴보실 수 있습니다.

최종 생각

“그냥 업그레이드하면 돼”라는 말은 까다로운 기술적 문제를 단 세 단어로 압축해 놓았기 때문에 듣기 좋게 들린다. 의존성 그래프는 그런 건 신경 쓰지 않는다.

업그레이드는 여전히 중요하며, 시스템을 적정 수준으로 최신 상태로 유지하는 것은 향후 업그레이드를 원활하게 진행하는 가장 좋은 방법 중 하나입니다. 사용 중인 소프트웨어의 수명 주기를 파악하고, 종속성 구조를 명확히 파악하며, 업그레이드를 지속적으로 테스트하고, 수년에 걸쳐 기술적 부채가 눈에 띄지 않게 쌓이는 것을 방지해야 합니다. 하지만 보안 취약점이 발견되었을 때, 가장 먼저 던져야 할 질문은 무조건 최신 버전으로 얼마나 빨리 전환할 수 있는지가 아닙니다. 다른 운영상의 문제를 일으키지 않으면서 이 취약점을 제거할 수 있는 가장 안전한 방법이 무엇인지가 핵심입니다. 때로는 그 답이 동일하기도 하고, 때로는 그렇지 않기도 합니다.

먼저 자신의 저장소부터 살펴보세요. 잠금 파일을 열고, 트리 내에서 가장 오래된 종속성을 확인한 뒤, 누가 여전히 이를 유지 관리하고 있는지, 실제로 어떤 버전이 지원되는지, 어떤 구성 요소가 이에 의존하고 있는지, 그리고 업그레이드를 진행할 경우 실제로 어떤 부분에 영향을 미치는지 파악하세요. 긴급 상황이 발생하기 전에 이 작업을 완료해야 합니다. 왜냐하면, 충분히 이해한 상태에서 진행하는 종속성 마이그레이션은 엔지니어링이지만, 보안 사고 발생 중에 즉흥적으로 진행하는 것은 도박과 다름없기 때문입니다.

요약
의존성 업그레이드 위험과 ‘그냥 업그레이드하기’ 방식이 실패하는 이유
기사 이름
의존성 업그레이드 위험과 ‘그냥 업그레이드하기’ 방식이 실패하는 이유
설명
“그냥 업그레이드하면 되잖아”라는 말은 종속성 업그레이드가 실제로 어떻게 이루어지는지를 간과한 것입니다. 호환성 깨짐, 취약점이 있는 전이적 패키지, 지원 종료(EOL) 코드 등에는 구체적인 계획이 필요합니다.
작성자
게시자 이름
TuxCare
게시자 로고

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.

목차
필요한 오픈 소스 보안에 대한 답변 얻기

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.