https://source.android.com/docs/core/connect/esim-overview?hl=ko

 

eSIM 구현  |  Android 오픈소스 프로젝트  |  Android Open Source Project

eSIM 구현 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 임베디드 SIM(eSIM 또는 eUICC) 기술을 사용하면 휴대기기 사용자는 실물 SIM 카드 없이 이동통신사 프로

source.android.com

 

eSIM 구현

 bookmark_border

임베디드 SIM(eSIM 또는 eUICC) 기술을 사용하면 휴대기기 사용자는 실물 SIM 카드 없이 이동통신사 프로필을 다운로드하고 이동통신사의 서비스를 활성화할 수 있습니다. 이 기술은 GSMA에서 지원하는 글로벌 사양으로 모든 휴대기기의 원격 SIM 프로비저닝(RSP)을 사용 설정할 수 있습니다. Android 프레임워크는 Android 9부터 eSIM 액세스 및 eSIM의 구독 프로필 관리를 위한 표준 API를 제공합니다. 이러한 eUICC API를 사용하면 서드 파티가 eSIM 지원 Android 기기에서 자체 이동통신사 앱 및 로컬 프로필 도우미(LPA)를 개발할 수 있습니다.

LPA는 Android 빌드 이미지에 포함되어야 하는 독립형 시스템 앱입니다. LPA는 SM-DP+(프로필 패키지를 준비하고 저장하여 기기에 전달하는 원격 서비스)와 eUICC 칩 사이를 연결하는 역할을 하므로 일반적으로 eSIM의 프로필 관리 작업을 담당합니다. LPA APK는 LPA UI 또는 LUI라 불리는 UI 구성요소를 선택적으로 포함할 수 있습니다. 이 구성요소를 통해 최종 사용자는 삽입된 모든 구독 프로필을 한곳에서 관리할 수 있습니다. Android 프레임워크는 사용 가능한 최적의 LPA를 자동으로 탐색하여 연결하고 LPA 인스턴스를 통해 모든 eUICC 작업을 라우팅합니다.

그림 1. 단순화된 RSP 아키텍처

이동통신사 앱을 만들려는 모바일 네트워크 운영자는 downloadSubscription(), switchToSubscription()  deleteSubscription()과 같은 상위 수준의 프로필 관리 작업을 제공하는 EuiccManager의 API를 살펴봐야 합니다.

자체 LPA 시스템 앱을 만들려는 기기 OEM은 Android 프레임워크가 LPA 서비스에 연결되도록 EuiccService를 확장해야 합니다. 또한, GSMA RSP v2.0 기반의 ES10x 함수를 제공하는 EuiccCardManager의 API를 사용해야 합니다. 이러한 함수는 prepareDownload(), loadBoundProfilePackage(), retrieveNotificationList()  resetMemory()와 같은 명령어를 eUICC 칩에 실행하는 데 사용됩니다.

EuiccManager의 API는 제대로 구현된 LPA 앱이 있어야 작동하며 EuiccCardManager API의 호출자는 LPA여야 합니다. 이는 Android 프레임워크에서 시행합니다.

Android 10 이상을 실행하는 기기는 여러 eSIM을 사용하는 기기를 지원할 수 있습니다. 자세한 내용은 여러 eSIM 지원을 참고하세요.

이동통신사 앱 만들기

Android 9의 eUICC API를 사용하면 모바일 네트워크 운영자가 이동통신사 브랜드의 앱을 만들어 프로필을 직접 관리할 수 있습니다. 즉, 이동통신사가 소유한 구독 프로필을 다운로드 및 삭제하고 이러한 프로필로 전환하는 등의 관리가 가능합니다.

EuiccManager

EuiccManager는 앱이 LPA와 상호작용하는 기본 진입점입니다. 여기에는 이동통신사가 소유한 구독을 다운로드 및 삭제하고 이러한 구독으로 전환하는 이동통신사 앱이 포함됩니다. 또한, 삽입된 모든 구독을 한곳의 UI에서 관리할 수 있는 LUI 시스템 앱이 포함됩니다. 이는 EuiccService를 제공하는 앱과는 별도의 앱일 수 있습니다.

공개 API를 사용하려면 먼저 이동통신사 앱에서 다음과 같이 Context#getSystemService를 통해 EuiccManager의 인스턴스를 가져와야 합니다.

 
EuiccManager mgr = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);

eSIM 작업을 실행하기 전에 기기에서 eSIM을 지원하는지 확인해야 합니다. android.hardware.telephony.euicc 기능이 정의되어 있고 LPA 패키지가 있다면 EuiccManager#isEnabled()는 일반적으로 true를 반환합니다.

 
if (mgr == null || !mgr.isEnabled()) {
    return;
}

다음과 같은 방법으로 eUICC 하드웨어와 eSIM OS 버전에 관한 정보를 얻을 수 있습니다.

 
EuiccInfo info = mgr.getEuiccInfo();
String osVer = info.getOsVersion();

downloadSubscription()  switchToSubscription()과 같은 많은 API는 완료하는 데 몇 초 또는 몇 분이 걸릴 수 있기 때문에 PendingIntent 콜백을 사용합니다. PendingIntent는 LPA에서 EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE로 전파되는 임의의 자세한 결과 코드뿐만 아니라 프레임워크에서 정의한 오류 코드를 제공하는 EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_ 공간의 결과 코드와 함께 전송되므로 이동통신사 앱에서 로깅/디버깅 목적으로 추적할 수 있습니다. PendingIntent 콜백은 BroadcastReceiver여야 합니다.

다운로드 가능한 특정 구독(활성화 코드 또는 QR 코드에서 생성)을 다운로드하려면 다음을 따르세요.

 
// Register receiver.
static final String ACTION_DOWNLOAD_SUBSCRIPTION = "download_subscription";
static final String LPA_DECLARED_PERMISSION
    = "com.your.company.lpa.permission.BROADCAST";
BroadcastReceiver receiver =
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (!action.equals(intent.getAction())) {
                    return;
                }
                resultCode = getResultCode();
                detailedCode = intent.getIntExtra(
                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                    0 /* defaultValue*/);

                // If the result code is a resolvable error, call startResolutionActivity
                if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR) {
                    PendingIntent callbackIntent = PendingIntent.getBroadcast(
                        getContext(), 0 /* requestCode */, intent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
                    mgr.startResolutionActivity(
                        activity,
                        0 /* requestCode */,
                        intent,
                        callbackIntent);
                }

                resultIntent = intent;
            }
        };
context.registerReceiver(receiver,
        new IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION),
        LPA_DECLARED_PERMISSION /* broadcastPermission*/,
        null /* handler */);

// Download subscription asynchronously.
DownloadableSubscription sub = DownloadableSubscription
        .forActivationCode(code /* encodedActivationCode*/);
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
mgr.downloadSubscription(sub, true /* switchAfterDownload */,
        callbackIntent);

구독 ID가 지정된 구독으로 전환하려면 다음을 따르세요.

 
// Register receiver.
static final String ACTION_SWITCH_TO_SUBSCRIPTION = "switch_to_subscription";
static final String LPA_DECLARED_PERMISSION
    = "com.your.company.lpa.permission.BROADCAST";
BroadcastReceiver receiver =
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (!action.equals(intent.getAction())) {
                    return;
                }
                resultCode = getResultCode();
                detailedCode = intent.getIntExtra(
                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                    0 /* defaultValue*/);
                resultIntent = intent;
            }
        };
context.registerReceiver(receiver,
        new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION),
        LPA_DECLARED_PERMISSION /* broadcastPermission*/,
        null /* handler */);

// Switch to a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
mgr.switchToSubscription(1 /* subscriptionId */, callbackIntent);

EuiccManager API와 코드 예의 전체 목록은 eUICC API에서 볼 수 있습니다.

해결 가능한 오류

시스템에서 eSIM 작업을 완료할 수 없지만 오류가 사용자에 의해 해결될 수 있는 몇 가지 경우가 있습니다. 예를 들어, 프로필 메타데이터에 이동통신사 확인 코드가 필수라고 표시되어 있으면 downloadSubscription은 실패할 수 있습니다. 또는 이동통신사 앱에 대상 프로필(이동통신사가 소유한 프로필)에 관한 이동통신사 권한은 있지만 현재 사용 설정된 프로필에 관한 이동통신사 권한은 없는 경우 switchToSubscription이 실패할 수 있으며 이런 이유로 사용자의 동의가 필요합니다.

이러한 상황에서 호출자의 콜백은 EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR와 함께 호출됩니다. 콜백 Intent는 호출자가 인텐트를 EuiccManager#startResolutionActivity에 전달할 때 LUI를 통해 확인을 요청할 수 있도록 내부 추가 항목들을 포함합니다. 예를 들어, 확인 코드를 다시 사용하면 EuiccManager#startResolutionActivity에서 사용자가 확인 코드를 입력할 수 있는 LUI 화면을 트리거합니다. 코드를 입력하면 다운로드 작업이 계속됩니다. 이 접근 방식은 이동통신사 앱에 UI가 표시되는 시점에 관한 전체 제어권을 부여하지만 LPA/LUI에는 향후 클라이언트 앱을 변경하지 않고 사용자가 복구할 수 있는 문제를 처리하는 새로운 방식을 추가하도록 확장 가능한 방법을 제공합니다.

Android 9는 LUI에서 처리해야 하는 이러한 해결 가능한 오류를 다음과 같이 EuiccService에 정의합니다.

 
/**
 * Alert the user that this action will result in an active SIM being
 * deactivated. To implement the LUI triggered by the system, you need to define
 * this in AndroidManifest.xml.
 */
public static final String ACTION_RESOLVE_DEACTIVATE_SIM =
        "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
/**
 * Alert the user about a download/switch being done for an app that doesn't
 * currently have carrier privileges.
 */
public static final String ACTION_RESOLVE_NO_PRIVILEGES =
        "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";

/** Ask the user to resolve all the resolvable errors. */
public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS =
        "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";

 

이동통신사 권한

EuiccManager를 호출하여 기기에 프로필을 다운로드하는 자체 이동통신사 앱을 개발하는 이동통신사라면 메타데이터의 이동통신사 앱에 상응하는 이동통신사 권한 규칙을 프로필에 포함해야 합니다. 이는 서로 다른 이동통신사에 속한 구독 프로필이 기기의 eUICC에 공존할 수 있고, 각 이동통신사 앱은 이동통신사에서 소유한 프로필에만 액세스할 수 있어야 하기 때문입니다. 예를 들어, 이동통신사 A에서 이동통신사 B가 소유한 프로필을 다운로드하거나 사용 또는 사용 중지할 수 있어서는 안 됩니다.

소유자만 프로필에 액세스할 수 있도록 하기 위해 Android는 프로필 소유자 앱(즉, 이동통신사 앱)에 특별한 권한을 부여하는 메커니즘을 사용합니다. Android 플랫폼은 프로필의 액세스 규칙 파일(ARF)에 저장된 인증서를 로드하고 이 인증서에서 서명한 앱에 권한을 부여하여 EuiccManager API를 호출합니다. 상위 수준의 프로세스는 다음과 같습니다.

  1. 운영자는 이동통신사 앱 APK에 서명하며, apksigner 도구는 APK에 공개키 인증서를 첨부합니다.
  2. 운영자/SM-DP+는 다음이 포함된 ARF를 포함하는 프로필과 메타데이터를 준비합니다.
    1. 이동통신사 앱의 공개키 인증서 서명(SHA-1 또는 SHA-256)(필수사항)
    2. 이동통신사 앱의 패키지 이름(적극 권장됨)
  3. 이동통신사 앱은 EuiccManager API를 통해 eUICC 작업 실행을 시도합니다.
  4. Android 플랫폼은 호출자 앱 인증서의 SHA-1 또는 SHA-256 해시가 대상 프로필의 ARF에서 가져온 인증서 서명과 일치하는지 확인합니다. 이동통신사 앱의 패키지 이름이 ARF에 포함되어 있다면 이 이름은 호출자 앱의 패키지 이름과도 일치해야 합니다.
  5. 서명 및 패키지 이름(포함된 경우)이 확인된 후에는 대상 프로필에 대한 이동통신사 권한이 호출자 앱에 부여됩니다.

