본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android 보안] 권한 #2

by 돼지왕 왕돼지 2018. 4. 17.
반응형

[android 보안] 권한 #2


/data/system/packages.xml, /system/framework/, ActivityManagerService, addpermission, android.uid.bluetooth, android.uid.log, android.uid.nfc, android.uid.phone, android.uis.system, application, bindService, com.google.uid.shared, contactsprovider, custom permission, data/system/urigrants.xml, development, development 권한, exported, F, FLAG_GRANT_PERSISTABLE_URI_PERMISSION, FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_PERMISSION, framework-res.apk, getCallingPid, getCallingUid, grant-uri-permission, grantUriPermission, GRANT_REVOKE_PERMISSIONS, INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, INSTALL_FAILED_UID_CHANGED, intent filter, INTERACT_ACROSS_USERS, IPC, jar 라이브러리, java package 형식, media key, permission tree, PID, platform key, pm grant, pm revoke, process, protection flag, registerreceiver, release key, releasePersistableUriPermission, removepermission, revokeUriPermission, sendBroadcastAsUser, shared key, Shared User ID, shared-user, sharedUserId, signature 권한, ssystem, startActivity, StartActivityForResult, StartService, stopservice, system flag, takePersistableUriPermission, targetsdk, testkey, UID, uri 권한, uri 접근 권한, [android 보안] 권한 #2, 공개 컴포넌트, 공유 사용자 id, 권한 그룹, 권한 우선순위, 동일 프로세스, 동일한 uid, 동적 권한 재부팅, 동적 권한 제거, 동적 권한 추가, 동적 제공자 권한, 메모리 공유, 명시적 공개, 보호 수준, 보호 플래그, 브로드캐스트 권한, 비공개 컴포넌트, 샌드박스, 시스템 권한, 시스템 이미지, 암시적 공개, 액티비티 권한과 서비스 권한, 재부팅, 정적 제공자 권한, 주소록 제공자, 직접 요청 권한, 커스텀 권한, 콘텐트 제공자 권한, 특정 사용자에게 보내는 broadcast, 플랫폼 키

2.7. 시스템 권한


-

프레임워크 클래스는 /system/framework/ 폴더에 JAR 파일로 패키지되어 있다.



-

프레임워크에는 JAR 라이브러리 외에도 framework-res.apk 라는 APK 파일이 있는데, 이름에서 알 수 있듯이 코드 없이 애니메이션, 그림 파일(Drawable), 레이아웃 등 패키지 프레임워크 리소스가 들어 있으며, android 패키지와 시스템 권한처럼 중요한 정보도 정의되어 있다.


framework-res.apk 도 APK 파일이므로 AndroidManifest.xml 이 들어 있는데, 여기에서는 권한 그룹과 권한이 선언되어 있다.



-

권한 그룹은 시스템 UI 에서 관련된 권한을 출력하기 위해 사용될 뿐이며, 권한은 개별적으로 요청해야 한다.

즉 앱은 권한 그룹에 속한 모든 권한을 한꺼번에 요청할 수 없다.



-

보호 수준에 보호 플래그(Protection flag)를 함께 사용하면 권한을 더 상세히 제어할 수 있다.

현재 정의되어 있는 플래그에는 system(0x10) 과 development(0x20) 플래그가 있다.


system 플래그는 시스템 이미지(읽기 전용의 system 파티션에 설치된다.)에 속해 있는 앱에만 권한을 부여한다.



* 2.7.1. signature 권한


-

시스템 앱은 “플랫폼 키"로 서명한다.

현재 안드로이드 소스 트리에는 플랫폼(Platform), 공유(Shared), 미디어(Media), 테스트 키(Testkey, 릴리즈 빌드를 위한 릴리즈 키(release key)), 이렇게 4자기 키가 있다.



-

시스템 UI, 설정, 전화, 블루투스 등 핵심 플랫폼에 속하는 패키지는 모두 플랫폼 키로,

검색 및 연락처에 관련된 패키지는 공유 키로, 

사진 앱과 미디어에 관련된 앱은 미디어 키로,

makefile 에서 서명 키를 명시하지 않은 패키지 및 이외 모든 앱은 테스트 키(릴리스 키)로 서명한다.


시스템 권한을 정의하는 framework-res.apk 는 플랫폼 키로 서명한다.

따라서 signature 보호 수준으로 시스템 권한은 요청하는 앱은 모두 프레임워크 리소스 패키지와 동일한 키로 서명되어 있어야 한다.




* 2.7.2. development 권한


-

안드로이드 쉘에서 pm grant 와 pm revoke 명령을 사용해 development 권한을 부여하거나 철회할 수 있다.


