브라우저 등 멀티 프로세스 아키텍처에서 의미적 권한 축소와 공격 표면 축소의 차이를 사례로 설명하고, 메모리 안전성이 확산될 때 샌드박싱 전략과 보안 투자 우선순위를 어떻게 재고해야 하는지 논의한다.
금, 2024년 8월 30일
샌드박싱과 메모리 안전성은 보안을 개선하는 데 있어 서로 직교적이며, 따라서 상호 보완적인 접근으로 여겨집니다. 메모리 안전성은 취약점이 도입될 가능성을 줄이고, 샌드박싱은 취약점이 악용되었을 때의 영향을 줄입니다. 하지만 저는 이것이 웹 브라우저 같은 정교한 멀티 프로세스 아키텍처에서의 샌드박싱을 지나치게 단순화하고, 보편적인 메모리 안전성이 샌드박싱에 미칠 잠재적 함의를 놓치게 만든다고 생각합니다.
샌드박싱에는 일반적으로 비슷하지만 서로 구별되는 두 가지 목적이 있습니다. 첫째는 프로세스가 갖는 의미적 권한(semantic privileges)을 줄이는 것이고, 둘째는 프로세스가 접근할 수 있는 공격 표면을 줄이는 것입니다. 다소 추상적이니 몇 가지 예를 들어 보겠습니다:
futex 시스템 콜을 호출할 수 있는 능력을 제거하면, 그 프로세스가 접근할 수 있는 커널의 공격 표면이 줄어듭니다.이 둘은 샌드박스 내부에서 코드 실행이 가능한 상황 이후의 공격자 벡터를 줄인다는 점에서 비슷해 보이지만, 실제로는 다릅니다! 전자는 우리 프로세스의 위협 모델을 강제하는 것입니다. 즉, 웹사이트가 파일 시스템에 접근해서는 안 되는 경우, 코드 실행 취약점이 있더라도 이를 불가능하게 만듭니다. 반면 후자는 커널의 futex 구현에 취약점이 있을 수 있다는 가능성으로부터 보호하기 위한 것입니다.
실제로 기존 프로젝트에 이러한 샌드박싱 정책 중 어느 하나를 도입하려면, 먼저 프로세스 내에서 해당 능력에 의존하는 부분이 있는지 파악하고, 있다면 그것을 중단시키는 방법을 찾아야 합니다(종종 해당 코드를 다른 프로세스로 옮기고, 그 프로세스에 IPC로 작업을 요청하는 방식). 그런데 만약 futex에 취약점이 전혀 없다고 매우 강하게 확신할 수 있다면 어떨까요? 그렇다면 futex 사용 여부를 감사하고, 이를 위해 IPC를 연결하는 번거로움에 비해 얻는 이득은 아주 미미할 것입니다.
좀 더 큰 예를 들어봅시다. 바이트로 제공된 MP3를 시스템 스피커로 재생하게 해주는 API(시스템 콜이거나 권한 있는 프로세스로의 IPC)가 있다고 상상해 보세요. 웹사이트는 소리를 재생할 수 있지만, 항상 그런 것은 아닙니다. 개별 탭을 음소거할 수 있고, 출처(origin)별로도 음소거가 가능합니다. 브라우저의 렌더러 프로세스에서 MP3 재생 API에 접근하지 못하게 하고, 대신 오디오 재생을 위해 브라우저 상위 프로세스에 IPC 요청을 보내도록 요구한다면(상위 프로세스가 탭 및 출처별 음소거를 강제) 이는 의미적 권한을 줄이는 조치가 됩니다. 이렇게 하면 렌더러가 침해되었더라도 음소거 규칙이 강제됩니다. 그러나 실제로 누군가가 사용자에게 릭롤을 시키기만 하려고 렌더러 프로세스를 익스플로잇할 위험은 거의 없고, 훨씬 더 큰 위험은 MP3의 바이트로 트리거될 수 있는 MP3 재생 API의 취약점이 존재하여 공격자에게 더 높은 권한 컨텍스트(커널 또는 시스템 데몬)에서 임의 코드 실행을 제공하는 경우입니다.
이 질문으로 다시 돌아옵니다. 오디오 재생 API에 취약점이 있을 가능성이 극히 낮다면, 우리는 무엇을 다르게 할까요? 제 생각에 답은, 브라우저 상위 프로세스를 통해 오디오를 우회 재생하게 하는 우리만의 API를 구축하는 대신, 렌더러 프로세스에 해당 API 접근 권한을 그냥 부여하는 것입니다. 왜냐하면 MP3 재생 API에 대한 접근을 제한했던 진짜 동기는 프로세스가 오디오를 재생하지 못하게 하려는 것이 아니라, 공격 표면을 줄이려는 것이었기 때문입니다. MP3 재생 API를 허용하면 의미적 보안은 일부 희생하겠지만, 그 대가로 우리 IPC의 시간, 노력, 복잡성을 크게 절약할 수 있으니 상당히 괜찮은 교환일 것입니다.
그렇다면 이러한 API에 취약점이 있을 가능성이 극히 낮다고 자신할 수 있으려면 무엇이 필요할까요? 먼저, 그것들이 메모리 안전한 언어로 구현되어야 합니다. 커널과 OS 데몬의 취약점 대부분이 메모리 비안전성 때문에 발생한다는 것은 데이터가 매우 분명히 보여줍니다. 하지만 평균적으로 메모리 비안전성은 취약점의 ‘겨우’ 70%에 불과하므로, 메모리 안전한 언어를 사용하더라도 여전히 30%의 취약점이 남습니다. 샌드박싱 전략을 바꾸기에는 위험이 너무 큽니다. 보안 팀의 시간이 그 30%만 찾는 데 집중될 수 있다면, 출시 전에 취약점을 더 효율적으로 찾을 수 있을 것이라고 주장할 수도 있지만, 이는 추정일 뿐입니다(그럴듯하긴 합니다). 만약 취약점의 거의 100%에 가까운 비율이 메모리 비안전성일 가능성이 큰 공격 표면을 식별할 수 있는 방법이 있다면 어떨까요? 예를 들어, 어떤 API가 파일 경로를 절대 입력받거나 반환하지 않는다면, 논리적 취약점이 발생할 가능성이 더 낮을까요? 얼마나 더 낮을까요? 저는 이 질문에 대한 답을 가지고 있지 않습니다. 그래서 이것을 행동 촉구로 제시합니다. 더 많은 메모리 안전성을 향해 나아가는 지금, 어떻게 하면 공격 표면의 설계를 분석하여, 취약점이 없을 것이라는 높은 확신을 가질 수 있는 종류의 표면인지 판단할 수 있을까요?
분명히 밝혀둘 가치가 있습니다. 한계적으로 보았을 때, 공격 표면을 줄이는 일은 보안에 언제나 도움이 됩니다. 그러나 보안 팀에는 시간이 한정되어 있고, 착수할 수 있는 프로젝트도 많습니다. 우리가 항상 답해야 하는 질문은 이것입니다. 이 프로젝트가 내가 할 수 있는 다른 일들보다 더 큰 보안 향상을 제공하는가? 취약점이 잘 생기는 API(예: win32k)를 차단하여 공격 표면을 줄이는 일은 투자 대비 수익이 매우 높은 프로젝트입니다. 반면, 이미 취약점이 없을 것이라고 매우 높은 확신을 갖고 있는 API를 차단해 공격 표면을 줄이는 일은 그렇지 않습니다. 게다가 많은 공격 표면 축소 노력은 시스템 API 호출을 프로세스 간 IPC 작업으로 대체하기 때문에 복잡성을 유발한다는 사실이 이를 더욱 악화시킵니다.
커널, 시스템 데몬, 기타 권한 있는 공격 표면이 메모리 비안전한 언어로 작성되어 있는 한, 샌드박스에서 공격 표면을 적극적으로 줄이려는 강력한 이유가 존재합니다. 그러나 공격 표면이 메모리 안전성을 기반으로 구축된 세계에서는, 보안 및 엔지니어링 팀의 시간 투자 대비 효과적인 공격 표면 축소가 무엇인지에 대한 우리의 가정을 재고할 수 있을지도 모릅니다. 그렇다고 해서 공격 표면 축소를 포기하자는 뜻은 아니다, 오히려 어떤 종류의 표면과 API가 위험을 가장 많이 야기하는지 더 분석할 기회로 삼아야 합니다. 지금은 모든 API가 메모리 비안전성 때문에 위험을 내포하지만, 앞으로는 더 구체적인 위험 요인을 식별해 우리의 시간을 더 현명하게 투자할 수 있기를 바랍니다.