프로필 메타데이터는 LPA가 프로필 메타데이터를 프로필이 다운로드되기 전에 SM-DP+에서 가져오거나 프로필이 사용 중지된 경우 ISD-R에서 가져올 수 있도록 프로필 외부에서도 사용할 수 있기 때문에, 프로필과 동일한 이동통신사 권한 규칙을 포함해야 합니다.

eUICC OS 및 SM-DP+는 프로필 메타데이터에서 독점 태그 BF76을 지원해야 합니다. 태그 콘텐츠는 다음과 같이 UICC 이동통신사 권한에 정의된 액세스 규칙 애플릿(ARA)에서 반환한 것과 동일한 이동통신사 권한 규칙이어야 합니다.

 
RefArDo ::= [PRIVATE 2] SEQUENCE {  -- Tag E2
    refDo [PRIVATE 1] SEQUENCE {  -- Tag E1
        deviceAppIdRefDo [PRIVATE 1] OCTET STRING (SIZE(20|32)),  -- Tag C1
        pkgRefDo [PRIVATE 10] OCTET STRING (SIZE(0..127)) OPTIONAL  -- Tag CA
    },
    arDo [PRIVATE 3] SEQUENCE {  -- Tag E3
        permArDo [PRIVATE 27] OCTET STRING (SIZE(8))  -- Tag DB
    }
}

앱 서명에 관한 자세한 내용은 앱 서명을 참고하세요. 이동통신사 권한에 관한 자세한 내용은 UICC 이동통신사 권한을 참고하세요.

 

로컬 프로필 어시스턴트 앱 만들기

Android Euicc API를 사용하여 연결해야 하는 자체 로컬 프로필 어시스턴트(LPA)를 구현할 수 있습니다. 다음 섹션에서는 LPA 앱을 만들고 Android 시스템과 통합하는 방법에 관한 간단한 개요를 설명합니다.

하드웨어/모뎀 요구사항

eUICC 칩의 LPA 및 eSIM OS는 최소 GSMA RSP(원격 SIM 프로비저닝) v2.0 또는 v2.2를 지원해야 합니다. 또한, RSP 버전이 일치하는 SM-DP+ 및 SM-DS 서버 사용을 계획해야 합니다. 자세한 RSP 아키텍처는 GSMA SGP.21 RSP 아키텍처 사양을 참고하세요.

또한, Android 9의 eUICC API와 통합하려면 기기 모뎀이 인코딩된 eUICC 기능(로컬 프로필 관리 및 프로필 다운로드)을 지원하는 터미널 기능을 전송해야 합니다. 또한, 다음 메서드를 구현해야 합니다.

  • IRadio HAL v1.1: setSimPower
  • IRadio HAL v1.2: getIccCardStatus참고: IRadio HAL v1.4 지원 기능은 Android 10을 실행하는 기기에 필요하며 다른 모든 버전의 Android에서도 사용하는 것이 좋습니다.
  • IRadioConfig HAL v1.0: getSimSlotsStatus참고: IRadioConfig HAL v1.2 지원 기능은 Android 10을 실행하는 기기에 필요하며 다른 모든 버전의 Android에서도 사용하는 것이 좋습니다.

모뎀은 유효한 SIM으로 사용 설정된 기본 부팅 프로필로 eSIM을 인식하고 SIM 전원이 켜진 상태를 유지해야 합니다.

Android 10을 실행하는 기기에서는 비탈착식 eUICC 슬롯 ID 배열을 정의해야 합니다. 아래 arrays.xml 예를 참고하세요.

 
<resources>
   <!-- Device-specific array of SIM slot indexes which are are embedded eUICCs.
        e.g. If a device has two physical slots with indexes 0, 1, and slot 1 is an
        eUICC, then the value of this array should be:
            <integer-array name="non_removable_euicc_slots">
                <item>1</item>
            </integer-array>
        If a device has three physical slots and slot 1 and 2 are eUICCs, then the value of
        this array should be:
            <integer-array name="non_removable_euicc_slots">
               <item>1</item>
               <item>2</item>
            </integer-array>
        This is used to differentiate between removable eUICCs and built in eUICCs, and should
        be set by OEMs for devices which use eUICCs. -->

   <integer-array name="non_removable_euicc_slots">
       <item>1</item>
   </integer-array>
</resources>

모뎀 요구사항의 전체 목록은 eSIM 지원을 위한 모뎀 요구사항을 참고하세요.

EuiccService

LPA는 LPA 백엔드 및 LPA UI(LUI), 이렇게 두 개의 개별 구성요소로 구성되어 있습니다(동일한 APK에 모두 구현될 수 있음).

LPA 백엔드를 구현하려면 EuiccService를 확장하고 매니페스트 파일에 이 서비스를 선언해야 합니다. 서비스는 시스템만 서비스에 결합할 수 있도록 하기 위해 android.permission.BIND_EUICC_SERVICE 시스템 권한을 요구해야 합니다. 또한 서비스에는 android.service.euicc.EuiccService 작업이 있는 인텐트 필터가 포함되어야 있어야 합니다. 기기에 여러 구현이 있다면 인텐트 필터의 우선순위를 0이 아닌 값으로 설정해야 합니다. 예:

 
<service
    android:name=".EuiccServiceImpl"
    android:permission="android.permission.BIND_EUICC_SERVICE">
    <intent-filter android:priority="100">
        <action android:name="android.service.euicc.EuiccService" />
    </intent-filter>
</service>

내부적으로 Android 프레임워크는 활성 LPA를 결정하고 필요한 경우 Android eUICC API를 지원하기 위해 해당 LPA와 상호작용합니다. PackageManager는 android.service.euicc.EuiccService 작업의 서비스를 지정하는 android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS 권한을 가진 모든 앱에 쿼리됩니다. 우선순위가 가장 높은 서비스가 선택됩니다. 서비스가 없으면 LPA 지원이 중지됩니다.

LUI를 구현하려면 다음 작업을 위해 활동을 제공해야 합니다.

  • android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS
  • android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION

서비스와 마찬가지로 각 활동은 android.permission.BIND_EUICC_SERVICE 시스템 권한을 요구해야 합니다. 각 활동에는 적절한 작업, android.service.euicc.category.EUICC_UI 카테고리 및 0이 아닌 우선순위가 지정된 인텐트 필터가 있어야 합니다. EuiccService의 구현을 선택하는 것과 마찬가지로 유사한 로직을 사용하여 이러한 활동의 구현을 선택합니다. 예:

 
<activity android:name=".MyLuiActivity"
          android:exported="true"
          android:permission="android.permission.BIND_EUICC_SERVICE">
    <intent-filter android:priority="100">
        <action android:name="android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS" />
        <action android:name="android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.service.euicc.category.EUICC_UI" />
    </intent-filter>
</activity>

이는 이러한 화면을 구현하는 UI가 EuiccService를 구현하는 APK가 아닌 다른 APK에서 제공될 수 있음을 의미합니다. 단일 APK를 사용할지 또는 여러 APK(예: EuiccService를 구현하는 APK와 LUI 활동을 제공하는 APK)를 사용할지 여부는 디자인 선택입니다.

EuiccCardManager

EuiccCardManager는 eSIM 칩과 통신하기 위한 인터페이스입니다. GSMA RSP 사양에 설명된 대로 ES10 함수를 제공하고 ASN.1 파싱과 함께 하위 수준의 APDU 요청/응답 명령어를 처리합니다. EuiccCardManager는 시스템 API이며 시스템 권한을 가진 앱에서만 호출할 수 있습니다.

그림 2. 이동통신사 앱과 LPA 모두 Euicc API 사용

EuiccCardManager를 통한 프로필 작업 API는 호출자가 LPA여야 합니다. 이는 Android 프레임워크에서 시행합니다. 즉, 호출자는 이전 섹션에서 설명한 것처럼 EuiccService를 확장하고 매니페스트 파일에 선언되어야 합니다.

EuiccManager와 마찬가지로 EuiccCardManager API를 사용하려면 먼저 다음과 같이 LPA에서 Context#getSystemService를 통해 EuiccCardManager의 인스턴스를 가져와야 합니다.

 
EuiccCardManager cardMgr = (EuiccCardManager) context.getSystemService(Context.EUICC_CARD_SERVICE);

그런 다음 eUICC의 모든 프로필을 가져오려면 다음 코드를 참고하세요.

 
ResultCallback<EuiccProfileInfo[]> callback =
       new ResultCallback<EuiccProfileInfo[]>() {
           @Override
           public void onComplete(int resultCode,
                   EuiccProfileInfo[] result) {
               if (resultCode == EuiccCardManagerReflector.RESULT_OK) {
                   // handle result
               } else {
                   // handle error
               }
           }
       };

cardMgr.requestAllProfiles(eid, AsyncTask.THREAD_POOL_EXECUTOR, callback);

내부적으로 EuiccCardManager는 AIDL 인터페이스를 통해 EuiccCardController(전화 프로세스에서 실행됨)에 결합하고 각 EuiccCardManager 메서드는 다른 전용 AIDL 인터페이스를 통해 전화 프로세스에서 콜백을 수신합니다. EuiccCardManager API를 사용할 때 호출자(LPA)는 Executor 객체를 제공해야 하며 이 객체를 통해 콜백이 호출됩니다. 이 Executor 객체는 단일 스레드 또는 선택한 스레드 풀에서 실행할 수 있습니다.

