안전하지 않은 역직렬화

OWASP 카테고리: MASVS-CODE: 코드 품질

개요

많은 양의 Java 객체 데이터를 저장하거나 전송할 때 데이터를 먼저 직렬화하는 것이 더 효율적인 경우가 많습니다. 그런 다음 데이터를 수신하고 처리하는 애플리케이션, 활동 또는 제공업체에서 데이터를 역직렬화합니다. 일반적인 상황에서는 데이터가 직렬화된 다음 사용자의 개입 없이 역직렬화됩니다. 그러나 역직렬화 프로세스와 의도한 객체 간의 신뢰 관계는 직렬화된 객체를 가로채고 변경할 수 있는 악의적인 행위자에 의해 악용될 수 있습니다. 이를 통해 악의적인 행위자는 서비스 거부 (DoS), 권한 상승, 원격 코드 실행 (RCE)과 같은 공격을 실행할 수 있습니다.

Serializable 클래스는 직렬화를 관리하는 일반적인 방법이지만 Android에는 Parcel라는 직렬화를 처리하는 자체 클래스가 있습니다. Parcel 클래스를 사용하면 객체 데이터를 바이트 스트림 데이터로 직렬화하고 Parcelable 인터페이스를 사용하여 Parcel로 패킹할 수 있습니다. 이를 통해 Parcel를 더 효율적으로 전송하거나 저장할 수 있습니다.

하지만 Parcel 클래스는 고효율 IPC 전송 메커니즘으로 사용하도록 설계되었으므로 이를 로컬 영구 스토리지 내에 직렬화된 객체를 저장하는 데 사용해서는 안 됩니다. 데이터 호환성 문제나 손실이 발생할 수 있기 때문입니다. 데이터를 읽어야 하는 경우 Parcelable 인터페이스를 사용하여 Parcel를 역직렬화하고 객체 데이터로 다시 변환할 수 있습니다.

Android에서 역직렬화를 악용하는 데는 세 가지 기본 벡터가 있습니다.

  • 커스텀 클래스 유형에서 진행되는 객체를 역직렬화하는 것이 안전하다는 개발자의 잘못된 가정을 활용하는 것이 좋습니다. 실제로는 어떤 클래스에서 가져온 객체도 악성 콘텐츠로 대체될 수 있으며, 최악의 경우 동일한 애플리케이션 또는 다른 애플리케이션의 클래스 로더를 방해할 수 있습니다. 이러한 간섭은 클래스 목적에 따라 데이터 유출이나 계정 도용으로 이어질 수 있는 위험한 값을 삽입하는 형태를 취합니다.
  • 설계상 안전하지 않은 것으로 간주되는 역직렬화 메서드를 악용합니다 (예: 딥 링크 역직렬화 벡터를 통해 임의의 JavaScript 코드 삽입을 허용한 로컬 권한 에스컬레이션 결함인 CVE-2023-35669).
  • 애플리케이션 로직의 결함을 악용합니다 (예: CVE-2023-20963: 앱이 Android의 WorkSource 패키지 로직 내 결함을 통해 권한이 있는 환경 내에서 코드를 다운로드하고 실행할 수 있게 한 로컬 권한 상승 결함).

영향

신뢰할 수 없거나 악의적인 직렬화된 데이터를 역직렬화하는 모든 애플리케이션은 원격 코드 실행 또는 서비스 거부 공격에 취약할 수 있습니다.

위험: 신뢰할 수 없는 입력의 역직렬화

공격자는 애플리케이션 로직 내에서 parcel 인증 부재를 악용하여 역직렬화되면 애플리케이션이 서비스 거부(DoS), 권한 에스컬레이션, 원격 코드 실행 (RCE)을 야기할 수 있는 악성 코드를 강제로 실행할 수 있는 임의의 객체를 삽입할 수 있습니다.