물론 이 연산은 아무나 실행할 수 있는 것은 아니며, GRANT_REVOKE_PERMISSIONS 라는 signature 권한을 가진 사용자만 이 명령을 실행할 수 있다.

이 권한은 android.uid.shell(UID 2000) 공유 사용자 ID 및 이 쉘에서 실행된 프로세스(이 프로세스들도 UID 2000으로 실행된다)에 부여된다.





2.8. 공유 사용자  ID


-

동일한 키로 서명된 안드로이드 앱은 동일한 UID 로 동일한 프로세스에서 실행될 수 있게 요청할 수 있다.

이 기능을 “공유 사용자 ID(Shared User ID)” 라고 하며, 핵심 프레임워크 서비스 및 시스템 앱에서 상당히 많이 사용한다.



-

공유 사용자 iD 는 프로세스 계정 및 앱 관리에 미묘한 영향을 미칠 수 있으므로 안드로이드 개발팀은 서드파티 앱에서는 공유 사용자 ID 사용을 권장하고 있지 않지만, 사용은 할 수 있다.

그리고 공유 사용자 ID 를 사용하지 않는 기존 앱은 공유 사용자 ID 앱으로 변환할 수 없으므로, 공유 사용자 ID 를 사용해야 하는 협업형 앱들은 개발을 시작할 떄부터 공유 사용자 ID 를 사용하도록 설계하고 배포해야 한다.



-

AndroidManifest.xml 의 최상위 요소에 sharedUserId 속성을 추가하면 공유 사용자 ID 가 활성화된다.

매니페스트에 명시한 사용자 ID 는 자바 패키지 형식(점이 적어도 하나 이상 있어야 한다)이며, 앱 패키지명과 상당히 비슷하게 일종의 식별자로 사용된다.

명시한 공유 UID 가 존재하지 않으면 새로 만들어지고, 동일한 공유 UID 를 가진 패키지가 이미 설치되어 있으면 서명 인증서를 기존 패키지의 인증서와 비교해, 만약 둘이 일치하지 않으면 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE 오류가 발생되면서 설치는 실패한다.



-

기존에 설치된 앱의 새 버전에 sharedUserId 속성을 추가하려면 UID 를 변경해야 하는데, 그러면 기존 파일에 접근할 수 없게 된다.

시스템은 UID 를 변경하지 않은 채로 sharedUserId 를 변경하는 것을 허용하지 않으며, 따라서 업데이트 할 때 INSTALL_FAILED_UID_CHANGED 오류가 발생한다.

즉, 공유 UID 앱을 계획하고 있다면 처음부터 공유 UID 를 사용하도록 설계하고 공유 UID 를 사용해야 한다.



-

안드로이드는 다음과 같이 다섯 개의 내장된 공유 UID 를 갖고 있으며, 시스템이 부팅될 때 자동으로 추가된다.


android.uis.system (SYSTEM_UID, 1000)

android.uid.phone (PHONE_UID, 1001)

android.uid.bluetooth (BLUETOOTH_UID, 1002)

android.uid.log (LOG_UID, 1007)

android.uid.nfc (NFC_UID. 1027)



-

공유 사용자 ID 를 가진 패키지는 직접 요청한 권한이 아니더라도 동일한 공유 사용자 ID 를 가진 앱이 요청한 권한까지도 사용할 수 있게 되는 부작용이 있다.

그러나 패키지를 설치하거나 제거함에 따라 권한이 <shared-user> 정의에서 동적으로 삭제될 수 있으므로, 권한들이 늘 보장되는 것은 아니다.



-

공유 UID 는 패키지 관리를 위해 사용할 뿐만 아니라 실행 시 실제로 리눅스 공유 UID 에 대응된다.



-

공유 사용자에 속한 앱은 동일한 프로세스에서 실행될 수 있다.

공유 사용자 앱은 이미 리눅스 사용자 ID 가 같아 동일한 시스템 자원에 접근할 수 있기 때문에, 공유 사용자를 지원하기 위해 커널을 수정할 필요는 없다.

매니페스트 파일의 <application> 태그의 process 속성에 동일한 프로세스명을 지정한 앱들은 모두 동일한 프로세스에서 실행된다.

동일한 프로세스에서 실행되면 메모리를 공유하므로 IPC 를 통하지 않고도 바로 통신할 수 있으며, 일부 시스템 서비스는 동일한 프로세스에서 실행되는 컴포넌트에 특별하게 접근할 수 있다. ( 예를 들어 사용자에게 로그인 화면을 보여주지 않고도 인증 토큰에 접근하거나 메모리에 저장된 패스워드에 바로 접근할 수 있다.)

