소프트웨어 공급망의 복잡성
시놉시스(Synopsys)의 2020년 연구에 따르면, 기업이 사용하는 상용 애플리케이션 가운데 99% 이상에 오픈소스 코드가 들어 있으며, 이런 코드는 기업의 전체 코드에서 최소한 70%를 차지한다. 이는 모든 프로그래밍 언어에 이용 가능한 서드파티 구성요소와 패키지로 구성된 거대한 생태계 덕분이다. 자바(Java)에는 중앙 저장소가 있고 자바스크립트(JavaScript)에는 npm이 있으며, 파이썬(Python)에는 PyPI(Python Package Index, 파이썬 패키지 인덱스), 루비(Ruby)에는 루비젬(RubyGems)이 있는 식이다. 이들은 모두 커뮤니티에서 유지 관리하는 공개 저장소로서 패키지가 의존성으로 정의될 때 개발 도구는 이 곳으로부터 패키지를 끌어온다.
패키지 사이의 관계가 복잡하다는 것은 한 구성요소를 애플리케이션 내에 의존성으로 끌어올 경우 수십 개 내지 수백 개의 다른 애플리케이션을 가져오는 결과를 낳을 수 있다는 의미다. 이런 점은 특히 저장소의 감시가 잘 이루어지지 않을 경우, 공격자들이 악용할 소지가 있다고 보안 연구원들이 오래 전부터 경고해 왔다.
과거에 해커들은 정상적인 패키지 개발자들을 해킹해 패키지에 악성코드를 주입했다. 그 결과, 해당 패키지를 의존성으로 갖고 있는 다른 많은 정상적인 패키지도 피해를 입었다. 공격자들은 개발자들이 애플리케이션 의존성을 정의할 때 이름을 잘못 입력하기를 기대하면서 정상적인 이름과 비슷한 이름의 구성요소를 업로드하기도 했다. 이런 공격을 '타이포스쿼팅(Typosquatting)'이라고 한다.
의존성 혼동 공격의 작동 방식
이런 과거의 공격에 대응해 공개 저장소 관리자들은 다중 인증 포함, 특정 패키지 이름 변종 금지, 디지털 서명 추가, 생태계 감시 강화 등의 추가 조치를 취했다. 최근에는 보안 연구원이자 버그 사냥꾼인 알렉스 버산이 막기 힘든 새로운 기법을 고안해 냈다.버산은 개발자들이 애플리케이션을 구축할 때 공개 저장소로부터 코드 패키지와 라이브러리를 끌어올 뿐만 아니라 사내 개발되어 회사의 비공개 저장소나 로컬 피드에 호스팅되는 비공개 구성요소도 끌어올 것이라는 사실을 깨달았다.
자바스크립트의 npm, 파이썬의 pip, 루비의 젬 등과 같은 대부분의 패키지 관리자와 기타 개발 환경에서는 사용자로 하여금 특히 이런 이유로 패키지에 대한 추가 소스를 정의하게 해 준다. 따라서, 버산은 패키지가 똑같은 이름의 공개 피드와 로컬 피드에 모두 존재할 경우, 이런 도구들은 어떻게 대처할 것이며 둘 가운데 어떤 것이 선택될 것인지 의문을 품었다.
기본적으로 많은 도구가 버전 번호가 높은 패키지를 다운로드해 실행하는 것으로 나타났다. 즉, 커뮤니티에서 유지 관리하는 공개 인덱스에는 존재하지 않는 패키지를 애플리케이션에서 의존성으로 본다는 사실을 공격자가 알고 있다면 해로운 패키지를 똑같은 이름으로 버전 번호를 높여 게시하는 수가 있다.
그러면 표적의 패키지 관리자 클라이언트는 해당 패키지를 다운로드해 설치하게 된다. 이런 패키지가 설치되면 로컬 시스템에서 원격 코드 실행(remote code execution)이 허용될 수 있다는 것 역시 주지의 사실이다.
버산이 이런 점에 착안한 것은 2020년 여름 페이팔(PayPal)의 버그 포상금 프로그램의 일환으로 친구와 함께 페이팔의 버그를 찾던 중이었다. 그 과정에서, 페이팔에서 개발해 내부적으로 사용되었으나 공개 저장소에 호스팅된 코드를 검토하게 된 것이다.
버산은 “그 코드는 페이팔 내부에서 사용하기 위한 것이었고 코드의 package.json 파일에는 공개 의존성과 비공개 의존성이 섞여서 들어있는 것 같았다. 즉, npm에서 온 공개 패키지는 물론 페이팔이 내부적으로 호스팅했을 가능성이 높은 비공개 패키지 이름도 있었다”며, 이 새로운 공격 기법을 자세히 설명하고 ‘의존성 혼동’이라고 명명했다. 버산은 “이런 이름들은 당시의 공개 npm 레지스트리에 존재하지 않았다”라고 말했다.
버산은 패키지가 서버에서 실행되면 알려주는 코드로 패키지를 만든 후에 해당 패키지를 페이팔 코드에 기재된 비공개 의존성의 이름으로 npm 인덱스에 게시하는 계획을 생각해 냈다. 그 코드의 목적은 뭔가가 실행된 시스템에 대한 기본 정보(예: 호스트명, IP 주소, 사용자명)를 수집한 후 DNS 질의를 사용해 빼내는 것이었다. DNS는 걸러지지 않는 조직이 많기 때문이다.
그 공격은 성공적이었고 덕분에 버산은 페이팔로부터 3만 달러의 포상금을 받았다. 이를 계기로 버산은 페이팔처럼 공개 버그 포상금 프로그램이 있어서 공격 테스트를 허용하는 다른 조직을 대상으로도 공격을 시도해 봐야겠다는 생각을 하게 되었다. 비공개 패키지 이름을 얻어내는 것은 크게 어렵지 않으며 자바스크립트 파일용 웹사이트나 깃허브(GitHub) 상의 공개 저장소를 검색하면 가능한 것으로 나타났다.
버산은 페이팔, 애플(Apple), 쇼피파이(Shopify), 넷플릭스(Netflix), 우버(Uber), 옐프(Yelp) 등 35곳이 넘는 기업에 소속된 서버에 이 기법을 활용해 코드를 성공적으로 실행할 수 있었고, 이들 기업 가운데 여러 곳으로부터 버그 포상금을 수령했다.
버산은 “성공률이 그야말로 놀라웠다”면서, “개발자들이 자신의 시스템에 저지른 일회성 실수에서부터 잘못 구성된 내부 또는 클라우드 기반 빌드 서버와 시스템적으로 취약한 개발 파이프라인에 이르기까지 한 가지 분명한 사실은 유효한 내부 패키지 이름을 비슷하게 만들어 두는 것이 최대 IT 기업의 네트워크에 침투해 원격 코드 실행권을 얻고 공격자가 빌드 중에 백도어를 추가하게 해 줄 수도 있는 거의 확실한 방법이라는 것이다”라고 덧붙였다.
의존성 혼동 공격의 완화 방법
이 문제를 가장 간단하게 완화하는 방법은 공개 및 비공개 피드가 모두 사용되는 혼합 구성을 사용하지 않는 것이다. 그 대신, 개발 조직의 통제 및 감시 하에 있고 모든 패키지의 면밀한 조사와 검증이 이뤄지는 비공개 피드를 항상 사용하도록 패키지 관리자를 구성해야 한다.파이썬의 pip와 같은 일부 패키지 관리자에는 이를 위한 구성 옵션(예: --index-url) 뿐만 아니라 --extra-index-url도 있는데 이는 기본적인 공개 인덱스 옆에 인덱스를 추가할 뿐이며 사용해서는 안 된다.
문제는 패키지 관리에 사용되는 일부 서드파티 도구(예, 제이프로그 아티팩토리(JFrog Artifactory))는 비공개 저장소와 공개 저장소를 가상 피드에 섞을 수 있게 하고 똑같은 행동에 의해 영향도 받는다는 점이다.
애저 아티팩트(Azure Artifacts)라는 패키지 호스팅 서비스도 제공하는 마이크로소프트(Microsoft)는 버산의 보고서에 부응해 완화 지침을 실은 백서를 공개했다.
이 백서에 따르면, “이런 구성이 있다고 해도 만일 해당 피드에서 공개 패키지가 비공개 패키지를 우선하도록 허용한다면 치환 공격이 여전히 가능할 수 있다. 따라서, 이를 허용하지 않도록 피드를 구성하거나 공개 인덱스 상에서 비공개 패키지 이름을 차지하거나 다른 완화 방법을 사용해야 한다.”
또 다른 완화 방법은 일부 패키지 관리자에서 허용하는 이름공간 또는 범위 기능을 사용하는 것이다. 이는 사용자나 조직이 소유하는 접두사로서 공개 저장소에 게시한 모든 패키지에 적용된다.
npm 설명 문서에 따르면, “범위는 연관된 패키지를 한 데 모으는 수단이며 npm이 패키지를 처리하는 방식에 대한 몇 가지 요소에 영향도 미친다. 각각의 npm 사용자/조직은 저마다의 범위가 있고 당사자만 당사자의 범위에 패키지를 추가할 수 있다. 즉, 누군가가 본인보다 먼저 패키지 이름을 가져가는 것에 대해 걱정할 필요가 없다. 따라서, 조직의 공식 패키지를 표시하는 좋은 방법이기도 하다.”
예를 들어, 페이팔에서 npm 상에 @paypal 범위를 소유하고 페이팔의 모든 비공개 패키지 의존성 정의에 패키지 이름과 더불어 해당 범위를 포함시킨다면, 아무도 공개 npm 인덱스에서 이를 강탈할 수 없게 된다는 의미다. 기업들은 이미 내부적으로 범위를 사용 중일 수도 있으나 이 완화 방법이 효율적이기 위해서는 오픈소스 저장소에서 해당 범위를 차지해야 한다.
예를 들어, 버산은 아틀라시안(Atlassian)에서 비공개적으로 사용하는 범위 하에 npm 상에서 여러 개의 패키지를 등록했는데 해당 범위를 차지하지 않았기 때문에 가능했다.
오픈소스 생태계에서의 공급망 공격
이 문제가 널리 퍼진 것으로 보이고 이를 완화하려면 개발 도구, 패키지 관리자 및 워크플로우의 구성에 상당한 변경이 필요하기 때문에, 영향을 받은 모든 조직들이 공격에 대해서 배우고 방어책을 구비하기까지는 오랜 시간이 걸릴 가능성이 높다. 데브옵스 자동화 및 오픈소스 통제관리 업체 소나타입(Sonatype)은 버산의 보고서가 공개된 지 48시간도 지나지 않아 버산의 공격을 흉내낸 다양한 작성자가 게시한 모방 npm 패키지를 275개 이상 찾아냈다.
오픈소스 소프트웨어 구성요소의 생태계가 크고 구성요소 간의 상호의존성이 복잡하기 때문에 소프트웨어 개발자들은 매우 매력적인 표적이다. 깃허브의 2020년 보고서에 따르면, 자바스크립트 애플리케이션에는 평균 10개의 직접 의존성이 있지만 이런 의존성에는 계속해서 다른 의존성이 있을 수 있기 때문에 자바스크립트 애플리케이션 하나에 대한 타동적 의존성 개수의 중간값은 실제로 683개이다.
한편, 소나타입이 관찰한 바에 따르면, 지난해 다양한 기법을 사용한 업스트림 소프트웨어 공급망 공격의 수는 430% 증가했다. editor@itworld.co.kr