API GUIDE/1. Introduction
2014. 2. 16. 19:22

이번 글은 보안에 관련된 글입니다.


짧은 영어 실력 때문이기도 하지만 보안에 대해서 지식이 부족한 터라 번역하는데에 시간이 많이 소요되었고 이해가 안되는 부분도 많았습니다.


그래서 번역이 좀 부실하네요. ^^;


오역에 대한 제보는 저 뿐만 아니라 이곳을 방문하시는 분 모두에게 도움이 될 겁니다. 많은 제보 부탁드립니다.

 

 


저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/topics/security/permissions.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 


시스템 퍼미션(System Permissions)

 

안드로이드는 각 어플리케이션이 ID(리눅스 사용자 ID와 그룹 ID)로 구분되는 사용 권한이 분리되어 있는 운영체제다. 시스템의 각 부분들도 ID로 서로 분리되어 있다. 이러한 방식으로 리눅스는 어플리케이션을 또다른 어플리케이션이나 시스템과 분리시켜 동작시킨다.

 

여기에 추가로 세분화된 보안 기능을 통해 URI마다 임시 사용권한을 부여하여 특정 데이터에 액세스할 수 있도록 할 수도 있다.

 

이 문서는 안드로이드에 의한 보안 기능을 개발자가 어떻게 사용할 수 있는지에 대해서 설명한다. 좀 더 일반적인 안드로이드 보안의 개요는 안드로이드 오픈 소스 프로젝트에서 제공된다.

 

 

보안 아키텍쳐(보안시스템의 구조; Security Archtecture) 


 

안드로이드 보안 아키텍쳐의 주요 골자는 기본적으로 어떠한 어플리케이션도 또다른 어플리케이션이나 운영체제, 사용자와 충돌이 있을만한 동작을 할 권한을 주지 않는다는 것이다. 충돌이 있을만한 동작이라 함은 사용자의 개인정보(주소록이나 이메일 같은)나 또다른 어플리케이션의 파일의 액세스, 네트워크 사용, 기기가 계속 켜져있게 하는 등의 동작으로 의미한다.

 

각각의 안드로이드 어플리케이션은 별도의 프로세스 샌드박스 안에서 동작하기 때문에 공유된 자원이나 데이터를 사용하고 싶다면 확실히 의사를 밝혀야 한다. 이것은 공유된 자원이나 데이터를 사용할 수 있는 권한을 선언함으로써 이루어진다. 어플리케이션이 필요한 권한을 선언하면 안드로이드 시스템은 어플리케이션이 설치될 때 이를 사용자에게 알리고 승인을 받는다. 안드로이드는 런타임(실행 중)에 동적으로 권한을 부여하지는 않는다. 왜냐하면 이런 방법은 사용자를 혼란시켜 보안에 헛점이 발생할 소지가 있기 때문이다.

 

샌드박스(sandbox)

 

보호된 영역 내에서 프로그램을 동작시키는 것으로, 외부 요인에 의해 악영향이 미치는 것을 방지하는 보안 모델. ‘아이를 모래밭(샌드 박스)의 밖에서 놀리지 않는다’라고 하는 말이 어원이라고 알려져 있다. 이 모델에서는 외부로부터 받은 프로그램을 보호된 영역, 즉 ‘상자’ 안에 가두고 나서 동작시킨다. ‘상자’는 다른 파일이나 프로세스로부터는 격리되어 내부에서 외부를 조작하는 것은 금지되고 있다.

 

출처 - [네이버 지식백과] 샌드박스 [sandbox] (IT용어사전, 한국정보통신기술협회)

 

어플리케이션 샌드박스는 어플리케이션 빌드를 위한 기술에 의존하지는 않는다. Dalvik VM은 보안의 경계가 아니며 어떤 앱이더라도 네이티브 코드로 실행이 가능하다(the Android NDK 참조). 모든 형식의 어플리케이션(Java, Native, 하이브리드)은 같은 방식으로 샌드박스 안에서 동작하게 되며 서로에 대해 동일한 보안 수준을 갖게 된다.

 