구글 플레이 서비스, 구글 위치 서비스 등의 구글 앱은 이 기능을 이용해 구글 로그인 서비스와 동일한 프로세스에서 실행되도록 요청한다

이렇게 해서 사용자를 방해하지 않고도 백그라운드에서 데이터를 동기화하는 것이다.

당연히 이 앱들은 모두 동일한 인증서로 서명되어 있으며, com.google.uid.shared 공유 사용자에 속한다.






2.9. 커스텀 권한


-

시스템 권한과 마찬가지로, 커스텀 권한의 보호 수준이 normal 이나 dangerous 인 경우 사용자가 확인 다이얼로그에서 [확인] 버튼을 누르면 자동으로 권한이 부여된다.

어느 앱에 커스텀 권한을 부여할지 제어하려면 권한 보호 수준을 signature 로 선언해야 한다.

보호 수준이 signature 면 동일한 키로 서명된 앱에만 권한이 부여될 수 있게 보장한다.



-

시스템은 자신이 알고 있는 권한만 부여할 수 있다.

따라서 커스텀 권한을 정의하는 앱은 그 권한을 사용하는 앱보다 먼저 설치되어 있어야 한다.

시스템이 알지 못하는 권한을 앱에서 요청하면 시스템은 그 요청을 무시하므로 권한을 받을 수 없다.



-

앱은 PackageManager.addPermission() API 를 이용해 동적으로 권한을 추가할 수 있고, 이 클래스의 removePermission() API 를 이용해 권한을 제거할 수 있다.

이렇게 동적으로 추가하는 권한은 앱이 정의하는 권한 트리(Permission Tree) 밑에 들어가야 한다.

앱에서는 자기 자신의 패키지나, 혹은 동일한 공유 사용자 ID 로 실행되는 다른 패키지의 권한 트리 밑에 있는 권한만 추가하거나 제거할 수 있다.


권한 트리명은 역 도메인 표기법으로 정의하며, 권한명 앞에 권한 트리명과 점(.) 이 오면 그 권한이 권한 트리에 속한다고 판단한다.



-

동적으로 추가한 권한은 패키지 데이터베이스(/data/system/packages.xml)에 추가된다.

이 권한은 매니페스트 파일에 정의한 권한과 마찬가지로 재부팅해도 사라지지 않으며, dynamic 값을 가진 type 속성을 부가적으로 갖고 있다.





2.10. 공개 컴포넌트와 비공개 컴포넌트


-

콘텐트 제공자를 제외한 모든 컴포넌트는 기본적으로 비공개로 설정된다.

콘텐트 제공자의 목적이 다른 앱과 데이터를 공유하는 것이므로 초기에는 기본적으로 공개로 설정되어 있었지만, 안드로이드 4.2(targetSdk17 이상)에서 이 방식이 변경되었다.

targetSdk17 및 이후 버전으로 설정하면 콘텐트 제공자가 기본적으로 비공개되지만, 하위 버전 호환을 위해 이전 targetSdk 를 이전 버전으로 하는 경우 기본적으로 공개로 설정된다.



-

컴포넌트는 exported 속성을 “true”로 설정하는 명시적인 방법이나 인텐트 필터를 선언하는 암시적인 방법으로 공개될 수 있다.

인텐트 필터를 갖고 있지만 공개하지 않을 컴포넌트는 exported 속성을 “false” 로 설정해 비공개로 설정할 수 있다.

컴포넌트가 공개되어 있지 않으면 루트나 시스템 프로세스가 아닌 호출자 프로세스가 갖고 있는 권한과 상관없이 액티비티 매니저는 호출을 차단한다.





2.11. 액티비티 권한과 서비스 권한


-

다른 앱에서 액티비티로 대응되는 인텐트를 가지고 Context.startActivity() 나 startActivityForResult() 를 호출할 때 액티비티 권한을 검사한다.

서비스의 경우 다른 앱이 해당 서비스에 대응되는 인텐트를 가지고 Context.startService(), stopService(), bindService() 중 하나를 호출할 때 권한을 검사한다.





2.12. 브로드캐스트 권한


-

다중 사용자 디바이스에 INTERACT_ACROSS_USERS 권한을 가진 시스템 앱은 sendBroadcastAsUser(Intent intent, UserHandler user) 나 sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) 메서드를 이용해 특정 사용자에게만 전달되는 브로드캐스트를 보낼 수 있다.



-

브로드캐스트 수신자는 정적이나 동적으로 자신에게 브로드캐스트를 보내는 발신자를 제한할 수 있다.