이러한 유형의 공격은 미묘할 수 있습니다. 예를 들어 애플리케이션에는 유효성 검사 후 역직렬화되는 매개변수 하나만을 예상하는 인텐트가 포함될 수 있습니다. 공격자가 예상되는 매개변수와 함께 예상치 못한 두 번째 악성 추가 매개변수를 전송하면 인텐트가 추가 항목을 Bundle로 취급하므로 삽입된 모든 데이터 객체가 역직렬화됩니다. 악의적인 사용자가 이 동작을 악용하여 역직렬화되면 RCE, 데이터 손상 또는 손실을 초래할 수 있는 객체 데이터를 삽입할 수 있습니다.

완화 조치

모든 직렬화된 데이터는 신뢰할 수 없고 악의적인 것일 수 있다고 가정하는 것이 좋습니다. 직렬화된 데이터의 무결성을 보장하려면 데이터에 대한 확인 검사를 실행하여 애플리케이션에서 예상하는 올바른 클래스 및 형식인지 확인합니다.

실행 가능한 해결 방법은 java.io.ObjectInputStream 라이브러리에 룩아헤드 패턴을 구현하는 것입니다. 역직렬화를 담당하는 코드를 수정하면 명시적으로 지정된 클래스 집합만 인텐트 내에서 역직렬화되도록 할 수 있습니다.

Android 13 (API 수준 33)부터 parcel 처리를 위한 이전 메서드와 현재 지원 중단된 메서드보다 더 안전한 대안으로 간주되는 여러 메서드가 Intent 클래스 내에서 업데이트되었습니다. getParcelableExtra(java.lang.String, java.lang.Class)getParcelableArrayListExtra(java.lang.String, java.lang.Class)와 같은 이러한 새로운 유형 안전 메서드는 데이터 유형 검사를 실행하여 애플리케이션 비정상 종료의 원인이 되거나 CVE-2021-0928과 같은 권한 에스컬레이션 공격을 실행하는 데 악용될 수 있는 불일치 약점을 포착합니다.

다음 예는 Parcel 클래스의 안전한 버전을 구현하는 방법을 보여줍니다.

UserParcelable 클래스가 Parcelable를 구현하고 사용자 데이터 인스턴스를 만든 후 Parcel에 기록된다고 가정해 보겠습니다. 그런 다음 readParcelable의 다음 유형에 안전한 메서드를 사용하여 직렬화된 parcel을 읽을 수 있습니다.

Kotlin

val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)

자바

Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);

위의 Java 예시에서 메서드 내에 UserParcelable.CREATOR가 사용된 것을 확인할 수 있습니다. 이 필수 매개변수는 readParcelable 메서드에 예상할 유형을 알려 주며 현재 지원 중단된 readParcelable 메서드 버전보다 성능이 높습니다.

구체적인 위험

이 섹션에는 특수한 완화 전략이 필요한 위험, 또는 특정 SDK 수준에서 완화되었으므로 여기에는 완전성을 위해 포함된 위험이 정리되어 있습니다.

위험: 원치 않는 객체 역직렬화

클래스 내에서 Serializable 인터페이스를 구현하면 자동으로 해당 클래스의 모든 서브유형이 인터페이스를 구현하게 됩니다. 이 시나리오에서는 일부 객체가 앞서 언급한 인터페이스를 상속할 수 있습니다. 즉, 역직렬화되지 않아야 하는 특정 객체가 계속 처리됩니다. 이렇게 하면 의도치 않게 공격 노출 영역이 늘어날 수 있습니다.

완화 조치

클래스가 OWASP 가이드에 따라 Serializable 인터페이스를 상속하는 경우 클래스의 객체 집합이 역직렬화되는 것을 방지하려면 readObject 메서드를 다음과 같이 구현해야 합니다.

Kotlin

@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
    throw IOException("Cannot be deserialized")
}

Java

private final void readObject(ObjectInputStream in) throws java.io.IOException {
    throw new java.io.IOException("Cannot be deserialized");
}

리소스