어플리케이션 서명하기(Application Signing) 


 

모든 APK(.apk 파일)는 개발자를 나타내는 개인키로 확인 서명을 해 줘야 한다. 이 확인키는 어플리케이션의 제작자를 구분해준다. 이 확인키는 인증을 받을 필요도 없으며 안드로이드 어플리케이션을 위한 자기 스스로의 증명키다. 확인키의 목적은 어플리케이션의 저자를 구분하기 위한 것이다. 이것은 시스템이 어플리케이션에 signature-level 퍼미션에 액세스 할 수 있도록 하거나 다른 어플리케이션과 동일한 리눅스 ID를 받고자 하는 요청을 할 수 있도록 해준다.

 

사용자 ID와 파일 액세스(User IDs and File Access)


 

앱을 설치할 때 안드로이드는 각 패키지에 고유의 리눅스 사용자 ID를 부여한다. ID는 프로그램이 기기에 설치되어 있는 동안 상수로써 존재하게 된다. 다른 기기에서는 같은 패키지라고 하더라도 다른 사용자 ID를 가질 수도 있다. 중요한 것은 각각의 패키지가 하나의 기기내에서는 고유한 UID를 갖는다는 사실이다.

 

보안 시스템은 프로세스 레벨에서 실행되기 때문에 두 개의 패키지가 서로 다른 유저ID를 가지고 있다면 일반적으로는 같은 프로세스에서 실행되기는 어렵다. 각 패키지가 같은 사용자 ID를 갖도록 하려면 각 패키지의 AndroidManifest.xml파일에 있는 manifest 태그의 sharedUserID 속성을 사용해야 한다. 이렇게 하면 (같은 사용자 ID를 갖게 되면) ID와 권한이 동일한 어플리케이션으로 취급받게 된다. 보안을 유지하고 싶다면 오직 두 개의 어플리케이션에서만 같은 사용자 ID를 갖도록 해야한다. 같은 사용자 ID를 갖도록 하려면 두 개의 앱은 같은 서명으로 발행되어야 하며 같은 sharedUserID를 요청해야 한다.


어플리케이션에서 저장된 모든 데이터는 해당 어플리케이션의 사용자 ID에 귀속되며 일반적으로 다른 패키지에서는 접근이 제한된다. getSharedPreferences(String, int), openFileOutput(String, int), 또는 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)를 사용하여 새로운 파일을 생성할 때 다른 패키지에서 파일을 읽고 쓸 수 있도록 하기 위해서 MODE_WORLD_READABLE 과/또는 MODE_WORLD_WRITEABLE 플래그를 사용할 수 있다. 이 플래그를 설정하려면 이 파일은 여러분이 만든 어플리케이션에 속해있어야 하며 설정이 완료되면 다른 어플리케이션에서도 파일을 읽고 쓸 수 있도록 전역 권한이 부여된다. 


퍼미션 사용하기(Using Permissions)



안드로이드 어플리케이션을 처음 생성하면 기본적으로 아무런 퍼미션도 갖고 있지 않다. 이것은 어플리케이션이 기기의 어떤 데이터에도 영향을 끼칠 수 없다는 것을 의미한다. 기기에서 보호되고 있는 기능을 사용할 수 있도록 하려면 어플리케이션에서 필요로 하는 권한을 AndroidManifest.xml에 하나 이상의 <uses-permission>태그로 선언해줘야 한다.


아래 예제는 수신되는 SMS를 감시하기 위해 필요한 권한을 선언하는 방법이다.


1
2
3
4
5
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >

    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

 