정적으로 등록된 수신자의 경우에는 매니페스트 파일의 <receiver> 태그에 permission 속성을 사용하고, 동적으로 등록된 수신자의 경우에는 Context.registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) 메서드를 호출해 발신자가 갖춰야 할 권한을 지정한다.





2.13. 콘텐트 제공자 권한


* 2.13.1. 정적 제공자 권한


-

제공자 전체에 대한 접근을 제어하는 권한을 단 하나의 permission 속성으로 지정할 수도 있지만, 대부분의 제공자는 읽기와 쓰기에 대해 다른 권한을 적용한다.

심지어 URI 마다 권한을 지정할 수도 있다.

내장된 주소록 제공자(ContactsProvider) 도 읽기와 쓰기에 대해 서로 다른 권한을 사용한다.



-

URI 마다 지정한 권한은 컴포넌트에 지정한 권한보다 우선순위가 높으므로, 연결된 권한이 있는 콘텐트 제공자 URI 에 접근하려는 앱은 원하는 URI 에 대한 권한만 있으면 되며 컴포넌트 수준의 권한은 필요하지 않다.




* 2.13.2. 동적 제공자 권한


-

정적으로 URI 마다 정의한 권한이 상당히 강력하지만, 간혹 앱에서는 특정한 권한이 없는 다른 앱이(URI 를 통해 참조할 수 있는) 일부 데이터에 일시적으로 접근할 수 있게 허용해야 할 경우가 있다.

예를 들어 이메일이나 메시지 앱은 첨부 파일을 화면에 보여주기 위해 이미지 뷰어 앱을 이용할 때가 있다.

그러나 앱에서는 첨부된 이미지의 URI 를 미리 알 수 없으므로, 정적으로 URI 마다 권한을 부여하려면 모든 첨부 이미지에 대한 읽기 권한을 이미지 뷰어 앱에 부여해야 하는데, 이런 방식은 바람직하지 않다.



-

모든 이미지에 읽기 권한을 부여함으로써 발생되는 보안 문제를 피하기 위해, 앱에서는 URI 에 대한 접근 권한을 일시적으로 허용하거나 철회할 수 있다.

동적으로 권한을 부여하려면 Context.grantUriPermission(String toPackage, Uri uri, int modeFlags) 메서드를 사용하고, 부여한 권한을 철회하려면 revokeUriPermission(Uri uri, int modeFlags) 메서드를 사용한다.

특정 URI 에 대한 접근을 임시로 허용하려면 grantUriPermissions 속성을 true 로 설정하거나 <grant-uri-permission> 태그를 추가해야 한다.



-

사실 앱에서 URI 마다 접근을 허용하기 위해 Context.grantPermission() 과 revokePermission() 메서드를 직접 호출하는 일은 거의 없다.

대신, 이미지 뷰어 등 앱을 실행시키기 위해 사용하는 인텐트에 FLAG_GRANT_READ_URI_PERMISSION 이나 FLAG_GRANT_WRITE_PERMISSION 플래그를 설정한다.

이 플래그를 설정하면 인텐트의 수신자는 URI 에 해당하는 데이터에 읽거나 쓸 수 있는 권한을 갖게 된다.



-

안드로이드 4.4(Kitkat)부터 인텐트 수신자가 FLAG_GRANT_PERSISTABLE_URI_PERMISSION 플래그를 설정하면 ContentResolver.takePersistableUriPermission() 메서드로부터 부여받은 URI 에 대한 접근 권한은 재부팅해도 사라지지 않고 계속 유지된다.

URI 접근 권한은 /data/system/urigrants.xml 파일에 저장되며, releasePersistableUriPermission() 메서드를 호출해야 제거된다.

일시적이든 영구적이든 URI 에 대한 접근 권한은 시스템 액티비티 매니저 서비스(ActivityManagerService)에 의해 관리된다.

URI 접근에 관련된 API 는 내부적으로 액티비티 매니저 서비스를 호출한다.



-

FLAG_GRANT_* 인텐트 플래그로 부여받은 URI 접근은 호출된 앱의 작업이 종료될 때 자동으로 해제되므로 revokePermission() 메서드를 호출할 필요가 없다.






2.14. 팬딩 인텐트


-

펜딩 인텐트(Pending Intent)는 안드로이드 컴포넌트도 아니고 권한도 아니지만, 앱에서 자신의 권한을 다른 앱에 허용한다.



-

팬딩 인텐트를 전달하면 다른 앱에서 인텐트를 생성한 원래 앱의 식별정보와 권한으로 지정한 액션을 수행할 수 있다.

팬딩 인텐트에 저장된 식별 정보는 시스템 액티비티 매니저 서비스에서 보장하며, 시스템 액티비티 매니저 서비스는 현재 활성화되어 있는 펜딩 인텐트를 추적관리한다.



