공급망 공격
고친 사람 github-actions[bot]
공급망 공격은 목표를 직접 뚫지 않고 목표가 믿고 받아 쓰는 소프트웨어를 먼저 오염시키는 공격입니다. 남이 만든 코드가 통로가 됩니다. 받는 쪽은 늘 하던 대로 설치했을 뿐입니다. 그런데 악성 코드가 함께 들어옵니다. 오염된 한 곳을 받아 쓰는 곳은 전부 한꺼번에 당합니다.
쉽고 빠른 이해
남이 만든 코드를 거쳐 들어오는 공격입니다. 널리 쓰이는 라이브러리의 새 버전에 악성 코드가 섞여 올라오면, 그 버전으로 올린 서비스가 전부 그 코드를 돌리게 됩니다.
이 공격에 따로 이름이 붙은 까닭은 서버를 잠가서는 못 막기 때문입니다. 방화벽과 접근 제어는 밖에서 두드리는 사람을 막습니다. 우리가 직접 내려받아 설치한 코드는 그 문을 당당히 지나 안으로 들어옵니다.
어떻게 도나:
- 공격자가 목표가 가져다 쓰는 라이브러리나 도구 하나를 고릅니다
- 그것을 만드는 곳이나 나르는 길에 악성 코드를 심습니다
- 목표가 평소처럼 설치하고 업데이트하면서 그 코드를 받아 실행합니다
막는 데도 대가가 있습니다. 받는 것마다 누가 만들었고 중간에 바뀌지 않았는지 확인해야 합니다. 버전을 고정해 두면 안전하게 고친 새 버전도 늦게 받습니다.
상세
이 절은 소프트웨어가 우리 서버에 오기까지 거치는 길을 먼저 그립니다. 그 길의 어느 고리가 공격받는지, 왜 한 번 오염되면 멀리 퍼지는지, 무엇으로 막는지를 차례로 봅니다.
상한 납품 재료
식당은 주방을 아무리 깨끗이 관리해도 납품받은 재료가 상해 있으면 손님이 탈이 납니다. 뒷문을 잠가 두어도 재료 상자는 매일 아침 정문으로 들어옵니다. 그리고 한 납품업체가 여러 식당에 재료를 대면 탈도 여러 식당에서 한꺼번에 납니다.
공급망 공격이 노리는 것도 우리 시스템의 벽이 아니라 우리가 믿고 들여오는 물건입니다. 방화벽과 접근 제어는 밖에서 두드리는 요청을 막습니다. 우리가 직접 내려받아 설치한 코드는 막지 않습니다. 벽을 넘을 필요가 없으니 벽을 아무리 높여도 소용이 없습니다.
소프트웨어 공급망
요즘 서비스는 코드 대부분을 직접 쓰지 않습니다. 로그를 남기고, 요청 본문을 해석하고, 데이터베이스에 붙는 코드는 대개 남이 만든 라이브러리입니다. 내 코드가 이렇게 가져다 쓰는 남의 코드를 의존성이라고 합니다.
의존성은 패키지 저장소에서 받아 옵니다. 누구나 라이브러리를 올리고 누구나 내려받는 공개 창고입니다. 이 저장소에 올라간 라이브러리 한 벌을 패키지라고 부릅니다. 이 문서에서 라이브러리와 패키지는 같은 것을 가리킵니다.
패키지를 받아 오는 일은 패키지 관리자가 합니다. 설정 파일에 이름과 버전을 적으면 이 도구가 저장소에서 찾아 설치해 줍니다.
의존성에도 의존성이 있습니다. 내가 고른 라이브러리 하나가 다른 라이브러리 열 개를 끌고 옵니다. 그 열 개가 또 다른 것을 끌고 옵니다. 내가 직접 고르지 않았는데 딸려 온 이것을 전이 의존성이라고 합니다. 한 서비스가 실행하는 코드 중 대부분이 이렇게 딸려 온 코드입니다.
코드가 모이면 빌드를 거칩니다. 소스를 컴파일하고 의존성과 묶어 배포할 수 있는 파일 하나로 만드는 과정입니다. 이렇게 나온 파일을 빌드 산출물이라고 합니다.
빌드는 빌드 도구라는 프로그램이 합니다. 이 도구도 대개 남이 만든 것입니다.
이 빌드를 자동으로 돌리는 서버를 빌드 서버라고 합니다. 코드가 올라올 때마다 빌드와 테스트를 돌려 줍니다. 흔히 지속적 통합(Continuous Integration, CI) 서버가 이 일을 맡습니다.
이 사람과 도구와 저장소의 사슬 전체를 소프트웨어 공급망이라고 부릅니다. 아래 그림이 그 사슬입니다.
flowchart TD
A["우리 개발자가 쓴 소스"] --> C["빌드 서버"]
B["패키지 저장소의 의존성"] --> C
D["빌드 도구"] --> C
C --> E["빌드 산출물"]
E --> F["배포와 업데이트"]
F --> G["우리 서버와 사용자 기기"]
그림의 칸 하나하나가 공격이 들어올 수 있는 고리입니다. 우리 개발자가 쓴 소스는 한 칸뿐입니다. 나머지는 전부 남이 만들었거나 관리하는 것입니다.
공격이 들어오는 고리
공격자는 사슬에서 가장 약한 고리를 고릅니다. 아래 표가 자주 노리는 고리와 그 수법입니다.
| 고리 | 수법 |
|---|---|
| 기존 의존성 | 라이브러리를 만들어 올리는 사람(작성자)의 [[사용자 계정 |
| 이름이 비슷한 가짜 | 인기 라이브러리와 한 글자만 다른 이름으로 가짜를 올려 오타를 기다립니다 |
| 사내 전용 이름 | 회사 안에서만 쓰는 패키지 이름으로 공개 저장소에 가짜를 올립니다 |
| 빌드 서버 | 서버에 들어가 빌드 도중에 악성 코드를 끼워 넣습니다 |
| 업데이트 서버 | 제품이 새 버전을 받아 가는 서버를 차지하고 파일을 바꿔 둡니다 |
| 외부 스크립트 | 웹 페이지가 남의 서버에서 불러오는 [[JavaScript |
둘째 줄의 수법을 타이포스쿼팅이라고 합니다. 설치 명령에서 이름을 한 글자 잘못 치면 가짜가 깔립니다. 공격자는 누군가 틀리기를 기다리기만 하면 됩니다.
셋째 줄은 의존성 혼동입니다. 패키지 관리자가 사내 저장소와 공개 저장소를 함께 뒤지도록 설정돼 있으면, 같은 이름일 때 버전 번호가 더 높은 쪽을 고르기도 합니다. 공격자는 사내 이름에 높은 번호를 붙여 공개 저장소에 올려 둡니다.
여섯째 줄은 프런트엔드 쪽 고리입니다. 페이지가 CDN(Content Delivery Network, 콘텐츠 전송 네트워크)에서 불러오는 스크립트가 바뀌면, 그 페이지를 여는 모든 방문자의 브라우저에서 공격자의 코드가 실행됩니다.
한 번 뚫리면 멀리 퍼지는 까닭
공급망 공격은 퍼지는 모양이 다른 공격과 다릅니다. 라이브러리 하나를 여러 라이브러리가 씁니다. 그 라이브러리를 또 여러 서비스가 씁니다. 한 층 내려갈 때마다 받는 곳이 불어납니다.
flowchart TD
P["오염된 라이브러리 하나"]
subgraph 라이브러리층["이 라이브러리를 쓰는 라이브러리"]
L1["라이브러리 A"]
L2["라이브러리 B"]
end
subgraph 서비스층["그 라이브러리를 쓰는 서비스"]
S1["서비스 1"]
S2["서비스 2"]
S3["서비스 3"]
S4["서비스 4"]
end
P --> L1
P --> L2
L1 --> S1
L1 --> S2
L2 --> S3
L2 --> S4
아래층 서비스들은 오염된 라이브러리를 직접 고른 적이 없습니다. 전이 의존성으로 딸려 왔을 뿐입니다. 그래서 자기가 그 라이브러리를 쓰는 줄도 모르는 경우가 많습니다.
공격자에게는 이것이 지렛대입니다. 목표 회사 백 곳을 하나씩 뚫는 대신 그들이 함께 쓰는 라이브러리 하나를 오염시키면 됩니다.
정상 절차가 악성 코드를 통과시킨다
공급망 공격은 들키기도 어렵습니다. 악성 코드가 정상 경로로 들어오기 때문입니다.
빌드 서버에서 끼워 넣은 코드는 그 뒤로 정상 빌드를 거칩니다. 그리고 회사의 코드 서명 키로 서명됩니다. 코드 서명은 이 파일을 누가 만들었고 그 뒤로 바뀌지 않았는지를 받는 쪽이 확인하게 해 주는 전자 서명입니다. 서명은 흠이 없으니 받는 쪽은 믿고 설치합니다. 서명이 지켜 주는 것은 서명한 뒤의 구간뿐입니다.
설치하는 순간에 코드가 도는 경우도 있습니다. 여러 패키지 관리자는 패키지 안에 든 스크립트를 설치 과정에서 실행하게 해 줍니다. 그러면 라이브러리를 한 줄도 부르지 않았는데 설치만으로 공격자의 코드가 개발자 노트북이나 빌드 서버에서 실행됩니다.
악성 코드는 대개 조용히 움직입니다. 원래 기능은 멀쩡히 둔 채 환경 변수의 비밀 키를 밖으로 보내거나, 나중에 들어올 뒷문을 열어 둡니다. 이 뒷문을 백도어라고 합니다. 기능이 멀쩡하니 테스트도 통과합니다.
막는 수단
막는 수단은 크게 셋으로 갈립니다. 무엇을 받는지 못 박는 것, 받은 것이 맞는지 확인하는 것, 뚫렸을 때 피해를 줄이는 것입니다.
첫째는 버전을 고정하는 것입니다. 설치할 때마다 최신 버전을 받으면 오늘 올라온 악성 버전도 오늘 받습니다. 그래서 설치한 의존성의 버전을 목록으로 적어 두고 다음부터 그 목록대로만 설치합니다. 이 목록이 락 파일입니다.
락 파일에는 버전과 함께 파일마다 해시를 적습니다. 해시는 파일 내용을 짧은 값으로 줄인 것입니다. 내용이 한 글자만 바뀌어도 값이 완전히 달라집니다. 적어 둔 해시와 내려받은 파일의 해시가 다르면 설치를 멈춥니다.
둘째는 받은 것을 확인하는 것입니다. 아래 표가 자주 쓰는 수단과 각각이 막는 고리입니다.
| 수단 | 하는 일 | 막는 고리 |
|---|---|---|
| 서명 확인 | 만든 쪽의 서명이 맞는지 보고 설치합니다 | 업데이트 서버 · 저장소 중간의 바꿔치기 |
| 하위 리소스 무결성(Subresource Integrity, SRI) | 외부 스크립트를 불러올 때 해시를 같이 적어 두고 다르면 실행하지 않습니다 | 외부 스크립트 |
| 콘텐츠 보안 정책 | 페이지가 스크립트를 불러와도 되는 출처를 미리 정해 둡니다 | 외부 스크립트 |
| 사내 저장소 우선 | 사내 이름은 사내 저장소에서만 찾게 설정합니다 | 사내 전용 이름 |
| 재현 가능한 빌드 | 같은 소스로 누가 빌드해도 같은 파일이 나오게 만들어 서로 대조합니다 | 빌드 서버 |
재현 가능한 빌드는 빌드 서버 한 대만 믿지 않는 방법입니다. 빌드 서버에서 코드가 끼워졌다면 그 서버가 내놓은 파일에는 소스에 없는 코드가 들어 있습니다. 다른 곳에서 같은 소스로 다시 빌드하면 그 코드가 없는 파일이 나옵니다. 두 파일이 서로 다르니 변조가 드러납니다.
셋째는 뚫렸을 때를 대비하는 것입니다. 빌드 서버와 배포 도구에는 일에 꼭 필요한 권한만 줍니다. 이것이 최소 권한 원칙입니다. 빌드 서버가 운영 데이터베이스 비밀번호까지 쥐고 있으면, 빌드 서버 하나에 들어온 공격자가 운영 전체를 손에 넣습니다.
공격이 알려진 뒤에는 우리가 그 라이브러리를 쓰는지부터 알아야 합니다. 그래서 제품에 들어간 구성 요소를 전부 적은 목록을 만들어 둡니다. 이 목록이 소프트웨어 자재 명세서(Software Bill of Materials, SBOM)입니다. 어느 라이브러리의 어느 버전이 오염됐다는 소식이 오면 이 목록을 찾아보고 영향을 받는지 바로 가립니다.
막는 데 드는 값
버전을 고정하면 보안 수정도 저절로 들어오지 않습니다. 라이브러리가 구멍을 막은 새 버전을 내도, 누군가 락 파일의 버전을 새로 올려 적기 전까지는 옛 버전이 계속 쓰입니다. 고정한 버전을 주기적으로 검토해서 올리는 일이 따로 생깁니다.
확인 절차도 일을 늘립니다. 서명 키를 관리해야 합니다. 해시가 안 맞아 빌드가 멈추면 누군가 원인을 봐야 합니다. 의존성을 줄이면 공격이 들어올 고리도 줄어듭니다. 대신 그만큼 직접 짜고 관리할 코드가 늘어납니다.
이 이름으로 부르지 않는 것
라이브러리 작성자가 실수로 남긴 구멍을 공격자가 찾아 쓰는 것은 취약점 공격입니다. 남의 코드를 거쳐 들어온다는 점은 같아서 넓게는 공급망 위험으로 함께 다루기도 합니다. 공급망 공격이라는 이름은 대개 누군가 일부러 사슬에 악성 코드를 심은 경우를 가리킵니다.
중간자 공격은 두 당사자 사이의 연결 하나에 끼어듭니다. 공급망 공격은 배포되는 물건 자체를 바꿔 둡니다. 그래서 중간자 공격은 그 연결을 쓴 사람만 당합니다. 공급망 공격은 그 물건을 받은 모든 곳이 당합니다.
관련 항목
공급망 공격이 파고드는 소프트웨어 공급망의 고리
의존성 · 전이 의존성 · 패키지 · 패키지 관리자 · 패키지 저장소 · 빌드 · 빌드 도구 · 빌드 서버 · 지속적 통합 · 아티팩트 저장소 · 컨테이너 이미지 · 자동 업데이트 · CDN · 서드파티 스크립트
공급망 공격이 쓰는 수법
타이포스쿼팅 · 의존성 혼동 · 계정 탈취 · 악성 패키지 · 설치 스크립트 · 백도어 · 악성 코드 · 빌드 변조
공급망 공격을 막는 수단
코드 서명 · 패키지 서명 · 전자 서명 · 락 파일 · 해시 함수 · 체크섬 · 하위 리소스 무결성 · 콘텐츠 보안 정책 · 재현 가능한 빌드 · 소프트웨어 자재 명세서 · 최소 권한 · 의존성 스캔 · 벤더링 · SLSA
공급망 공격이 속하는 상위 분류
보안 엔지니어링 · 소프트웨어 공급망 보안 · 공격 표면 · 위협 모델링 · 애플리케이션 보안
공급망 공격과 헷갈리는 이웃 공격
중간자 공격 · 취약점 · CVE · 제로데이 취약점 · 내부자 위협 · 워터링 홀 공격
공급망 공격으로 알려진 사고
SolarWinds 해킹 · xz 백도어 · event-stream 사건 · NotPetya
다른 이름: supply chain attack · 소프트웨어 공급망 공격 · software supply chain attack · 서플라이 체인 공격