어플리케이션에서 요청했던 퍼미션은 어플리케이션이 설치될 때 패키지 설치관리자에 의해 부여 된다. 이런 퍼미션은 그냥 부여되는 것이 아니라 퍼미션을 요청한 어플리케이션의 서명을 확인하고 사용자에게 의견을 물어본 후에 결정되는 것이다. 어플리케이션이 실행되고 있는 동안에 사용자에게 퍼미션을 요청하는 일은 없다. 앱은 설치할 때 받은 퍼미션이 있다면 원할 때 언제든 연관된 기능을 사용할 수 있지만 퍼미션이 없다면 연관된 기능을 사용하려할 때 사용자에게 다시 물어보는 일 없이 무조건 실패하게 된다.


가끔 권한의 부재 때문에 securityException이 발생하기도 한다. 그러나 이 예외가 항상 발생하는 것은 아니다. 예를 들어, sendBroadcast(Intent) 함수는 호출을 하고 리턴이 된 후에 각 리시버에 데이터가 전달이 되면 그 때서야 권한을 확인하게 된다. 그래서 만일 권한 부재로 인한 문제가 있어도 예외가 발생했는지 알 수가 없다. 하지만 거의 대부분의 경우 권한 부재로 인한 문제는 시스템 로그에 기록이 남게 된다.


하지만 일반적인 사용자의 경우(구글 플레이 스토에서 앱을 설치하는 경우), 앱에서 요청하는 권한을 사용자가 승인하지 않으면 앱을 설치되지 않는다. 그렇게 때문에 일반적으로 권한 부재에 따른 실행 중 오류는 크게 걱정하지 않아도 된다.


안드로이드 시스템에서 제공하는 권한은 Manifest.permission에서 확인할 수 있다. 어떤 어플리케이션은 자체적인 권한을 정의하고 요구할 수도 있게 때문에 사용 가능한 모든 권한의 목록은 아니다.


자체적인 권한은 프로그램이 동작하는 도중에 여러 곳에서 요청될 수 있다.


  • 시스템에서 호출하려고 하는 순간 어플리케이션이 특정 함수를 실행하는 것을 막기 위하여
  • 액티비티를 시작할 때 어플리케이션이 다른 어플리케이션의 액티비티를 실행하는 것을 막기 위하여
  • 브로드케스트를 보내거나 받을 때 브로드캐스트를 받는 쪽이나 브로드캐스트를 보내는 쪽을 제어하기 위해
  • 컨텐트 프로바이더에 접근하여 뭔가 작업을 수행하려고 할 때
  • 서비스에 바인딩하거나 서비스를 시작할 때


Caution : 시간이 지나면 어떤 API를 실행하기 위해 이전에는 요구하지 않았던 어떤 권한을 요구할 수도 있기때문에 새로운 제한 조건이 플랫폼에 추가될 지도 모른다. 기존의 앱은 사용중인 API를 자유로이 사용할 수 있다고 생각하기 때문에 안드로이드는 앱이 새로운 플랫폼에서 문제가 되는 것을 방지하기 위해 앱의 매니페스트에 새로운 권한 요구를 추가시킨다. 안드로이드는 targetSdkVersion 값을 기반으로 새로운 권한이 추가되어야 할 지 여부를 판단하게 된다. 만일 새로운 권한이 추가된 플랫폼의 버전보다 이 값이 낮다면 권한을 추가하게 된다.


예를 들어, WRITE_EXTERNAL_STORAGE 권한은 공유된 저장 공간에 접근하는 것을 제한하기 위해 API 레벨 4부터 추가되었다. 만일, 여러분의 앱에 설정된 targetSdkVersion이 3이하라면 그 이후의 안드로이드 버전에서는 여러분의 앱에 이 권한이 추가될 것이다.


만일 이런 상황이 벌어진다면 여러분의 앱이 실제로 그 권한을 필요로 하지 않더라도 구글 플레이에서 사용자가 앱을 설치하려고 하면 권한을 요구하게 된다는 사실을 명심하기 바란다.