-

펜딩 인텐트는 알람(Alarm) 및 노티피케이션(Notification)을 구현하기 위해 사용한다.

알림과 노티피케이션을 생성한 앱은 종료된 후에도 알람과 노티피케이션이 발생될 수 있으며, 시스템은 펜딩 인텐트에 들어 있는 정보를 이용해 앱을 대신해 인텐트 액션을 실행한다.



-

펜딩 인텐트는 비시스템 앱에도 전달할 수 있으며, 적용되는 규칙은 같다.



-

펜딩 인텐트의 구현은 약간 복잡하지만 다른 안드로이드 컴포넌트가 기반을 두고 있는 동일한 IPC 와 샌드박스 원리에 기반을 두고 있다.

앱이 펜딩 인텐트를 생성하면 시스템은 Binder.getCallingUid() 와 Binder.getCallingPid() 를 이용해 UID/PID 를 받는다.

이 식별 정보에 기반해 시스템은 인텐트를 생성한 앱의 패키지명과 (다중 사용자 디바이스에서) 사용자 ID 를 알아내고, 전송할 인텐트와 추가적인 메타데이터와 함께 PendingIntentRecord 에 저장한다.


액티비티 매니저는 활성화된 펜딩 인텐트들을 추적관리하며, 알람이나 노티피케이션이 발생할 때 필요한 레코드를 가져온다.

그런 다음 레코드에 들어 있는 정보를 이용해 펜딩 인텐트 생성자의 식별 정보를 이용해 지정한 연산을 수행한다.

이때부터 프로세스는 다른 안드로이드 컴포넌트와 마찬가지로 권한 검사를 거치며 실행된다.





2.15. 요약


-

안드로이드는 각각의 앱을 제한된 샌드박스에서 실행하며, 다른 앱이나 시스템의 기능을 사용하려면 앱이 구체적인 권한을 요청해야 한다.

권한은 특정 연산을 수행할 수 있는 능력을 나타내는 문자열이며, (development 권한을 제외하고) 앱 설치 시에 부여되고 앱을 제거하기 전까지 해당 권한을 유지한다. ( MOS, RuntimePermission 이 등장하면서 또 이야기는 달라진다. )

권한은 리눅스 보조 GID 에 대응되며, 커널은 시스템 자원에 대한 접근을 허용하기 전에 보조 GID 를 검사한다.



-

상위 수준 시스템 서비스는 바인더를 통해 호출자 앱의 UID 를 가져오고, 이 UID 의 권한으로 패키지 매니저 데이터베이스를 검색해 권한을 적용한다.

앱의 매니페스트 파일에 선언된 컴포넌트에 연결된 권한은 시스템에 의해 자동으로 적용되지만, 앱에서 추가적으로 동적 권한 검사를 수행할 수도 있다.

내장된 권한 외에도 앱에서는 커스텀 권한을 정의하고 자신의 컴포넌트에 연결해 접근을 제어할 수 있다.



-

모든 안드로이드 컴포넌트는 권한을 요구할 수 있으며, 콘텐트 제공자는 부가적으로 URI 마다 읽기 및 쓰기 권한을 지정할 수 있다.

팬딩 인텐트는 인텐트와 수행할 액션 외에도 자신을 생성한 앱의 식별 정보를 캡슐화해서 시스템이나 서드파티 앱이 원래 앱의 식별 정보와 권한으로 연산을 수행할 수 있게 한다.





정리 목차


2. 권한

     2.1. 권한의 본질


     2.2. 권한 요청


     2.3. 권한 관리


     2.4. 권한 보호 수준

          2.4.1. normal

          2.4.2. dangerous

          2.4.3. signature

          2.4.4. signatureOrSystem


     2.5.권한 할당

          2.5.1. 권한과 프로세스 속성

          2.5.2. 프로세스 속성 할당 


     2.6. 권한 적용

          2.6.1. 커널 수준 적용

          2.6.2. 네이티브 데몬 수준 적용

          2.6.3. 프레임워크 수준 적용


     2.7. 시스템 권한

          2.7.1. signature 권한

          2.7.2. development 권한


     2.8. 공유 사용자 ID


     2.9. 커스텀 권한


     2.10. 공개 컴포넌트와 비공개 컴포넌트


     2.11. 액티비티 권한과 서비스 권한


     2.12. 브로드캐스트 권한


     2.13. 콘텐트 제공자 권한

          2.13.1. 정적 제공자 권한

          2.13.2. 동적 제공자 권한


     2.14. 팬딩 인텐트


     2.15. 요약





반응형

댓글