대부분의 EuiccCardManager API는 동일한 사용 패턴을 보입니다. 예를 들어, 결합한 프로필 패키지를 eUICC로 로드하는 방법은 다음과 같습니다.

 
...
cardMgr.loadBoundProfilePackage(eid, boundProfilePackage,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

특정 ICCID를 사용하여 다른 프로필로 전환하는 방법은 다음과 같습니다.

 
...
cardMgr.switchToProfile(eid, iccid, true /* refresh */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

eUICC 칩에서 기본 SM-DP+ 주소를 가져오는 방법은 다음과 같습니다.

 
...
cardMgr.requestDefaultSmdpAddress(eid, AsyncTask.THREAD_POOL_EXECUTOR,
        callback);

지정된 알림 이벤트의 알림 목록을 가져오는 방법은 다음과 같습니다.

 
...
cardMgr.listNotifications(eid,
        EuiccNotification.Event.INSTALL
              | EuiccNotification.Event.DELETE /* events */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

이동통신사 앱을 통해 eSIM 프로필 활성화

Android 9 이상을 실행하는 기기에서는 이동통신사 앱을 사용하여 eSIM을 활성화하고 프로필을 다운로드할 수 있습니다. 이동통신사 앱은 downloadSubscription을 직접 호출하거나 LPA에 활성화 코드를 제공하여 프로필을 다운로드할 수 있습니다.

이동통신사 앱이 downloadSubscription을 호출하여 프로필을 다운로드할 때 호출은 앱이 프로필의 이동통신사 권한 규칙을 인코딩하는 BF76 메타데이터 태그를 통해 프로필을 관리할 수 있도록 합니다. 프로필에 BF76 태그가 없거나 BF76 태그가 호출하는 이동통신사 앱의 서명과 일치하지 않으면 다운로드가 거부됩니다.

아래 섹션에서는 활성화 코드를 사용하여 이동통신사 앱을 통해 eSIM을 활성화하는 방법을 설명합니다.

활성화 코드를 사용하여 eSIM 활성화

활성화 코드를 사용하여 eSIM 프로필을 활성화할 때 LPA는 이동통신사 앱에서 활성화 코드를 가져오고 프로필을 다운로드합니다. 이 흐름은 LPA에 의해 시작될 수 있으며 LPA는 UI 흐름 전체를 제어할 수 있습니다. 즉, 이동통신사 앱 UI가 표시되지 않습니다. 이 접근법은 BF76 태그 확인을 우회하므로 네트워크 운영자는 eSIM 프로필 다운로드 및 오류 처리를 포함하여 eSIM 활성화 UI 흐름 전체를 구현할 필요가 없습니다.

이동통신사 eUICC 프로비저닝 서비스 정의

LPA 및 이동통신사 앱은 두 가지 AIDL 인터페이스, 즉 ICarrierEuiccProvisioningService  IGetActivationCodeCallback을 통해 통신합니다. 이동통신사 앱은 ICarrierEuiccProvisioningService 인터페이스를 구현하여 매니페스트 선언에 노출해야 합니다. LPA는 ICarrierEuiccProvisioningService에 결합하고 IGetActivationCodeCallback을 구현해야 합니다. AIDL 인터페이스를 구현하고 노출하는 방법에 관한 자세한 내용은 정의 및 AIDL 인터페이스를 참고하세요.

AIDL 인터페이스를 정의하려면 LPA와 이동통신사 앱 모두를 대상으로 다음 AIDL 파일을 만드세요.

  • ICarrierEuiccProvisioningService.aidl
    package android.service.euicc;
    
    import android.service.euicc.IGetActivationCodeCallback;
    
    oneway interface ICarrierEuiccProvisioningService {
        // The method to get the activation code from the carrier app. The caller needs to pass in
        // the implementation of IGetActivationCodeCallback as the parameter.
        void getActivationCode(in IGetActivationCodeCallback callback);
    
        // The method to get the activation code from the carrier app. The caller needs to pass in
        // the activation code string as the first parameter and the implementation of
        // IGetActivationCodeCallback as the second parameter. This method provides the carrier
        // app the device EID which allows a carrier to pre-bind a profile to the device's EID before
        // the download process begins.
        void getActivationCodeForEid(in String eid, in IGetActivationCodeCallback callback);
    }
    
    
  •  
  • IGetActivationCodeCallback.aidl
    package android.service.euicc;
    
    oneway interface IGetActivationCodeCallback {
        // The call back method needs to be called when the carrier app gets the activation
        // code successfully. The caller needs to pass in the activation code string as the
        // parameter.
        void onSuccess(String activationCode);
    
        // The call back method needs to be called when the carrier app failed to get the
        // activation code.
        void onFailure();
    }
    
  •  

LPA 구현 예

이동통신사 앱의 ICarrierEuiccProvisioningService 구현에 결합하려면 LPA가 ICarrierEuiccProvisioningService.aidl과 IGetActivationCodeCallback.aidl을 모두 프로젝트에 복사하고 ServiceConnection을 구현해야 합니다.

 
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    mCarrierProvisioningService = ICarrierEuiccProvisioningService.Stub.asInterface(iBinder);
}

이동통신사 앱의 ICarrierEuiccProvisioningService 구현에 결합한 후 LPA는 getActivationCode 또는 getActivationCodeForEid를 호출하여 IGetActivationCodeCallback 스터브 클래스의 구현을 전달함으로써 이동통신사 앱에서 활성화 코드를 가져옵니다.

getActivationCode와 getActivationCodeForEid의 차이점은 바로 getActivationCodeForEid는 다운로드 프로세스가 시작되기 전에 이동통신사가 프로필을 기기의 EID에 미리 결합할 수 있도록 한다는 것입니다.

 
void getActivationCodeFromCarrierApp() {
    IGetActivationCodeCallback.Stub callback =
            new IGetActivationCodeCallback.Stub() {
                @Override
                public void onSuccess(String activationCode) throws RemoteException {
                    // Handle the case LPA success to get activation code from a carrier app.
                }

                @Override
                public void onFailure() throws RemoteException {
                    // Handle the case LPA failed to get activation code from a carrier app.
                }
            };

    try {
        mCarrierProvisioningService.getActivationCode(callback);
    } catch (RemoteException e) {
        // Handle Remote Exception
    }
}

이동통신사 앱 구현 예

LPA가 이동통신사 앱에 결합하려면 이동통신사 앱이 ICarrierEuiccProvisioningService.aidl과 IGetActivationCodeCallback.aidl을 모두 프로젝트에 복사하고 AndroidManifest.xml 파일에서 ICarrierEuiccProvisioningService 서비스를 선언해야 합니다. 시스템 권한이 있는 앱인 LPA만 서비스에 결합할 수 있도록 하려면 서비스에 android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS 시스템 권한이 있어야 합니다. 또한 서비스에는 android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE 작업이 있는 인텐트 필터가 포함되어 있어야 합니다.

  • AndroidManifest.xml
    <application>
      ...
      <service
          android:name=".CarrierEuiccProvisioningService"
          android:exported="true"
          android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS">
        <intent-filter>
          <action android:name="android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"/>
        </intent-filter>
      </service>
      ...
    </application>
    
  •  

AIDL 이동통신사 앱 서비스를 구현하려면 서비스를 생성하고 Stub 클래스를 확장한 후 getActivationCode  getActivationCodeForEid 메서드를 구현합니다. 그러면 LPA는 두 메서드 중 하나를 호출하여 프로필 활성화 코드를 가져올 수 있습니다. 이동통신사의 서버에서 코드를 성공적으로 가져왔다면 이동통신사 앱은 활성화 코드와 함께 IGetActivationCodeCallback#onSuccess를 호출하여 응답해야 합니다. 코드를 가져오지 못했다면 이동통신사 앱은 IGetActivationCodeCallback#onFailure로 응답해야 합니다.

  • CarrierEuiccProvisioningService.java
    import android.service.euicc.ICarrierEuiccProvisioningService;
    import android.service.euicc.ICarrierEuiccProvisioningService.Stub;
    import android.service.euicc.IGetActivationCodeCallback;
    
    public class CarrierEuiccProvisioningService extends Service {
        private final ICarrierEuiccProvisioningService.Stub binder =
            new Stub() {
              @Override
              public void getActivationCode(IGetActivationCodeCallback callback) throws RemoteException {
                String activationCode = // do whatever work necessary to get an activation code (HTTP requests to carrier server, fetch from storage, etc.)
                callback.onSuccess(activationCode);
              }
    
              @Override
              public void getActivationCodeForEid(String eid, IGetActivationCodeCallback callback) throws RemoteException {
                String activationCode = // do whatever work necessary (HTTP requests, fetch from storage, etc.)
                callback.onSuccess(activationCode);
              }
          }
    }
    
  •  

LPA 활성화 흐름에서 이동통신사 앱 UI 시작

Android 11 이상을 실행하는 기기에서는 LPA가 이동통신사 앱의 UI를 시작할 수 있습니다. 이 기능은 이동통신사 앱이 LPA에 활성화 코드를 제공하기 전에 사용자의 추가 정보가 필요할 때 유용합니다. 예를 들어 이동통신사는 사용자가 로그인하여 전화번호를 활성화하거나 다른 포팅 서비스를 실행하도록 해야 할 수 있습니다.

다음은 LPA에서 이동통신사 앱 UI를 시작하는 프로세스입니다.

  1. LPA는 android.service.euicc.action.START_CARRIER_ACTIVATION 인텐트를 작업이 포함된 이동통신사 앱 패키지로 전송하여 이동통신사 앱의 활성화 흐름을 시작합니다. (LPA 이외의 앱에서 인텐트를 수신하지 않도록 하려면 이동통신사 앱 수신기가 android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"로 매니페스트 선언에서 보호되어야 합니다.)
    String packageName = // The carrier app's package name
    
    Intent carrierAppIntent =
        new Intent(“android.service.euicc.action.START_CARRIER_ACTIVATION”)
            .setPackage(packageName);
    
    ResolveInfo activity =
        context.getPackageManager().resolveActivity(carrierAppIntent, 0);
    
    carrierAppIntent
        .setClassName(activity.activityInfo.packageName, activity.activityInfo.name);
    
    startActivityForResult(carrierAppIntent, requestCode);
    
  2.  
  3. 이동통신사 앱은 자체 UI를 사용하여 작업을 실행합니다. 예를 들어 사용자 로그인 또는 이동통신사의 백엔드로 HTTP 요청 전송을 실행합니다.
  4. 이동통신사 앱은 setResult(int, Intent)  finish()를 호출하여 LPA에 응답합니다.
    1. 이동통신사 앱이 RESULT_OK로 응답하면 LPA는 활성화 흐름을 계속합니다. 이동통신사 앱은 LPA를 통해 이동통신사 앱의 서비스를 결합하는 대신 사용자가 QR 코드를 스캔해야 한다고 판단하면 RESULT_OK와 함께 setResult(int, Intent)를 사용하고 true로 설정된 부울 추가 항목 android.telephony.euicc.extra.USE_QR_SCANNER가 포함된 Intent 인스턴스를 사용하여 LPA에 응답합니다. 그러면 LPA는 추가 항목을 확인하며 이동통신사 앱의 ICarrierEuiccProvisioningService 구현을 결합하는 대신 QR 스캐너를 시작합니다.
    2. 이동통신사 앱이 비정상 종료되거나 RESULT_CANCELED(기본 응답 코드)로 응답하면 LPA가 eSIM 활성화 흐름을 취소합니다.
    3. 이동통신사 앱이 RESULT_OK 또는 RESULT_CANCELED 이외의 다른 코드로 응답하면 LPA는 이를 오류로 처리합니다.
    보안상의 이유로 LPA는 비 LPA 호출자가 이동통신사 앱에서 활성화 코드를 가져올 수 없도록 결과 인텐트에 제공된 활성화 코드를 직접 받아서는 안 됩니다.

이동통신사 앱에서 LPA 활성화 흐름 시작

Android 11부터는 이동통신사 앱이 eUICC API를 사용하여 eSIM 활성화를 위한 LUI를 시작할 수 있습니다. 이 메서드는 LPA의 eSIM 활성화 흐름 UI를 표시하여 eSIM 프로필을 활성화합니다. 그러면 LPA는 eSIM 프로필 활성화가 완료될 때 브로드캐스트를 전송합니다.

  1. LPA는 android.service.euicc.action.START_EUICC_ACTIVATION 작업과 함께 인텐트 필터가 포함된 활동을 선언해야 합니다. 기기에 여러 구현이 있다면 인텐트 필터의 우선순위를 0이 아닌 값으로 설정해야 합니다. 예:
    <application>
      ...
    <activity
        android:name=".CarrierAppInitActivity"
        android:exported="true">
    
        <intent-filter android:priority="100">
            <action android:name="android.service.euicc.action.START_EUICC_ACTIVATION" />
        </intent-filter>
    </activity>
      ...
    </application>
    
  2.  
  3. 이동통신사 앱은 자체 UI를 사용하여 작업을 실행합니다. 예를 들어 사용자 로그인 또는 이동통신사의 백엔드로 HTTP 요청 전송을 실행합니다.
  4. 이 시점에서 이동통신사 앱은 ICarrierEuiccProvisioningService 구현을 통해 활성화 코드를 제공할 준비가 되어 있어야 합니다. 이동통신사 앱은 android.telephony.euicc.action.START_EUICC_ACTIVATION 작업과 함께 startActivityForResult(Intent, int)를 호출하여 LPA를 시작합니다. 또한 LPA는 부울 추가 항목 android.telephony.euicc.extra.USE_QR_SCANNER를 확인합니다. 값이 true이면 LPA가 QR 스캐너를 시작하여 사용자가 프로필 QR 코드를 스캔할 수 있도록 합니다.
  5. LPA 측에서 LPA는 이동통신사 앱의 ICarrierEuiccProvisioningService 구현에 결합하여 활성화 코드를 가져오고 해당 프로필을 다운로드합니다. LPA는 로드 화면과 같이 다운로드 과정에서 필요한 모든 UI 요소를 표시합니다.
  6. LPA 활성화 흐름이 완료되면 LPA는 이동통신사 앱이 onActivityResult(int, int, Intent)에서 처리하는 결과 코드로 이동통신사 앱에 응답합니다.
    1. LPA는 새 eSIM 프로필을 성공적으로 다운로드하면 RESULT_OK로 응답합니다.
    2. 사용자가 LPA에서 eSIM 프로필 활성화를 취소하면 LPA는 RESULT_CANCELED로 응답합니다.
    3. LPA가 RESULT_OK 또는 RESULT_CANCELED 이외의 다른 코드로 응답하면 이동통신사 앱은 이를 오류로 처리합니다.
    보안상의 이유로 LPA는 비 LPA 호출자가 이동통신사 앱에서 활성화 코드를 가져올 수 없도록 제공된 인텐트에서 활성화 코드를 직접 받지 않습니다.

여러 eSIM 지원

참고: Android 13 이상을 실행하는 기기에서는 사용 설정된 프로필을 여러 개 지원할 수 있으므로 단일 eSIM을 사용하면 여러 프로필을 동시에 지원할 수 있습니다. 자세한 내용은 다중 지원 프로필을 참고하세요.

Android 10 이상을 실행하는 기기의 경우 EuiccManager 클래스는 여러 eSIM이 있는 기기를 지원합니다. 플랫폼이 자동으로 EuiccManager 인스턴스를 기본 eUICC와 연결하므로 Android 10으로 업그레이드하는 단일 eSIM을 사용하는 기기는 LPA 구현을 수정할 필요가 없습니다. 기본 eUICC는 Radio HAL 버전이 1.2 이상인 기기의 플랫폼 및 Radio HAL 버전이 1.2 미만인 기기의 LPA에 의해 결정됩니다.

요구사항

여러 eSIM을 지원하려면 기기에 2개 이상의 eUICC가 있어야 합니다. 이 eUICC는 내장 eUICC이거나 탈착식 eUICC를 삽입할 수 있는 물리적 SIM 슬롯일 수 있습니다.

여러 개의 eSIM을 지원하려면 Radio HAL 버전이 1.2 이상이어야 합니다. Radio HAL 버전 1.4 및 RadioConfig HAL 버전 1.2를 사용하는 것이 좋습니다.

구현

여러 eSIM(탈착식 eUICC 또는 프로그래밍 가능한 SIM 포함)을 지원하려면 LPA는 호출자가 제공한 카드 ID에 대응하는 슬롯 ID를 수신하는 EuiccService를 구현해야 합니다.

arrays.xml에 지정된 non_removable_euicc_slots 리소스는 기기에 내장된 eUICC의 슬롯 ID를 나타내는 정수 배열입니다. 이 리소스를 지정하여 삽입된 eUICC가 탈착 가능한지 아닌지를 플랫폼에서 판단하도록 해야 합니다.

여러 eSIM이 있는 기기의 이동통신사 앱

여러 eSIM이 있는 기기의 이동통신사 앱을 만들 때는 EuiccManager의 createForCardId 메서드를 사용하여 지정된 카드 ID에 고정된 EuiccManager 객체를 만듭니다. 카드 ID는 기기의 UICC 또는 eUICC를 고유하게 식별하는 정수 값입니다.

기기의 기본 eUICC 카드 ID를 가져오려면 TelephonyManager의 getCardIdForDefaultEuicc 메서드를 사용합니다. 이 메서드는 Radio HAL 버전이 1.2 미만이면 UNSUPPORTED_CARD_ID를 반환하고 기기에서 eUICC를 읽지 않았다면 UNINITIALIZED_CARD_ID를 반환합니다.

또한 카드 ID는 TelephonyManager의 getUiccCardsInfo getUiccSlotsInfo(시스템 API) 및 SubscriptionInfo의 getCardId를 통해 얻을 수 있습니다.

EuiccManager 객체가 특정 카드 ID로 인스턴스화됐다면 모든 작업이 해당 카드 ID를 사용하는 eUICC로 전달됩니다. eUICC에 연결할 수 없다면(예: eUICC가 사용 중지되거나 삭제된 경우) EuiccManager는 더 이상 작동하지 않습니다.

다음 코드 샘플을 사용하여 이동통신사 앱을 만들 수 있습니다.

예 1: 활성 구독 가져오기 및 EuiccManager 인스턴스화하기

 
// Get the active subscription and instantiate an EuiccManager for the eUICC which holds
// that subscription
SubscriptionManager subMan = (SubscriptionManager)
        mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
int cardId = subMan.getActiveSubscriptionInfo().getCardId();
EuiccManager euiccMan = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE)
            .createForCardId(cardId);

예 2: UICC를 반복하고 탈착식 eUICC용 EuiccManager 인스턴스화하기

 
// On a device with a built-in eUICC and a removable eUICC, iterate through the UICC cards
// to instantiate an EuiccManager associated with a removable eUICC
TelephonyManager telMan = (TelephonyManager)
        mContext.getSystemService(Context.TELEPHONY_SERVICE);
List<UiccCardInfo> infos = telMan.getUiccCardsInfo();
int removableCardId = -1; // valid cardIds are 0 or greater
for (UiccCardInfo info : infos) {
    if (info.isRemovable()) {
        removableCardId = info.getCardId();
        break;
    }
}
if (removableCardId != -1) {
    EuiccManager euiccMan = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE)
            .createForCardId(removableCardId);
}

유효성 검사

AOSP는 LPA 구현과 함께 제공되지 않으며 모든 Android 빌드에서 LPA를 사용할 수 있는 것은 아닙니다(일부 스마트폰은 eSIM을 지원하지 않음). 따라서 엔드 투 엔드 CTS 테스트 사례는 없습니다. 하지만, 노출된 eUICC API가 Android 빌드에서 유효한지 확인하기 위해 AOSP에서 기본 테스트 사례를 사용할 수 있습니다.

빌드가 다음의 CTS 테스트 사례를 통과하는지 확인해야 합니다(공개 API의 경우). /platform/cts/tests/tests/telephony/current/src/android/telephony/euicc/cts

이동통신사 앱을 구현하는 이동통신사는 기본적인 사내 품질보증 주기를 거쳐 구현된 모든 기능이 예상대로 작동하고 있는지 확인해야 합니다. 최소한 이동통신사 앱은 동일한 운영자가 소유한 모든 구독 프로필을 나열하고 프로필을 다운로드 및 설치할 수 있어야 하며 프로필에서 서비스를 활성화하고 프로필을 전환 및 삭제할 수 있어야 합니다.

자체 LPA를 만들고 있다면 더욱 엄격한 테스트를 거쳐야 합니다. 모뎀 공급업체, eUICC 칩 또는 eSIM OS 공급업체, SM-DP+ 공급업체 및 이동통신사와 협업하여 문제를 해결하고 RSP 아키텍처 내에서 LPA의 상호 운용성을 보장해야 합니다. 충분한 양의 수동 테스트는 필수입니다. 최상의 테스트 범위를 얻으려면 GSMA SGP.23 RSP 테스트 계획을 따라야 합니다.

 

이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.

최종 업데이트: 2023-04-27(UTC)

블로그 이미지

wtdsoul

,

https://www.jacobbaek.com/1287

 

Teleport

Teleport란 Teleport는 CA(Certificate Authority) 와 infrastructure에 접근하는 Plane을 만들어주고 접근을 수행할수 있도록 해준다. Teleport는 다음과 같은 일반적인 기능을 제공한다. SSO설정과 SSH Server, Kubernetes,

www.jacobbaek.com

 

Teleport란

Teleport는 CA(Certificate Authority) 와 infrastructure에 접근하는 Plane을 만들어주고 접근을 수행할수 있도록 해준다.
Teleport는 다음과 같은 일반적인 기능을 제공한다.

  • SSO설정과 SSH Server, Kubernetes, Database들, Web App들에 접근할 하나의 서버를 생성
  • Infrastructure에 대한 접근 정책을 다양한 Programming Language로 정의
  • 모든환경의 세션에 대한 Record 및 공유가 가능

Teleport 상세 기능

  • SSH를 이용한 서버 접근
  • application 접근
  • Kubernetes 접근
  • DB 접근
  • Session 기록후 재생가능
  • Session 공유 및 join 가능
  • 웹 UI 상의 terminal 기능 제공

https://goteleport.com/docs/

Teleport 구성

구성요소는 다음과 같은 역할로 나누어진다.

위 3가지 구성요소는 single binary로 제공되어 All-in-one으로도 설치가 가능하다.
(또한 분리 구성도 가능하다.)

Teleport 동작방식

SSH 접근에 대해 먼저 설명하자면 Teleport Auth 서비스에서 자체 CA를 가지고 인증서를 발급 및 폐기를 진행한다.
SSH접근을 예로 들면 위 CA 인증서를 기반으로 OpenSSH certificates 방식으로 인증을 수행하게 된다.

실제 동작은 다음과 같이 수행되어진다.

SSH Certificate 를 사용한 인증방식
아래는 SSH Certificate 의 동작방식에 대한 내용이 나와있는 링크들이다.

Teleport 설치

다음과 같은 방식으로 설치가 가능하다.

설치전 알아두어야할 정보들
아래와 같은 3022~3027,3080 등이 사용되어진다.
하여 방화벽이 서버에 존재시 해당 포트를 open 하는 작업이 필요하다.

rpm 방식으로 teleport server(proxy/auth) 설치

[root@rocky8-server ~]# dnf install https://get.gravitational.com/teleport-6.2.8-1.x86_64.rpm

자동으로 config yaml 파일이 생성되지 않는다. 하여 다음링크에서 yaml 파일 내용을 복사해온다.

혹은 아래 명령을 통해 teleport.yaml 파일을 생성할 수 있다.

teleport configure > /etc/teleport/teleport.yaml

또한 systemd로 자동 등록되지 않기에 이를 추가 해야 한다.

참고
root 계정을 사용하지 말고 그외 계정을 생성해서 사용하기를 권장한다.
https://goteleport.com/docs/admin-guide/

systemd service로 생성후 daemon-reload 및 start를 수행하면 동작이 된다.

teleport.yaml 파일을 원하는 방식으로 동작되도록 설정한다.

아래 ansible-playbook으로 server(proxy,auth) / node에 대한 기본 설치 및 systemd 등록 그리고 teleport.yaml을 생성하는 것을 만들어놓았으니 이를 참고하면 좀더 쉽게 설치/설정을 할수 있을거라 보인다.

node는 아래 node 추가항목을 참고하길 바란다.

teleport 사용

먼저 사용자 생성이 중요하다.

사용자 로그인

login은 tctl을 통해 먼저 계정 생성을 한후 접근이 가능하다.

아래링크를 참고하여 사용자를 생성하자.

실제 tctl 명령어를 이용하여야 하고

[root@rocky8-server etc]# tctl users add dubaek
NOTE: Teleport 6.0 added RBAC in Open Source edition.

In the future, please create a role and use a new format with --roles flag:

$ tctl users add "dubaek" --roles=[add your role here]

We will deprecate the old format in the next release of Teleport.
Meanwhile we are going to assign user "dubaek" to role "admin" created during migration.

User "dubaek" has been created but requires a password. Share this URL with the user to complete user setup, link is valid for 1h:
https://rocky8-server:3080/web/invite/cdc955154e112eeb1ce23884165545e7

NOTE: Make sure rocky8-server:3080 points at a Teleport proxy which users can access.

언급된 주소(https://rocky8-server:3080/web/invite/xxxxx)로%EB%A1%9C) 연결하여 아래와 같은 password 변경을 수행한다.
(이때 google OTP를 이용하여 token을 발행해야 한다.)

 

 

실제 생성된 계정에는 앞서 생성시 추가했던 root,dubaek,ubuntu 등이 logins에 추가 되어 있다.

[root@rocky8-server log]# tctl get user
kind: user
metadata:
  id: 1627887255392573657
  name: dubaek
spec:
  created_by:
    time: "2021-08-02T06:54:15.392197653Z"
    user:
      name: 7c3e1de2-e8cc-407e-a046-774e8ce2f26f.rocky8-server
  expires: "0001-01-01T00:00:00Z"
  roles:
  - admin
  status:
    is_locked: false
    lock_expires: "0001-01-01T00:00:00Z"
    locked_time: "0001-01-01T00:00:00Z"
  traits:
    kubernetes_groups:
    - ""
    kubernetes_users:
    - dubaek
    logins:
    - dubaek
    - root
    - ubuntu
version: v2

만약 user의 logins 에 다른 계정을 추가하고자 할 경우 다음과 같은 방식으로 수정 및 업데이트가 가능하다.

[root@rocky8-server log]# tctl get user/dubaek > dubaek.yaml
[root@rocky8-server log]# vim dubaek.yaml
[root@rocky8-server log]# tctl create -f dubaek.yaml
user "dubaek" has been updated

node 추가

node 추가에는 invite 하는 방식과 token을 발행하여 join 하는 방식 두가지가 있다.

invite 방식의 node 추가
[root@rocky8-server etc]# tctl nodes add
The invite token: 217d9e2757b28c8c74e3efa808f72977
This token will expire in 30 minutes

Run this on the new node to join the cluster:

> teleport start \
   --roles=node \
   --token=217d9e2757b28c8c74e3efa808f72977 \
   --ca-pin=sha256:817e4eb920cbfe46c0549623e871b4b1e9805dc18cbd5db96514fbe16ea5746f \
   --auth-server=10.0.2.15:3025

Please note:

  - This invitation token will expire in 30 minutes
  - 10.0.2.15:3025 must be reachable from the new node
token발행과 cluster join을 통한 node 추가

아래 링크를 참고하여 노드 추가가 가능하다.

역할별 접근 권한 관리

다음과 같이 4개의 role이 기본으로 제공된다.

[root@rocky8-server log]# tctl get role | grep -E "^  name"
  name: access
  name: admin
  name: auditor
  name: editor

서버별 접근을 제어하기 위해서는 role내에 label을 통한 제어를 수행해야 한다.

먼저 role template을 다음 명령어로 생성한다.(기존 양식을 가져와 수정하여 적용하는 방식이다.)

[root@rocky8-server ~]# tctl get role/access > dev-role.yaml
[root@rocky8-server ~]# vim dev-role.yaml

여기서 metadata id는 제거하고 name은 생성하려는 role name으로 지정한다.
또한 아래와 같이 spec내에 접근을 하고자 하는 teleport node의 label을 등록해주면
지정된 label을 보유한 node들만 해당 role을 할당받은 사용자들에게 출력된다.

spec:
  allow:
    ...
    node_labels:
      'env': 'prd'

수정이 완료되면 다음과 같이 role을 생성한다.

[root@rocky8-server ~]# tctl create -f dev-role.yaml
role 'dev' has been created
[root@rocky8-server ~]# tctl get role | grep "^  name"
  name: access
  name: admin
  name: auditor
  name: dev
  name: editor

dev role이 생성된것을 확인할 수 있다.

이후 해당 role로 접근을 시도해보면 앞서 지정한 label에 매핑되는 node들이 출력되고 접근이 될수 있게 된다.

jacob@dubaekPC:~$ tsh login --insecure --proxy=rocky8-server --user=testuser
Enter password for Teleport user testuser:
Enter your OTP token:
194164
WARNING: You are using insecure connection to SSH proxy https://rocky8-server:3080
> Profile URL:        https://rocky8-server:3080
  Logged in as:       testuser
  Cluster:            rocky8-server
  Roles:              dev
  Logins:             -teleport-nologin-76b845e0-f75e-4e51-8176-a42bb600f277
  Kubernetes:         disabled
  Valid until:        2021-08-04 12:30:46 +0900 KST [valid for 1h0m0s]
  Extensions:         permit-agent-forwarding, permit-port-forwarding, permit-pty
jacob@dubaekPC:~$ tsh ls
Node Name Address            Labels
--------- ------------------ -------
ubuntu20  192.168.56.20:3022 env=prd

기능 확인

SSH Proxy

아래와 같이 tsh ssh 명령을 통해 등록된 서버에 모두 접근이 가능하다.

jacob@dubaekPC:~/temp/teleport/examples$ tsh ls
Node Name     Address            Labels
------------- ------------------ -----------------------------------
rocky8-server 127.0.0.1:3022     env=example, hostname=rocky8-server
ubuntu20      192.168.56.20:3022

jacob@dubaekPC:~/temp/teleport/examples$ tsh ssh root@ubuntu20
root@ubuntu20:~#
root@ubuntu20:~# exit
logout
the connection was closed on the remote side on  02 Aug 21 16:31 KST
jacob@dubaekPC:~/temp/teleport/examples$ tsh ssh ubuntu@ubuntu20
ubuntu@ubuntu20:~$
ubuntu@ubuntu20:~$ exit
logout
the connection was closed on the remote side on  02 Aug 21 16:31 KST
jacob@dubaekPC:~/temp/teleport/examples$ tsh ssh root@rocky8-server
[root@rocky8-server ~]#

참고
아래와 같이 x509 인증서 에러가 나오는경우는 로그인과정상에 인증실패가 된것이라 보면 된다.

jacob@dubaekPC:~$ tsh ssh -p 22 root@ubuntu20
ERROR: Get "https://rocky8-server:3080/webapi/ping": x509: certificate signed by unknown authority

Access Kubernetes

WIP..

Access Application

WIP..

Access Database

WIP..

Session Recording and joining

아래와 같이 UI를 통해 recording 된 리스트를 확인할 수 있고 play 버튼을 클릭하여 실제 영상으로 재생해볼수 있다.

 

 

추가로

 

 

Agentless 방식의 SSH

Bastion Host 형태로 제공되는 Teleport를 사용하는 경우 Teleport Node에서 동작되는 Teleport Agent가 종료된 경우
접근이 불가능한 상황이 초래될수 있다. 이러한 경우 이를 우회하여 접근할 수 있는 방법에 대하여 알아보도록 하자.

아래 링크에서는 agentless 방식으로 접근이 가능하다고 한다.

실제로는 openssh 방식을 통한 접근을 사용하게 되면 Teleport Agent가 예기치 않게 종료되어도 접근을 수행할 수 있다.

Teleport Server(Proxy/Auth)에서 아래 명령을 입력하여 CA를 export 하고

sudo tctl auth export --type=user > teleport_user_ca.pub

이를 각 Teleport Node의 /etc/ssh 디렉토리로 복사한뒤 아래 설정을 넣어준후 sshd를 재시작하게 되면 Teleport Server의 CA를 인지하고 인증이 이루어질수 있다.

TrustedUserCAKeys /etc/ssh/teleport_user_ca.pub

certificate rotate

일반적으로 아래와 같은 rotated 상태이다.

[root@rocky8-server ~]# tctl status
Cluster  rocky8-server
Version  6.2.8
Host CA  rotated Aug  4 10:34:44 UTC
User CA  rotated Aug  4 10:34:44 UTC
Jwt CA   rotated Aug  4 10:34:44 UTC
CA pin   sha256:817e4eb920cbfe46c0549623e871b4b1e9805dc18cbd5db96514fbe16ea5746f

만약 initialize나 다른 상태이며 아래와 같은 명령으로 이를 정상화 시킬수 있다.

[root@rocky8-server ~]# tctl auth rotate --manual --phase=standby
Updated rotation phase to "standby". To check status use 'tctl status'

Audit

Audit log는 다음 3가지 방식으로 제공이 된다.

  1. SSH Event : login 성공/실패
  2. Recorded Sessions : SSH shell session에 대한 record 된 영상
  3. Enhanced Session Recording : BPF 기반으로 사용했던 명령들을 json 형태로 남기고 이를 encoding 하여 만약의 해킹과 같은 위험 상황에 손쉽게 확인되지 않도록 함.

2번 Recorded Session의 경우 영상 기반으로 재생이되는 방식으로 아래와 같이 tsh 명령을 이용해 해당 영상을 재생하거나 UI를 통해 확인해볼수 있다.

tsh play [session_record_id]

3번의 경우 enhanced recording session 이라는 기능이며 최신버전에서 제공하고 있으며 커널버전이 5.8이상으로 최신 OS사용에 대한 제한이 있다. 다만 해당 기능을 통해 SSH로 연결하여 수행했던 history를 좀더 편리하게 확인할 수 있다.

아무래도 영상 기반의 파일이라 향후 추적을 위한 검색기능을 구현해야할 경우 이를 활용하기는 어려워 보인다.
하여 text 기반으로 남겨지는 session recording 데이터가 없는지 확인해본 결과 chunks 파일을 통해 확인할 수 있었다.
다만, 해당 chunks 파일은 tsh play를 한번이라도 한 경우 남게 되어 매번 이를 수행하는 cron job 같은게 필요할 것으로 보인다.

root@service1:/var/lib/teleport/log/playbacks/sessions# tree
.
└── default
    ├── 820bee91-e7e9-4627-8af6-1213be477906-0.chunks
    ├── 820bee91-e7e9-4627-8af6-1213be477906-0.chunks.gz
    ├── 820bee91-e7e9-4627-8af6-1213be477906-0.events.gz
    ├── 820bee91-e7e9-4627-8af6-1213be477906.index
    ├── 820bee91-e7e9-4627-8af6-1213be477906.tar
    ├── bb03fa95-cd98-4745-af7f-b054a8898583-0.chunks
    ├── bb03fa95-cd98-4745-af7f-b054a8898583-0.chunks.gz
    ├── bb03fa95-cd98-4745-af7f-b054a8898583-0.events.gz
    ├── bb03fa95-cd98-4745-af7f-b054a8898583.index
    ├── bb03fa95-cd98-4745-af7f-b054a8898583.tar
    ├── e3a74e05-8192-440a-8be6-eda12d16dfcc-0.chunks
    ├── e3a74e05-8192-440a-8be6-eda12d16dfcc-0.chunks.gz
    ├── e3a74e05-8192-440a-8be6-eda12d16dfcc-0.events.gz
    ├── e3a74e05-8192-440a-8be6-eda12d16dfcc.index
    └── e3a74e05-8192-440a-8be6-eda12d16dfcc.tar

1 directory, 15 files

tsh play 명령 (혹은 UI상에서 play 버튼을 클릭한 경우) 수행후 위와 같은 파일이 남게 되고
여기서 *.chunks 파일을 확인해보면 다음과 같은 history를 확인할 수 있다.

root@service1:/var/lib/teleport/log/playbacks/sessions# cat default/e3a74e05-8192-440a-8be6-eda12d16dfcc-0.chunks
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@service2:~$ ls
ubuntu@service2:~$ pwd
/home/ubuntu
ubuntu@service2:~$ exit
logout

위 audit log들에 대한 설정은 아래 링크에서 session_recording 를 검색하여 해당 설정에 대한 설명을 읽어보길 바란다.

Pros/Cons

아래 blog에 teleport에 대한 장/단점 요약이 잘되어 있다.

참고사이트

출처: https://www.jacobbaek.com/1287 [Jacob Baek's home:티스토리]

블로그 이미지

wtdsoul

,

 

https://labs.detectify.com/writeups/a-deep-dive-into-aws-s3-access-controls-taking-full-control-over-your-assets/

 

A deep dive into AWS S3 access controls - Labs Detectify

Original research from Frans Rosen on vulnerabilities in AWS S3 bucket access controls and how to do set it up properly and monitor security.

labs.detectify.com

 

TL;DR: Setting up access control of AWS S3 consists of multiple levels, each with its own unique risk of misconfiguration. We will go through the specifics of each level and identify the dangerous cases where weak ACLs can create vulnerable configurations impacting the owner of the S3-bucket and/or through third party assets used by a lot of companies. We also show how to do it properly and how to monitor for these sorts of issues.

A simplified version of this write-up is available on the Detectify blog.

Quick background

Amazon Web Services (AWS) provides a service called Simple Storage Service (S3) which exposes a storage container interface. The storage container is called a “bucket” and the files inside the bucket are called “objects”. S3 provides an unlimited storage for each bucket and owners can use them to serve files. Files can be served either privately (via signed URLs) or publicly via an appropriately configured ACL (Access Control List) or ACP (Access Control Policy).

AWS also provides a (CDN) service called CloudFront which is often configured to quickly serve S3 hosted files/objects from an optimized CloudFront server as close as possible to the user who is requesting the file.

Introduction

Recently, a few blog posts have mentioned scenarios where the misconfiguration of a S3 bucket may expose sensitive data as well as explaining that the S3 access control lists (ACL) are quite different to the regular user permission setup in AWS which is called Identify Access Management (IAM).

However, we decided to approach this from a different angle. By identifying a number of different misconfigurations we discovered that we could suddenly control, monitor and break high end websites due to weak configurations of the bucket and object ACLs.

Disclaimer

All instances disclosed below were reported to the affected parties using responsible disclosure policies. In some of the cases, third party companies were involved and we got assistance from the companies affected to contact the vulnerable party.

We do not recommend testing any of the vulnerable scenarios below without prior approval. This is especially important in scenarios where the only way to identify the vulnerability was to actually override files and configurations. We did, however, identify one method to detect one of the vulnerable setups without actually modifying the data. You should still make sure you’re not affecting any party that has not given you written approval.

Technical details

The different misconfigurations and the impact of each depend on the following criteria:

  • Who owns the S3 bucket
  • What domain is being used to serve the files from the bucket
  • What type of files are inside the bucket

We will try to go through all different cases below and explain when they can be created with a vulnerable misconfiguration.

Identification of buckets

To start off, we need to be able to identify buckets owned by or used by the company. We need the specific bucket’s name to make signed requests to the bucket.

Identifying a bucket depends on the setup and also how the bucket is being reached: The request can go directly to S3, to CloudFront (or any other CDN proxy serving files from the bucket), to the S3 “Static Website” option, or more.

Some methods to identify S3-buckets are:

  • Look at the HTTP-response for a Server-header which says AmazonS3.
  • Look at a random URL that doesn’t exist and see if it gives you a S3-404, either with “Static Website enabled” or not, containing Access Denied or NoSuchKey: 
  •  
  • The DNS-entry of the domain might reveal the bucket-name directly if the host points directly to S3.
  • Try accessing the root-URL. If index-listing is enabled (public READ on the Bucket ACL) you will be able to see the bucket-name defined in <Name>-element.

We have identified multiple ways to make an S3-bucket actually reveal itself independent of proxies in front of it. We have notified AWS about these methods and chosen not mention them above.

If you do find a domain that is pointing to a bucket, but cannot get the bucket name, try the actual fully qualified domain name (FQDN) as the bucket name, this is a common setup, having the bucket named as the domain that is pointing to it.

If this doesn’t work, try to:

  • Google the domain and see if any history of it exposes the bucket name.
  • Look at response headers of objects in the bucket to see if they have meta data that reveals the bucket name.
  • Look at the content and see if it refers to any bucket. We’ve seen instances where assets are tagged with the bucket name and a date when they were deployed.
  • Brute-force. Be nice here, don’t shoot thousands of requests against S3 just to find a bucket. Try be clever depending on the name of the domain pointing to it and the actual reason why the bucket exists. If the bucket contains audio files for ACME on the domain media.acme.edu, try media.acme.edu, acme-edu-media, acme-audio or acme-media.

If the response on $bucket.s3.amazonaws.com shows NoSuchBucket you know the bucket doesn’t exist. An existing bucket will either give you ListBucketResult or AccessDenied.

(You might also stumble upon AllAccessDisabled, these buckets are completely dead).

Remember, just because a bucket is named as the company or similar, that doesn’t mean it is owned by that company. Try find references directly from the company to the bucket to confirm it is indeed owned by the specific company.

Permission/predefined groups

First, we will explore the different options that can be used for giving access to a requester of a bucket and the objects within:

ID / emailAddress

You are able to give access to a single user inside AWS using either the AWS user ID or their email address. This makes sense if you want to allow a single user to have specific access to the bucket.

AuthenticatedUsers

This is probably the most misunderstood predefined group in AWS S3’s ACL. Having the ACL set to AuthenticatedUsers basically means “Anyone with a valid set of AWS credentials”. All AWS accounts that can sign a request properly are inside this group. The requester doesn’t need to have any relation at all with the AWS account owning the bucket or the object. Remember that “authenticated” is not the same thing as “authorized”.

This grant is likely the most common reason a bucket is found vulnerable in the first place.

AllUsers

When this grant is set, the requester doesn’t even need to make an authenticated request to read or write any data, anyone can make a PUT request to modify or a GET request to download an object, depending on the policy that is configured.

Policy permissions / ACP (Access Control Policies)

The following policy permissions can be set on the bucket or on objects inside the bucket.

The ACPs on bucket and objects control different parts of S3. AWS has a list showing exactly what each grant does. There are more cases not mentioned below where you can create specific IAM policies for a bucket, called a bucket-policy. Creating a bucket-policy has its own issues, however, we will only cover the standard setup of ACLs set on buckets and objects.

READ

This gives the ability to read the content. If this ACP is set on a bucket, the requester can list the files inside the bucket. If the ACP is set on an object, the content can be retrieved by the requester.

READ will still work on specific objects inside a bucket, even if Object Access READ is not set on the complete bucket.

With the following ACL setup inside AWS S3:

Bucket-ACL: Object-ACL:

We can still read the specific object:

$ aws s3api get-object --bucket test-bucket --key read.txt read.txt
{
    "AcceptRanges": "bytes", 
    "ContentType": "text/plain", 
    "LastModified": "Sun, 09 Jul 2017 21:14:15 GMT", 
    "ContentLength": 43, 
    "ETag": ""1398e667c7ebaa95284d4efa2987c1c0"", 
    "Metadata": {}
}

This means READ can be different for each object, independently of the settings on the bucket.

READ_ACP

This permission gives the ability to read the access control list of the bucket or object. If this is enabled, you can identify vulnerable assets without trying to modify the content or ACP at all.

READ_ACP will still work on specific objects inside a bucket, even if Object Access READ_ACP is not set on the complete bucket.

Bucket-ACL: Object-ACL:
$ aws s3api get-object-acl --bucket test-bucket --key read-acp.txt        
{
    "Owner": {
        "DisplayName": "fransrosen", 
        ...

This means READ_ACP can be different for each object, independently of the settings on the bucket.

WRITE

This permission gives the ability to write content. If the bucket has this enabled for a user or group, that party can upload, modify and create new files.

WRITE will not work on specific objects inside a bucket, if Object Access WRITE is not set on the complete bucket:

Bucket-ACL: Object-ACL:
$ aws s3api put-object --bucket test-bucket --key write.txt --body write.txt

An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

However, if WRITE is set on the bucket, all objects will obey and will not be able to decide individually if they should be writable or not:

Bucket-ACL: Object-ACL:
$ aws s3api put-object --bucket test-bucket --key write.txt --body write.txt
{
    "ETag": ""1398e667c7ebaa95284d4efa2987c1c0""
}

This means, WRITE can be verified on the bucket in two ways, either by uploading a random file, or by modifying an existing one. Modifying an existing file is destructive and should not be done at all. Below we will explain a way to check this without doing a destructive call, by triggering an error in between the access control check and the actual modification of the file.

WRITE_ACP

This permission gives the ability to modify the permission ACL of a bucket or object.

If the bucket has this enabled for a user or a group, that party can modify the ACL of the bucket which is extremely bad. Having WRITE_ACP on a bucket will completely expose it to be controlled by the party having the ACP set, meaning any content of any object can now be controlled by the party. The attacker might not be able to READ every object already in the bucket, but they can still fully modify the existing objects. Also, the initial owner of the S3-bucket will get an Access Denied in the new AWS S3-console when the attacker is claiming ownership of it when removing the READ-access on the bucket.

First, no access to READ_ACP or WRITE:

$ aws s3api get-bucket-acl --bucket test-bucket

An error occurred (AccessDenied) when calling the GetBucketAcl operation: Access Denied

$ aws s3api put-object --bucket test-bucket --key write-acp.txt --body write-acp.txt

An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

Then we try to change the bucket ACL:

$ aws s3api put-bucket-acl --bucket test-bucket --grant-full-control emailaddress=frans@example.com && echo "success"
success

The initial owner of the bucket will now see this:


(Being the owner, they will still be able to modify the policy of the bucket, but this is a weird case anyway.)

We can now control everything:

$ aws s3api get-bucket-acl --bucket test-bucket
{
...
    "Grants": [
        {
            "Grantee": {
                "Type": "CanonicalUser", 
                "DisplayName": "frans", 
                "ID": "..."
            }, 
            "Permission": "FULL_CONTROL"

$ aws s3api put-object --bucket test-bucket --key write-acp.txt --body write-acp.txt
{
    "ETag": ""1398e667c7ebaa95284d4efa2987c1c0""
}

A very interesting thing is that WRITE_ACP will actually still work on specific objects inside a bucket even if Object Access WRITE_ACP is not set on the complete bucket:

Bucket-ACL: Object-ACL:
$ aws s3api put-object-acl --bucket test-bucket --key write-acp.txt --grant-write-acp uri=http://acs.amazonaws.com/groups/global/AuthenticatedUsers && echo "success"
success

Also, the opposite of WRITE applies here, having WRITE_ACP on the bucket, doesn’t mean you directly have WRITE_ACP on an object:

Bucket-ACL: Object-ACL:
$ aws s3api put-object-acl --bucket test-bucket --key write-acp.txt --grant-full-control emailaddress=frans@example.com

An error occurred (AccessDenied) when calling the PutObjectAcl operation: Access Denied

However, by performing the following steps when having WRITE_ACP on the bucket you will still gain full access of the content of any object, by replacing the existing object with new content:

  1. Modify the bucket ACL:
    $ aws s3api put-bucket-acl --bucket test-bucket --grant-full-control emailaddress=frans@example.com && echo "success"
    success
  2. Modify the object (This changes you to the owner of the object):
    $ aws s3api put-object --bucket test-bucket --key write-acp.txt --body write-acp.txt
    {
     "ETag": ""1398e667c7ebaa95284d4efa2987c1c0""
    }
  3. Modify ACP of the object again:
    $ aws s3api put-object-acl --bucket test-bucket --key write1.js --grant-full-control emailaddress=frans@example.com && echo "success"
    success

Since WRITE still needs to be set on the bucket, you cannot upgrade a WRITE_ACP on an object to give yourself WRITE on the same object:

$ aws s3api put-object-acl --bucket test-bucket --key write-acp.txt --grant-write-acp uri=http://acs.amazonaws.com/groups/global/AuthenticatedUsers --grant-write uri=http://acs.amazonaws.com/groups/global/AuthenticatedUsers --grant-read-acp uri=http://acs.amazonaws.com/groups/global/AuthenticatedUsers --grant-read uri=http://acs.amazonaws.com/groups/global/AuthenticatedUsers && echo "success"
success
Bucket-ACL: Object-ACL:

This will still give you:

$ aws s3api put-object --bucket test-bucket --key write-acp.txt --body write-acp.txt

An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

However, you can still remove all ACPs on the object, making the object completely private, which will stop it being served, giving a 403 Forbidden.

WRITE_ACP can unfortunately only be verified by testing writing a new ACP on a bucket or object. Modifying the existing one is of course destructive and should not be done without approval. We have not found a non-destructive way of testing this ACP.

FULL_CONTROL

This is the policy that combines all other policies. However, WRITE will still not work on an object unless the bucket has it set, even if this permission is set on an object.

Vulnerable scenarios

The following scenarios are cases where the company can be affected.

1. Bucket used on a domain owned by the company

You found a bucket which is served by a subdomain or domain of the company.

You should test for:

  • BUCKET READ
    Listing files in the bucket. Sensitive information might be exposed.
  • BUCKET READ-ACP
    Let’s look at the ACP and see if we can identify the bucket being vulnerable without actually trying anything. If we see that AllUsers or AuthenticatedUsers has WRITE_ACP set, we know we can gain full control over the bucket, without doing anything else.
  • BUCKET WRITE (Simulate using invalid-MD5 hack)
    If we can upload a new file to the bucket. This also tells us we can overwrite any object in the bucket. However, if we want to avoid uploading anything, we can try the following hack, not uploading anything but still see that we are able to do it:
    When making a signed PUT request to a bucket, we have the option to add a Content-MD5 telling AWS the checksum of the content being uploaded. It turns out that this check is happening inside the following flow:
    1. Check that the user has access writing the file.
    2. Check that the MD5-checksum is matching the content.
    3. Upload the file.
    Since the checksum control happens after we know that we have access to the file, but before actually modifying it, we do not need to write to the file to know that we are able to.
    # use this by: ./put-simulate.sh test-bucket/write.txt 
    AWS_ACCESS_KEY_ID="***"
    AWS_SECRET_ACCESS_KEY="***"
    AWS_S3_BUCKET="$(echo "$1" | cut -d "/" -f1)"
    AWS_PATH="/$(echo "$1" | cut -d "/" -f2-)"
    date=$(date +"%a, %d %b %Y %T %z")
    acl="x-amz-acl:private"
    content_type='application/octet-stream'
    
    # we create a checksum of the word "yepp", but will upload a file with the content "nope".
    content_md5=$(openssl dgst -md5 -binary <(echo "yepp") | openssl enc -base64)
    
    string="PUTn${content_md5}n${content_type}n${date}n${acl}n/${AWS_S3_BUCKET}${AWS_PATH}"
    signature=$(echo -en "${string}" | openssl sha1 -hmac "${AWS_SECRET_ACCESS_KEY}" -binary | base64)
    echo "PUT to S3 with invalid md5: ${AWS_S3_BUCKET}${AWS_PATH}"
    result=$(curl -s --insecure -X PUT --data "nope" 
    -H "Host: ${AWS_S3_BUCKET}.s3.amazonaws.com" 
    -H "Date: $date" 
    -H "Content-Type: ${content_type}" 
    -H "Content-MD5: ${content_md5}" 
    -H "$acl" 
    -H "Authorization: AWS ${AWS_ACCESS_KEY_ID}:${signature}" 
    "https://${AWS_S3_BUCKET}.s3.amazonaws.com${AWS_PATH}")
    
    if [ "$(echo ${result} | grep 'The Content-MD5 you specified did not match what we received')" != "" ]; then
      echo "SUCCESS: ${AWS_S3_BUCKET}${AWS_PATH}"
      exit 0
    fi
    echo "$result"
    exit 1
    On a bucket we can upload to, this will result in:On a bucket we cannot upload to, this will result in:We will therefore never modify the content, only confirm we can do it. This unfortunately only works on WRITE on objects, not on WRITE_ACP as far as we know.
  • $ ./put-simulate.sh test-secure-bucket/write.txt PUT to S3 with invalid md5: test-secure-bucket/write.txt <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message>
  • $ ./put-simulate.sh test-bucket/write.txt PUT to S3 with invalid md5: test-bucket/write.txt SUCCESS: test-bucket/write.txt
  • The following bash code simulates this scenario:
  • BUCKET WRITE-ACP
    The most dangerous one. Fully upgradable to full access of the bucket. Destructive call. Be careful. The only way to do this one properly is to first figure out how the bucket behaves to not break any current ACP. Remember that you can still have access to WRITE_ACP even though you do not have access to READ_ACP.
    API-documentation reference
  • OBJECT READ
    We can try to read the content of files we are interested in found by BUCKET READ.
  • OBJECT WRITE
    No need to test this one, since BUCKET WRITE decides fully. If BUCKET WRITE gives an error the object will not be writable and if BUCKET WRITE is successful, the object will always be writable.
    However, if the company using the bucket has an application where users can upload files, look at the implementation of how they make the actual file upload to S3. If the company is using a POST Policy upload, specifically look in the policy at the Condition Matching of the $key and the Content-type. Depending on if they use starts-with you might be able to modify the content type to HTML/XML/SVG or similar, or change the location of the file being uploaded.
  • OBJECT WRITE-ACP
    We can try modifying the ACP of the specific object. It will not enable us to modify the content, but only the access control of the file, giving us the ability to stop files from working publicly.
    API-documentation reference

Possible vulnerabilities:

  • Reflected XSS. If we can do BUCKET READ we can list assets and might find vulnerable objects, like a vulnerable SWF served on the company’s domain.
  • Stored XSS / asset control. If we can do BUCKET WRITE or BUCKET WRITE-ACP (also meaning OBJECT WRITE) we can modify existing content or create new content, being able to modify javascript/css-files or by uploading a new HTML-file.
  • Denial of server. If we can modify the ACP of objects using OBJECT WRITE-ACP, we can prevent objects from loading publicly.
  • Information disclosure. If we can list objects we might find sensitive information.
  • RCE. If the bucket contains modifiable executables this can result in Remote Code Execution (RCE) depending on where the executables are being used and if/by whom they are being downloaded.

2. Assets from bucket used by the company

Additional Disclaimer: The assets being used by a company might not always be owned by the company. You need to be extremely careful here not to attack anyone other than the intended target who has given you permission to test.

There are projects trying to automate this, such as Second Order. However, Second Order only checks for assets being referenced in the HTTP-response, files being loaded dynamically are not being checked. Below is a quick example of also checking for dynamically loaded assets using Headless Chrome.

First, start the headless version on port 9222:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 --disable-gpu --headless

We can then use a small script. (context.js is borrowed from the HAR-capturer-project since that one properly closes tabs)

const CDP = require('chrome-remote-interface');
const URL = require('url').URL;
const Context = require('./context');

async function log_requests(orig_url) {
    const context = new Context({});

    process.on('SIGTERM', function () {
        context.destroy();
    });

    try {
        const client = await context.create();
        const {Network, Page} = client;
        const ourl = new URL('http://' + orig_url);
        const ohost = ourl.host;

        Network.requestWillBeSent((params) => {
            if (params.request.url.match('^data:')) {
                return;
            }
            const url = new URL(params.request.url);
            console.log(ohost + ':' + url.host + ':' + params.request.url);
        });
        await Promise.all([Network.enable(), Page.enable()]);
        await Page.navigate({url: 'http://' + orig_url});
        await Page.loadEventFired();
        await Page.navigate({url: 'https://' + orig_url});
        await Page.loadEventFired();
    } finally {
        await context.destroy();
    }
}
const url = process.argv.slice(2)[0];
log_requests(url);

Which will give us all assets on the page which we then can use to figure out if they are served from S3 or not:

You should test for:

Possible vulnerabilities:

  • Stored XSS / asset control. If we can do BUCKET WRITE or BUCKET WRITE-ACP (also meaning OBJECT WRITE) we can modify existing content or create new content, being able to modify javascript/css-files or similar. This can be extremely bad depending on where the assets are being used, such as on login pages or on main pages.
  • Denial of server. If we can modify the ACP of objects using OBJECT WRITE-ACP, we can prevent objects from loading publicly.
  • RCE. If assets are modifiable executables this can result in Remote Code Execution (RCE) depending on where the executables are being used and if/by whom they are being downloaded.

3. Bucket randomly found, indications it’s owned by the company

This one is a bit complicated. You need to have clear evidence and proof that the bucket is indeed owned by the company. Try to find references from the company pointing to this bucket, such as references on their website, CI logs or open source code.

You should test for:

Possible vulnerabilities:

  • Stored XSS / asset control. If we can do BUCKET WRITE or BUCKET WRITE-ACP (also meaning OBJECT WRITE) we can modify existing content or create new content, being able to modify javascript/css-files. However, in this case we don’t know where the files are being used so we cannot know how big the impact is without talking with the company.
  • Denial of server. If we can modify the ACP of objects using OBJECT WRITE-ACP, we can prevent objects from loading publicly. We do not know in this case if they are however.
  • Information disclosure. If we can list objects we might find sensitive information. Only do this if you have confirmed that the bucket is indeed connected to the company you have approval from.
  • RCE. If the bucket contains modifiable executables this can result in Remote Code Execution (RCE) depending on where the executables are being used and if/by whom they are being downloaded.

Results

During this research we were able to confirm we could control assets on high profile websites. We reported these issues directly and were able to get them solved quickly. The following categories of websites were affected:

  • Password managers
  • DNS/CDN providers
  • File storage
  • Gaming
  • Audio and video streaming providers
  • Health tracking

We identified vulnerable assets placed on the login pages of some companies.

In some cases, vulnerable assets were loaded using Google Tag Manager (gtm.js) however, they did not sandbox the third parties properly, running the third party assets directly on the domain itself (not by sandboxing them using www.googletagmanager.com)

We got in touch with some third party providers, both directly but also with help from the affected companies, quickly identifying the issue and solving it very fast.

How to stay safe

The following processes can prevent this issue from happening:

  1. Sandbox third party assets. As soon as you are in need of third party assets, through gtm.js or similar, try isolating the scripts either by using the iframe provided by Google Tag Manager or by placing them on a separate domain (not only using a subdomain). Also ask your provider how they handle access control on their files, and if they are using S3 for file serving.
  2. If you have your own buckets, take a look through the bucket ACLs to verify WRITE and WRITE_ACP are only set on specific users, never on groups such as AllUsers or AuthenticatedUsers.
  3. The hardest fix is to prevent any object in any bucket from having WRITE_ACP, test yourself by doing a aws s3api put-object-acl with the appropriate settings using a restricted AWS-user against your own objects in your buckets. You might need to update the ACL on every object to mitigate this completely.
  4. Take a look and see how you are uploading objects to S3 buckets and make sure you set the proper ACLs on both buckets and objects.
  5. Do not use a secret bucket name as a form of Security through Obscurity. Treat the bucket name like it is already public information.

On a final note

It’s clear after this research that this problem is widespread and hard to identify and completely solve, especially if the company uses a huge amount of buckets, created by different systems. WRITE_ACP is the most dangerous one for reasons mentioned, both on buckets and objects.

An interesting detail when manually uploading files to S3 using Cyberduck, changing the access control on a file looks like this:

Pretty easy to accidentally pick the wrong one there.

Until next time.

What Detectify scans for

Detectify tests web applications for the following S3 misconfiguration vulnerabilities with a severity range between 4.4-9 on the CVSS scale:

  • Amazon S3 bucket allows for full anonymous access
  • Amazon S3 bucket allows for arbitrary file listing
  • Amazon S3 bucket allows for arbitrary file upload and exposure
  • Amazon S3 bucket allows for blind uploads
  • Amazon S3 bucket allows arbitrary read/writes of objects
  • Amazon S3 bucket reveals ACP/ACL

 

'경로 및 정보' 카테고리의 다른 글

eSIM 구현 Android 오픈소스  (0) 2024.03.06
Teleport 관련 자료  (1) 2024.02.24
nginx version 숨기기 및 header 정보 숨기기  (1) 2023.12.07
/.vscode/sftp.json  (0) 2023.11.19
Active Directory Basic 문제  (0) 2023.11.12
블로그 이미지

wtdsoul

,

https://xinet.kr/?p=3478

 

nginx version 숨기기 및 header 정보 숨기기 ( nginx remove the server header )

기본적으로 Nginx에서 nginx 버전 정보를 숨기는 것은 간단하게 해결 할수 있다기본값이 on 상태일때 값을 확인해 보면버전 정보를 숨기기 위해서는 nginx.conf 환경설정에서 server_tokens 값을 off 로 변

xinet.kr

 

기본적으로 Nginx에서 nginx 버전 정보를 숨기는 것은 간단하게 해결 할수 있다
기본값이 on 상태일때 값을 확인해 보면

 
 
 
 
 
Shell
 
1
2
3
4
5
[root@xinet nginx-1.21.6]# vi /usr/local/nginx/conf/nginx.conf
 
 
### version hide
    server_tokens on;

이렇게 구성할 경우 버전이 출력된다

 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
8
9
10
11
[root@xinet ~]# curl -IsL https://xinet.kr --insecure
HTTP/1.1 200 OK
Server: nginx/1.21.7
Date: Fri, 29 Apr 2022 06:58:54 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: PHPSESSID=nv5flpefh076b2a92taf95u093; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Link: <https://xinet.kr/index.php?rest_route=/>; rel="https://api.w.org/"


버전 정보를 숨기기 위해서는 nginx.conf 환경설정에서 server_tokens 값을 off 로 변경하면 된다

 
 
 
 
 
Shell
 
1
2
3
4
[root@xinet nginx-1.21.6]# vi /usr/local/nginx/conf/nginx.conf
 
### version hide
    server_tokens off;

nginx 재시작 후 확인

 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@xinet ~]# systemctl restart nginx
 
 
[root@xinet ~]# curl -IsL https://xinet.kr --insecure
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 29 Apr 2022 07:02:06 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: PHPSESSID=1ljq6md9ngrd6q7pm3njstrlst; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Link: <https://xinet.kr/index.php?rest_route=/>; rel="https://api.w.org/"

이렇게 하면 버전의 정보를 숨길 수가 있다


웹페이지에서도 확인해보자

그러면 header 값에 server에 nginx 값이 표시가 되는데 이것도 숨길 수가 있다
모듈이 추가해서 이용하면 되는데 사용되믄 모듈은 ngx_security_headers 이용하면 된다

다운로드 후 모듈을 생성 후 추가해주면 된다

 
 
 
 
 
Shell
 
1
2
3
[root@xinet ~]# cd /usr/local/src/
 

기본 설치된 버전 없으면 다운로드 있으면 처음 설치 폴더로 가서 모듈 생성 및 복사

 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
[root@xinet src]# cd nginx-1.21.6/
 
[root@xinet nginx-1.21.6]# ./configure --with-compat --add-dynamic-module=/usr/local/src/ngx_security_headers
 
[root@xinet nginx-1.21.6]# make modules
 
[root@xinet nginx-1.21.6]# cp -a objs/ngx_http_security_headers_module.so /usr/local/nginx/modules/

모듈이 복사가 되었으면 환경설정 값에  추가

 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
8
9
[root@xinet nginx-1.21.6]# vi /usr/local/nginx/conf/nginx.conf
 
 
 
 
load_module modules/ngx_http_security_headers_module.so;
 
### header hide
    hide_server_tokens on;

nginx 재시작 및 curl 확인 server 정보가 표시되지 않는다

 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@xinet ~]# systemctl restart nginx
 
 
[root@xinet ~]# curl -IsL https://xinet.kr --insecure
HTTP/1.1 200 OK
Date: Fri, 29 Apr 2022 06:52:39 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: PHPSESSID=ukiujk8i29fo97enqlr4fops2i; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Link: <https://xinet.kr/index.php?rest_route=/>; rel="https://api.w.org/"


크롬 웹페이지에서 확인

만약 yum 으로 설치된 환경이라면  해당 설치 후 모듈 추가된거 확인 후 이용하면 된다

 
 
 
 
 
Shell
 
1
2
3
yum -y install https://extras.getpagespeed.com/release-latest.rpm
 
yum -y install nginx-module-security-headers
 
 
 
 
 
Shell
 
1
2
3
4
5
6
7
load_module modules/ngx_http_headers_more_filter_module.so;
 
http {
    ...
    more_clear_headers Server;
    ...
}

 

'경로 및 정보' 카테고리의 다른 글

Teleport 관련 자료  (1) 2024.02.24
A deep dive into AWS S3 access controls – taking full control over your assets  (1) 2024.02.05
/.vscode/sftp.json  (0) 2023.11.19
Active Directory Basic 문제  (0) 2023.11.12
Fiddler 셋팅  (0) 2023.11.10
블로그 이미지

wtdsoul

,

https://dev-blackcat.tistory.com/1

 

[VSCODE] 비주얼스튜디오코드(Visual Studio Code) SFTP 연동하기

먼저, 확장 탭을 클릭 후 "SFTP" 검색한 후 아래의 이미지의 확장을 설치해 주세요. 새로운 폴더 혹은 기존 프로젝트 폴더를 열어주신 후 "F1"을 눌러 "sftp" 검색 후 "sftp:config"을 선택해 주세요. 아래

dev-blackcat.tistory.com

 

먼저, 확장 탭을 클릭 후 "SFTP" 검색한 후 아래의 이미지의 확장을 설치해 주세요.

VSCODE 확장프로그램

 

새로운 폴더 혹은 기존 프로젝트 폴더를 열어주신 후 "F1"을
눌러 "sftp" 검색 후 "sftp:config"을 선택해 주세요.

VSCODE 확장프로그램

 

아래와 같이 ".vscode" 폴더가 생성되며 "sftp.json" 파일이 생성됩니다.

VSCODE 확장프로그램

 

초기 생성 시 없는 속성이 있습니다. 아래와 비교하여 추가해 주세요.

옵션 설명
uploadOnSave 저장시에 자동으로 업로드 (true/false)
igonre 업/다운로드를 제외할 파일 및 폴더 보안상 ".vscode" 폴더 안의 파일 내용에는 서버 계정의 비밀번호까지 있기 때문에 대부분 설정합니다.
{
    "name": "프로젝트 이름",
    "host": "서버 IP 주소",
    "protocol": "sftp",
    "port": 22,
    "username": "서버 계정",
    "password": "서버 비밀번호",
    "remotePath": "서버 디렉토리 루트",
    "uploadOnSave": true,
    "watcher": {
        "files": "**/*",
        "autoUpload": true,
        "autoDelete": true
    },
    "ignore": [
        "**/.vscode",
        "**/.git",
        "**/.DS_Store",
        "**/.sftp.json",
        "**/node_modules"
    ]
}

 

예제

다시 "F1"을 눌러 "sftp:Download Project"을 선택하여 프로젝트 다운로드하기

{
    "name": "TEST Project",
    "host": "01.234.567.890",
    "protocol": "sftp",
    "port": 22,
    "username": "root",
    "password": "123456",
    "remotePath": "/root/test",
    "uploadOnSave": true,
    "watcher": {
        "files": "**/*",
        "autoUpload": true,
        "autoDelete": true
    },
    "ignore": [
        "**/.vscode",
        "**/.git",
        "**/.DS_Store",
        "**/.sftp.json",
        "**/node_modules"
    ]
}
 
 
 
블로그 이미지

wtdsoul

,

https://tryhackme.com/room/winadbasics

 

TryHackMe | Active Directory Basics

This room will introduce the basic concepts and functionality provided by Active Directory.

tryhackme.com

 

슬슬 시작할때가 왔구만... 

 

Several groups are created by default in a domain that can be used to grant specific privileges to users. As an example, here are some of the most important groups in a domain:

Security Group Description
Domain Admins Users of this group have administrative privileges over the entire domain. By default, they can administer any computer on the domain, including the DCs.
Server Operators Users in this group can administer Domain Controllers. They cannot change any administrative group memberships.
Backup Operators Users in this group are allowed to access any file, ignoring their permissions. They are used to perform backups of data on computers.
Account Operators Users in this group can create or modify other accounts in the domain.
Domain Users Includes all existing user accounts in the domain.
Domain Computers Includes all existing computers in the domain.
Domain Controllers Includes all existing DCs on the domain.

You can obtain the complete list of default security groups from the Microsoft documentation.

 

블로그 이미지

wtdsoul

,

Fiddler 셋팅

경로 및 정보 2023. 11. 10. 10:55

 

Fiddler 내부망

 

Options > HTTPS > Decrypt

Conntection 8888 확인 > 브라우저 127.0.0.1 > 인증서 설치

Gateway > Manual Proxy Config > 127.0.0.1:8080 (Burp 포트 넘버)

 

캡처링 (F12) 체크한 뒤 Burp 통해서 패킷을 받아 봄

블로그 이미지

wtdsoul

,

https://seonu-lim.github.io/python/%EC%98%A4%ED%94%84%EB%9D%BC%EC%9D%B8%EC%97%90%EC%84%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0/

 

오프라인 상태에서 패키지를 설치하기?

이직한 곳에서는 보안 상의 이유로 VDI(Virtual Desktop Infra) 라는 것을 사용하는데, 나는 처음 접해보는 것이다보니 아직 익숙하지 않다. 모든 사내 데이터는 원칙적으로 VDI 내부에서만 존재하고, 허

seonu-lim.github.io

 

이직한 곳에서는 보안 상의 이유로 VDI(Virtual Desktop Infra) 라는 것을 사용하는데, 나는 처음 접해보는 것이다보니 아직 익숙하지 않다. 모든 사내 데이터는 원칙적으로 VDI 내부에서만 존재하고, 허가를 받아야 데이터의 반출이 가능하다. 사내 메신저나 메일도 VDI 안에서만 확인이 가능하다… 게다가 인터넷은 회사 홈페이지를 제외하고는 연결이 되지 않는다!

이렇게 폐쇄적인 환경이다보니, 개발이나 분석 툴로써 파이썬을 사용할 때에도 어려움이 따른다. 패키지들을 설치하려면 보통은 pip 이나 conda 를 사용하는데 인터넷 연결이 되지 않으니 패키지를 다운받는 게 정말 번거로운 일이 된다. 설상가상으로 나는 지금 회사에서 파이썬 교육 업무를 맡고 있어서, 아무것도 모르는 사람들이 VDI 에 패키지를 설치할 수 있도록 만들어주어야 한다. 따라서 최대한 간소한 방법을 서술하도록 하겠다.

우선 VDI 를 접속하는 로컬 컴퓨터는 몇몇 사이트를 제외하고 인터넷 연결이 되기 때문에 우선 로컬에서 원하는 패키지를 인스톨한다. 단, 우리 회사 인터넷망에서는 그냥 pip install foo 하면, 뭔 ssl certificate 이 어쩌구 하면서 에러가 난다. 그러므로 다음과 같이 argument를 더해주도록 한다.

pip --trusted-host pypi.org --trusted-host files.pythonhosted.org install foo

이렇게 다운받고 나서, 폴더를 하나 만들어서 shell 을 켜고 그곳으로 이동한다. 그리고 다음과 같이 다운로드 해준다.

pip --trusted-host pypi.org --trusted-host files.pythonhosted.org download foo

폴더에 foo 파일과 그의 dependency들도 같이 다운로드되어있을 것이다. 이것을 파일 전송 시스템으로 VDI 로 옮겨준다. VDI 에는 python만 깔려있다고 가정한다. 이제 파일 전송 시스템에서 받아온 파일들을 특정 경로에 저장해주고 shell 을 켠다.

python -m pip install --no-index -f . foo

오류가 난다면, whl 파일을 다운받았던 파이썬 버전과 패키지 인스톨하고자 하는 파이썬 버전이 다르지 않은지 확인해보자.

블로그 이미지

wtdsoul

,

https://medium.com/@pratyush1337/information-disclosure-via-misconfigured-aws-to-aws-bucket-takeover-6a6a66470d0e

 

Information Disclosure via Misconfigured AWS to AWS Bucket Takeover

Hey! Welcome to a new write up on my recent finding of a Misconfigured AWS bucket and how i was able to Take full control of the AWS…

medium.com

 

Information Disclosure via Misconfigured AWS to AWS Bucket Takeover

Hey! Welcome to a new write up on my recent finding of a Misconfigured AWS bucket and how i was able to Take full control of the AWS bucket.

I was checking out the website mainly for the IDOR Vulnerabilities as those are considered as High priority bugs and are paid in high range. I was trying to check every possible end-points to find any parameter to manipulate the numerical value so i fired up my burp suite and sent the request to spider tab to check out all the endpoints but i failed because they have encrypted every numerical value using salted algorithm.

As it was not possible to find any IDOR , i found an interesting endpoint where i was able to set my organization logo and there was a direct link to the logo which was residing at an AWS bucket. You can check below:

So i checked this logo by directly coping it and opening it in the new tab:

Basically i never though that i will find anything like this so i never thought of doing anything in any programs or private programs i have worked on but that day i thought that let’s go to origin directory of the file[in hacker’s language ../ ;)]. so i checked it by going to the origin directory as you can see:

Bingo! this was a proper Information disclosure due to Misconfigured ACL of the AWS bucket. I was happy and thought of reporting this directly but as a Hacker you are always overwhelmed and curious to find all the juicy information that might be possible to exploit. So without wasting any time , I went ahead to check out all the files getting listed in the Directory but before that i tried to access one of the file to check if the files are real or not.

Than i opened the file to see what is the content:

Now i am confident enough that all the files available here are legitimate[Use of sophisticated word to look geeky 🤓] and we can see all the internal files of the xyz company here with small tutorials , screenshot and this was an internal S3 bucket used for training and demonstration purposes, such as sharing screenshots of their products……I guess so now you can see why it’s Critical.

At that moment , I felt like it’s enough to report now but i took a chance and thought if there is something else the bucket is offering to compromise itself…Damn Is it possible? Let’s see what happens…. I started checking files with extensions, especially with .zip or .htm or .eml or .doc or .csv and while searching through the entire bucket[which consisted of more than 700+ files] and found the first zip file:

So i downloaded it and checked the contents:

After checking on the files of that zip , i figured out that it’s not going to offer me anything to compromise the AWS bucket. So i started searching for other zip files and found an interesting zip file in the AWS bucket:

Now i downloaded the file and opened to check the contents:

I checked all the files but the important file was the “document.wflow” , It has everything i required to TAKEOVER the AWS Bucket. Let’s check the content:

I was so happy to see this credentials but now the funny thing is that i don’t know what to do with that because Zero knowledge in AWS. So the best way i found was i asked one of my office colleague who is a Dev and works on AWS. He told me that , Go to google and download S3 Browser to start browsing the AWS bucket if you have the “access_key” and “secret_key” which was a very new learning experience in the field of my Web Application Penetration Testing. I was like:

 

So i downloaded it and started providing all the required credentials:

Boom!

The next thing i checked on the Access Control List permission on each directory and found a directory with full access:

With the full access to this directory now I am the Owner of this and i can upload any file i want , I can delete it and i can Delete the whole directory. I had all the access in the world but As you all know we are all ethical in what we do so to make a Proof Of Concept i uploaded a file:

Now to re-verify it I checked it in the public facing bucket with my uploaded file name.

 

Final check I pasted the filename in the URL and checked:

Damn! AWS Bucket Takeover!

Following my initial report and its review, they had promptly and fairly compensated me for letting them know about this bug. I am really thankful for that :)

'경로 및 정보' 카테고리의 다른 글

Fiddler 셋팅  (0) 2023.11.10
VDI python Package 설치  (0) 2023.11.08
Hacking Swagger-UI - from XSS to account takeovers  (0) 2023.11.02
Active Directory 정보 수집  (0) 2023.10.25
Active Directory Pentesting Resources  (1) 2023.10.25
블로그 이미지

wtdsoul

,

https://blog.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers/

 

Hacking Swagger-UI - from XSS to account takeovers

We have reported more than 60 instances of this bug across a wide range of bug bounty programs including companies like Paypal, Atlassian, Microsoft, GitLab, Yahoo, ...

blog.vidocsecurity.com

 

 

블로그 이미지

wtdsoul

,