앱 샌드박스
고친 사람 github-actions[bot]
앱 샌드박스는 앱 하나가 다른 앱의 데이터에 손대지 못하게 막습니다. 운영체제가 앱을 깔 때마다 그 앱만 쓰는 담을 따로 쳐 줍니다. 앱은 담 안의 자기 파일만 읽고 씁니다. 담 밖의 것은 운영체제가 정한 길을 거쳐서만 얻습니다.
쉽고 빠른 이해
휴대폰 운영체제가 앱마다 담을 쳐서 앱이 자기 파일만 만지게 하는 방식입니다. 메신저 앱은 같은 휴대폰에 깔린 은행 앱의 저장 데이터를 열 수 없습니다.
출처가 제각각인 앱을 모두 믿기는 어렵습니다. 담이 없으면 앱 하나가 뚫렸을 때 휴대폰 안의 다른 앱 데이터까지 함께 털립니다.
어떻게 도나:
- 앱을 깔 때 운영체제가 그 앱만 쓰는 사용자 번호와 전용 폴더를 만듭니다
- 앱은 그 번호로 실행됩니다. 다른 번호가 주인인 파일은 못 엽니다
- 카메라나 다른 앱의 데이터가 필요하면 허락을 받거나 운영체제가 정한 통로로 요청합니다
휴대폰 운영체제는 모든 앱에 늘 이 담을 칩니다. 개발자가 켤지 말지 고르는 일이 아닙니다. 데스크톱 운영체제에서는 대개 앱이 켜기로 골랐을 때만 담이 쳐집니다.
대가도 있습니다. 앱끼리 파일 하나를 건네는 일도 통로를 거쳐야 해서 번거롭습니다. 앱을 만드는 쪽은 사용자가 허락을 거절해도 앱이 멈추지 않게 짜야 합니다.
상세
오피스텔은 집마다 열쇠가 따로 있습니다. 내 열쇠로는 옆집 문이 열리지 않습니다. 이웃에게 물건을 빌리려면 문을 두드려 건네받아야 합니다.
앱 샌드박스는 운영체제가 설치된 앱마다 이렇게 열쇠를 따로 주는 방식입니다. 앱은 자기 몫으로 받은 폴더 안의 파일만 읽고 씁니다. 예를 들어 메신저 앱은 은행 앱이 저장한 로그인 정보를 열 수 없습니다. 두 앱이 같은 휴대폰에 깔려 있어도 운영체제가 그 요청을 거절합니다.
이름에 든 샌드박스는 믿기 어려운 코드를 좁은 울타리 안에 가두는 방법을 넓게 가리킵니다. 이렇게 코드끼리 서로 닿지 못하게 떼어 놓는 일을 격리라고 합니다.
앱 샌드박스는 앱 단위의 격리를 모든 앱에 기본으로 겁니다. 휴대폰 운영체제에서는 앱을 깔면 언제나 켜져 있습니다. 개발자가 켤지 말지 고르는 기능이 아닙니다.
데스크톱 프로그램과 다른 점
앱 샌드박스가 왜 생겼는지는 데스크톱 운영체제와 견주면 드러납니다. 둘은 권한을 나누는 단위가 다릅니다.
전통적인 데스크톱 운영체제는 사용자 계정을 단위로 권한을 나눕니다. 내가 로그인해서 실행한 프로그램은 전부 내 계정의 권한으로 돕니다. 그래서 내가 읽을 수 있는 파일이면 어느 프로그램이든 읽을 수 있습니다.
이 방식은 프로그램을 믿을 때만 안전합니다. 방금 받은 게임이 브라우저에 저장된 비밀번호 파일을 읽어도 운영체제는 막지 않습니다. 둘 다 같은 사용자의 프로그램이기 때문입니다.
스마트폰에는 만든 곳이 제각각인 앱이 수십 개씩 깔립니다. 그 앱들을 모두 믿기는 어렵습니다. 그래서 모바일 운영체제는 권한을 나누는 단위를 사용자에서 앱으로 좁혔습니다.
필요한 것만 주는 원칙을 최소 권한 원칙이라고 합니다. 앱 샌드박스는 이 원칙을 앱마다 기본값으로 겁니다. 두 방식의 차이를 표로 놓으면 이렇습니다.
| 전통적인 데스크톱 | 앱 샌드박스 | |
|---|---|---|
| 권한을 나누는 단위 | 사용자 계정 | 앱 |
| 다른 프로그램의 파일 | 같은 사용자면 읽는다 | 못 읽는다 |
| 프로그램 하나가 뚫리면 | 그 사용자의 모든 파일이 위험하다 | 그 앱의 데이터만 위험하다 |
앱마다 사용자 번호를 따로 주는 방식
안드로이드를 예로 앱을 어떻게 격리하는지 봅니다. 안드로이드는 새 장치를 만들지 않았습니다. 리눅스가 원래 가진 사용자 구분을 빌려 씁니다.
안드로이드는 리눅스 커널 위에서 도는 휴대폰 운영체제입니다. 리눅스는 사용자마다 번호를 매기고 파일마다 주인 번호를 적어 둡니다. 이 번호를 UID(User ID, 사용자 ID)라고 부릅니다.
파일을 여는 쪽은 프로세스입니다. 프로세스는 실행 중인 프로그램 하나를 말합니다. 주인만 읽게 설정된 파일은 UID 가 다른 프로세스가 열려 할 때 커널이 거절합니다.
안드로이드는 이 구분을 사람이 아니라 앱에 씁니다. 앱을 설치할 때마다 그 앱에만 쓰는 UID 를 새로 내줍니다. 앱 전용 데이터 폴더도 만들고 그 UID 를 주인으로 적습니다. 앱을 실행하면 그 UID 로 프로세스가 뜹니다.
아래 그림은 메신저 앱이 파일 두 개를 열려 할 때 커널이 어떻게 가르는지 보입니다. 메신저 앱의 UID 는 A, 은행 앱의 UID 는 B 입니다.
flowchart TD
P["메신저 프로세스 · UID A"] --> K{"커널 검사: 파일 주인이 UID A 인가"}
K -->|"메신저 폴더 · 주인 A"| OK["연다"]
K -->|"은행 앱 폴더 · 주인 B"| NO["거절한다"]
이 검사는 앱이 아니라 커널이 합니다. 앱이 스스로 지키는 약속이 아니므로 앱 코드가 공격자에게 넘어가도 격리는 남습니다. 공격자가 얻는 것은 그 앱 하나의 데이터뿐입니다.
백엔드 개발자에게 익숙한 장면과 같은 발상입니다. 서버에서는 웹 서버를 www-data 같은 전용 계정으로, PostgreSQL 을 postgres 계정으로 따로 돌립니다. 웹 서버가 뚫려도 데이터베이스 파일을 못 읽게 하려는 것입니다. 앱 샌드박스는 이 일을 운영체제가 모든 앱에 알아서 해 줍니다.
안드로이드는 그 위에 SELinux(Security-Enhanced Linux)라는 커널 보안 기능을 한 겹 더 씌웁니다. SELinux 는 프로세스를 종류로 나눕니다. 예를 들어 일반 앱 프로세스와 시스템 서비스 프로세스는 서로 다른 종류입니다.
SELinux 는 종류마다 닿을 수 있는 파일과 기능을 규칙 목록으로 정해 둡니다. 파일 권한이 실수로 넓게 열려 있어도 목록에 없는 접근은 막힙니다.
격리 밖으로 나가는 길
격리만 있으면 앱이 할 수 있는 일이 너무 적습니다. 사진 앱은 카메라가 필요하고 메신저는 연락처가 필요합니다. 그래서 운영체제는 길 두 종류를 열어 둡니다. 시스템 기능을 쓰는 권한과, 앱끼리 데이터를 주고받는 통로입니다.
인터넷 연결이나 카메라 같은 시스템 기능은 권한으로 엽니다. 앱은 필요한 권한을 앱 설정 파일에 미리 적어 둡니다. 이 설정 파일을 매니페스트라고 합니다.
권한은 두 갈래입니다. 인터넷 연결처럼 사용자에게 해가 적은 권한은 적어 두기만 하면 설치할 때 바로 받습니다. 카메라·위치·연락처처럼 민감한 권한은 적어 두는 것만으로 끝나지 않습니다.
민감한 권한은 앱이 그 기능을 쓰기 직전에 운영체제가 사용자에게 허락을 묻습니다. 이렇게 쓰는 순간에 묻는 방식을 런타임 권한이라고 부릅니다.
권한 하나는 격리된 앱에 정해진 구멍 하나를 내 줍니다. 카메라 권한을 받은 앱은 카메라를 쓸 수 있습니다. 같은 앱이라도 다른 앱의 폴더는 여전히 못 엽니다.
앱끼리 데이터를 나눌 때는 상대 폴더를 직접 열지 않습니다. 운영체제가 가운데 서는 통로로 요청을 보냅니다. 받는 앱이 무엇을 내줄지 스스로 정합니다. 안드로이드에서 앱이 쓰는 통로는 둘입니다.
| 통로 | 하는 일 | 예 |
|---|---|---|
| 인텐트 | 다른 앱에 「이 일을 해 달라」고 부탁하는 메시지 | 사진 앱의 공유 버튼이 메신저를 띄운다 |
| 콘텐츠 프로바이더 | 앱이 자기 데이터를 다른 앱에 내주는 통로 | 메신저가 연락처 앱의 전화번호 목록을 읽는다 |
두 통로는 밑에서 Binder 라는 장치를 씁니다. Binder 는 프로세스끼리 메시지를 주고받는 안드로이드의 프로세스 간 통신 장치입니다.
그래서 앱끼리 오가는 요청은 모두 운영체제를 거칩니다. 운영체제는 어느 앱이 누구에게 무엇을 요청했는지 압니다. 권한이 없는 요청이면 중간에서 막습니다.
운영체제마다 다른 모습
앱마다 격리한다는 생각은 같아도 방법은 운영체제마다 다릅니다. 셋을 나란히 놓으면 이렇습니다.
| 격리하는 방법 | 언제 걸리나 | |
|---|---|---|
| 안드로이드 | 앱마다 리눅스 UID 와 전용 폴더를 준다 | 모든 앱에 언제나 |
| iOS | 앱마다 전용 폴더를 주고 그 밖을 못 읽게 막는다 | 모든 앱에 언제나 |
| 데스크톱 운영체제 | 기본은 사용자 계정 단위 · 앱 샌드박스는 별도 기능 | 대개 앱이 켜기로 골랐을 때만 |
데스크톱에서 앱 샌드박스가 선택 사항인 까닭 하나는 오래된 프로그램입니다. 내 문서 폴더 전체를 자유롭게 읽는다고 가정하고 만든 프로그램이 많습니다. 격리를 강제로 걸면 그런 프로그램이 깨집니다.
격리가 막지 못하는 것
앱 샌드박스는 앱과 앱 사이를 막습니다. 그 경계 밖에서 생기는 문제는 다른 장치가 막아야 합니다. 대표적인 것이 셋입니다.
첫째는 사용자가 허락한 일입니다. 연락처 권한을 받은 앱이 연락처를 외부 서버로 보내는 것은 허락받은 범위 안에서 일어나는 일입니다. 운영체제는 권한을 준 뒤의 쓰임새까지 따지지 않습니다.
둘째는 커널의 버그입니다. 격리를 지키는 쪽이 커널이므로 커널에 버그가 있으면 앱이 그 버그를 타고 격리를 벗어날 수 있습니다. 이런 공격을 샌드박스 탈출이라고 합니다.
셋째는 사용자가 스스로 격리를 허무는 경우입니다. 기기의 관리자 권한을 직접 얻는 일을 안드로이드에서는 루팅, iOS 에서는 탈옥이라고 부릅니다. 관리자 권한으로 도는 앱은 UID 검사를 건너뛸 수 있어서 격리 보장이 약해집니다.
대가
앱끼리 파일을 나누기가 번거로워집니다. 데스크톱이라면 파일 경로 하나를 넘기면 끝날 일입니다. 앱 샌드박스에서는 통로로 요청하고 받는 앱이 허락해야 합니다.
앱을 만드는 쪽의 일도 늘어납니다. 필요한 권한을 미리 적어야 합니다. 사용자가 거절했을 때 돌아갈 길도 짜야 합니다. 권한 하나가 없다고 앱 전체가 멈추면 사용자는 그 앱을 지웁니다.
관련 항목
앱 샌드박스가 속하는 상위 개념
샌드박스 · 격리 · 접근 제어 · 최소 권한 · 심층 방어
격리를 맡는 운영체제 기능
UID · 파일 권한 · 프로세스 · 커널 · SELinux · 강제적 접근 제어 · 시스템 콜
격리 밖으로 내는 권한과 통로
권한 · 런타임 권한 · 매니페스트 · 인텐트 · 콘텐츠 프로바이더 · Binder · 프로세스 간 통신
앱 샌드박스를 갖춘 운영체제
Android · iOS · macOS · Windows · 운영체제 · 모바일 개발
격리를 넘거나 허무는 공격과 우회
샌드박스 탈출 · 권한 상승 · 루팅 · 탈옥 · 악성 앱
같은 목적으로 코드를 떼어 놓는 다른 격리 수단
다른 이름: app sandbox · application sandbox · 애플리케이션 샌드박스