안전하지 않은 역직렬화

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

개요

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

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

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

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

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

영향

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

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

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

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

완화 조치

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

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

Android 13 (API 수준 33)부터는 Intent 클래스 내에서 여러 메서드가 업데이트되었으며, 이는 이전 메서드와 현재 지원 중단된 메서드를 대체하여 parcel을 처리하는 더 안전한 방법으로 간주됩니다. 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");
}

리소스