여러분의 앱이 필요로 하지 않는 권한이 기본적으로 요구되지 않도록 하려면 항상 targetSdkVersion이 가능한한 최대한 높게 유지되도록 업데이트 해줘야 한다. 새로운 플랫폼에 어떤 권한이 새로 추가되었는지 확인하려면 Build.VERSION_CODES 문서를 참고할 수 있다.



권한을 선언하고 요청하기(Declaring and Enforcing Permissions)


 

권한을 요청하기 위해서는 먼저 AndroidManifest.xml<permission> 태그를 사용하여 권한을 선언해야만 한다.


예를 들어, 어떤 어플리케이션이 여러분의 앱에 있고 외부 실행이 가능한 액티비티 중에 하나를 시작하려고 한다면 아래와 같이 권한을 선언해야 한다.


1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.me.app.myapp" >

    <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
        android:label="@string/permlab_deadlyActivity"
        android:description="@string/permdesc_deadlyActivity"
        android:permissionGroup="android.permission-group.COST_MONEY"
        android:protectionLevel="dangerous" />

    ...
</manifest>

<protectionLevel> 속성은 필수 사항이며 링크된 문서에 설명된 대로 어플리케이션이 요구하는 권한을 사용자에게 어떻게 알릴 것이며 누가 권한을 가질 수 있는지 시스템에 알려준다.


<permissionGroup>은 옵션 사항이며 시스템이 사용자에게 요구 권한을 표시하는 것을 돕기 위해서만 사용된다. 여러분은 통상적으로 이 속성을 표준 시스템 그룹(android.Manifest.permission_group에 있는 그룹)에 적용하거나 여러분이 정한 특별한 경우에 사용하게 되는데 이 속성은 사용자에게 보여주는 권한 요청 UI를 단순화시키기 위해 기존 그룹에 사용하는 것이 좋다.


필요한 권한을 선언할 때는 레이블과 설명이 함께 제공되어야 한다. 이것들은 문자열 리소스로써 사용자에게 권한의 리스트(android:label)를 보여주거나 하나의 권한에 대해 자세한 설명(android:description)을 보여줄 때 사용된다.  레이블은 권한을 주어 보호하려는 기능을 잘 표현할 수 있는 한두개 정도의 단어로 구성되도록 한다. 설명은 권한이 보호하려는 게 무엇인지에 대해 두 개의 문장으로 구성해야 한다. 첫번째 문장은 권한이 의미하는 바가 무엇인지, 두번째 문장은 사용자가 어플리케이션에 해당 권한을 부여했을 때 발생할 수 있는 나쁜 점은 무엇인지에 대해 쓰여져야 한다.


다음은 CALL_PHONE이라는 권한에 대한 레이블과 설명에 대한 예제다.


1
2
3
4
5
<string name="permlab_callPhone">directly call phone numbers</string>
    <string name="permdesc_callPhone">Allows the application to call
        phone numbers without your intervention. Malicious applications may
        cause unexpected calls on your phone bill. Note that this does not
        allow the application to call emergency numbers.</string>


현재 시스템에 정의된 권한들은 설치된 앱과 쉘커맨드 adb shell pm list permissions를 통해서 알아볼 수 있다. 설치된 앱을 통해 확인하는 방법은 설정 > 어플리케이션으로 이동한 후 하나의 앱을 선택하고 아래로 스크롤하면 된다. 개발자를 위한 방법으로는 adb에 '-s'옵션을 주면 사용자가 보는 것과 비슷하게 퍼미션들이 표시된다.


$ adb shell pm list permissions -s
All Permissions:

Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state

Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location

Services that cost you money: send SMS messages, directly call phone numbers

...


AndroidManifest.xml을 통해서 퍼미션 적용하기(Enforcing Permissions in AndroidManifest.xml)


시스템이나 어플리케이션의 전체 컴포넌트들에 대한 액세스를 제한하기 위한 고수준의 권한은 AndroidManifest.xml을 통해서 적용이 가능하다. 이러한 모든 요청은 제한하고자 하는 컴포넌트의 android:permission 속성을 설정하고 권한의 이름을 정해줘야 한다.


Activity 퍼미션(<activity>태그에 적용한다.)은 해당 액티비티의 시작 가능여부를 제한한다. 이 때 권한은 Context.startActivity()Activity.startActivityForResult()를 호출할 때 확인한다. 만일 호출자가 요구되는 권한을 갖고 있지 않으면 호출할 때 SecurityException이 발생한다.


Service 퍼미션(<service>태그에 적용한다.)은 해당 서비스를 시작시키거나 바인딩 할 수 있는지 여부를 제한한다. 권한은 Context.startService(), Context.stopService(), Context.bindService()를 하는 동안 확인한다. 만일 호출자가 요구된 권한을 갖고 있지 않으면 SecurityException이 발생한다.


BroadcastReceiver 퍼미션(<receiver>태그에 적용한다.)은 해당 리시버에 브로드캐스트를 보내는 것을 제한한다. 권한은 Context.sendBroadcast() 함수가 리턴(종료)을 하고 목표 리시버에 브로드캐스트 내용을 전달하려고 할 때 체크하게 된다. 그래서 권한 부족이라는 문제가 발생해도 호출자에 예외상황을 알리지 않는다. 그저 인텐트만 전달하지 못 할 뿐이다. 같은 이유로 브로드캐스트리시버를 프로그램 코드에서 등록하는 Context.registerReceiver()의 사용 여부를 결정짓는 권한도 마찬가지 상황에 처하게 된다. 정반대의 상황으로 Context.sendBroadcast()를 호출할 때 브로드캐스트를 수신하는 브로드캐스트 리시버를 제한하도록 퍼미션을 사용할 수도 있다.(이후 내용 참조)


ContentProvider의 퍼미션(<provider>태그로 적용시킬 수 있다.)은 ContentProvider의 데이터에 액세스하는 것을 제한한다. (컨텐트 프로바이더는 이후에 설명될 URI 퍼미션이라 불리는 중요한 보안장치를 추가로 가지고 있다.) 컨텐트 프로바이더는 다른 컴포넌트들과 달리 두 개의 퍼미션 속성을 설정할 수 있다. android:readPermission은 프로바이더로 부터 읽는 것을 제한하고 android:writePermission은 쓰는 것을 제한한다. 만일 읽기와 쓰기에 대해 퍼미션을 요구하는 프로바이더는 쓰기 권한을 가졌다고 해도 읽기를 수행할 수 없다는 것을 명심하자. 퍼미션 확인은 프로바이더를 처음 획득하게 될 때(읽기, 쓰기 권한이 둘 다 없다면 SecurityException이 발생한다.)와 프로바이더를 통해 작업을 수행할 때 이루어진다. ContentResolver.query() 함수를 사용하려면 읽기 권한이 필요하고 ContentResolver.insert(), ContentResolver.update(), ContentResolver().delete() 함수를 사용하려면 쓰기 권한이 필요하다. 이 모든 경우에 필요한 권한을 갖고 있지 않다면 SecurityException 예외가 발생한다.


브로드캐스팅 할 때 권한 적용하기(Enforcing Permissions when Sending Broadcasts)


등록된 BroadcastReceiver에 인텐트를 보낼 때 권한을 요구하는 것(앞서 설명되었다.) 외에도 브로드캐스트를 보낼 때 받는 쪽에도 권한을 요구할 수 있다. 퍼미션 문자열과 함께 Context.sendBroadcast()를 호출하게 되면 리시버가 속한 어플리케이션은 여러분이 보낸 브로드캐스트를 받기 위해서 요구된 퍼미션을 갖고 있어야만 한다.


리시버와 브로드캐스터 양쪽 모두 퍼미션을 요구할 수 있다는 사실을 명심하기 바란다. 이 경우 관련 대상에 전달되는 인텐트는 퍼미션 검사를 통과할 수 있어야 한다.


다른 사용권한 사용하기(Other Permission Enforcement)


세분화된 퍼미션은 서비스로의 어떠한 호출에도 적용할 수 있다. 그러면 어떻게 세분화되어 있는 많은 퍼미션을 가지고 있는지 알 수 있을까? 이것은 Context.checkCallingPermission() 메서드를 통해 해결할 수 있다. 필요한 퍼미션 문자열과 함께 호출하면 현재 호출하고 있는 프로세스가 퍼미션을 갖고 있는지를 나타내는 정수를 반환한다. 이 메서드는 일반적으로 서비스나 다른 프로세스로 부터 게시된 IDL 인터페이스를 통해 호출되는 경우에만 사용할 수 있다.(마지막 문장은 무슨 뜻인지 모르겠습니다. ^^;)


퍼미션을 확인하는 다른 유용한 방법도 많이 있다. 만일 여러분이 다른 프로세스의 PID를 알고 있다면 Context.checkPermission(String, int, int)메서드를 사용하여 해당 PID를 갖고 있는 프로세스가 적절한 퍼미션을 갖고 있는지 체크할 수 있다. 만일 다른 어플리케이션의 패키지 이름을 알고 있다면 패키지 매니저의 메서드인 PackageManager.checkPermission(String, String)을 통해 해당 패키지가 지정된 퍼미션을 갖고 있는지 알아낼 수 있다.


URI 사용권한(URI Permissions)



지금까지 설명한 표준 퍼미션 시스템은 컨텐트프로바이더를 사용할 때 종종 부족한 감이 있다. 컨텐트 프로바이더는 읽고 쓰기 퍼미션으로 자신을 지키고자 할 것이다. 반면 특정 URI를 다른 어플리케이션에 넘겨 그쪽에서 작업이 이루어지도록 해야할 수도 있다. 전형적인 예로 이메일에 첨부된 파일을 들 수 있다. 메일은 민감한 개인정보이기 때문에 퍼미션을 주어 액세스로부터 보호해야 한다. 그러나 만일 첨부된 파일이 이미지이고 그 이미지를 이미지 뷰어 앱으로 보려면 어떻게 해야할까. 이미지 뷰어 앱은 모든 이메일에 대해 퍼미션을 가질 필요가 없었기 때문에 십중팔구 첨부파일을 열어보기 위한 퍼미션을 갖고 있지 않을 것이다.


이 문제에 대한 해결은 URI 퍼미션이 답이다.; 액티비티를 시작하거나 액티비티에 결과를 반환할 때 호출자 쪽에서는 Intent.FLAG_GRAND_READ_URI_PERMISSION 과/또는 Intent.FLAG_GRANT_WRITE_URI_PERMISSION을 설정할 수 있다. 이 플래그들은 인텐트의 특정 URI를 수신하는 액티비티가 프로바이더의 데이터에 대해 퍼미션을 갖고 있건 말건 상관없이 특정 URI에 대해 접근할 수 있는 권한을 부여해준다.


이 메카니즘은 사용자와의 상호작용(첨부파일을 열거나 특정 연락처를 선택하는 등의 행동)을 통해 세분화된 퍼미션에 임시로 권한을 부여하는게 가능하도록 해준다.


그러나 이런 세분화된 URI 퍼미션을 부여하는 것은 URI를 보유하고 있는 컨텐트 프로바이더와 약간의 협력이 필요하다. 컨텐트 프로바이더는 이 기능을 구현하고 android:grantUriPermissions 속성이나 <grand-uri_permission>태그를 이용해 기능을 지원한다는 사실을 외부로 알려야 한다.


Context.grantUriPermission(), Context.revokeUriPermission(), Context.checkUriPermission() 메서드를 통해 좀 더 많은 정보를 구할 수 있다.



안드로이드 보안!!! 정말 어려웠습니다.


내용에 많은 오류가 있겠지만 잘 걸러서 읽어주세요.


다음 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (1)

이전 글 >> 기기 호환성(Device Compatibility)

 


posted by 리치크루