안전하지 않은 역직렬화란? 객체 변조가 코드 실행으로 이어지는 이유

안전하지 않은 역직렬화 취약점의 발생 원인과 객체 변조가 코드 실행으로 이어지는 과정, 취약한 Java 코드 및 안전한 대응 방법을 알아봅니다.

웹 애플리케이션은 사용자 설정, 로그인 상태, 캐시 데이터처럼 여러 정보를 저장하거나 다른 시스템으로 전달한다. 이 과정에서 프로그램이 사용하는 객체를 파일이나 문자열 형태로 변환하기도 한다.

변환된 데이터를 다시 객체로 복원할 때 외부에서 조작된 값을 그대로 사용하면 예상하지 못한 클래스가 생성되거나 위험한 메서드가 실행될 수 있다. 이러한 문제를 안전하지 않은 역직렬화 취약점이라고 한다.

직렬화와 역직렬화란?

직렬화는 프로그램에서 사용하는 객체를 파일, 바이트 배열 또는 문자열처럼 저장하고 전송할 수 있는 형태로 변환하는 과정이다. 반대로 역직렬화는 변환된 데이터를 다시 프로그램의 객체로 복원하는 과정이다.

예를 들어 사용자의 화면 설정을 객체로 관리한다고 가정해 보자. 해당 객체를 직렬화하면 파일이나 데이터베이스에 저장할 수 있고, 다음 접속에서 역직렬화하면 이전 설정을 다시 사용할 수 있다.

직렬화와 역직렬화 기능 자체가 취약한 것은 아니다. 문제는 신뢰할 수 없는 사용자가 직렬화 데이터를 수정할 수 있고, 서버가 데이터의 출처와 내용을 확인하지 않은 채 객체로 복원할 때 발생한다.

안전하지 않은 역직렬화의 핵심은 외부 입력값이 서버에서 생성할 객체의 종류와 상태에 영향을 준다는 점이다.

안전하지 않은 역직렬화가 발생하는 원인

가장 흔한 원인은 쿠키, HTTP 요청 본문, 업로드 파일처럼 사용자가 조작할 수 있는 데이터를 그대로 역직렬화하는 것이다. 서버가 생성한 데이터라도 브라우저나 외부 저장소를 거쳐 다시 전달된다면 변조 가능성을 확인해야 한다.

직렬화 데이터를 Base64로 변환했다고 해서 안전해지는 것은 아니다. Base64는 데이터를 다른 문자 형식으로 표현하는 인코딩 방식일 뿐이며 누구나 원래 데이터로 되돌리거나 수정할 수 있다.

역직렬화할 수 있는 클래스의 범위를 제한하지 않는 것도 문제다. 애플리케이션과 외부 라이브러리에 포함된 여러 클래스 중에는 객체가 생성되거나 특정 속성이 설정되는 과정에서 다른 메서드를 호출하는 클래스가 있을 수 있다.

오래된 라이브러리를 계속 사용하는 환경에서는 이미 알려진 위험한 클래스 조합이 포함돼 있을 가능성도 있다. 사용하지 않는 라이브러리라도 애플리케이션의 클래스 경로에 존재하면 역직렬화 과정에서 영향을 줄 수 있으므로 의존성 관리가 필요하다.

역직렬화에 취약한 Java 코드

다음 코드는 HTTP 요청으로 받은 바이트 데이터를 Java 객체로 변환한다.

@PostMapping("/settings/restore")
public String restore(@RequestBody byte[] data)
        throws Exception {

    try (ObjectInputStream input =
            new ObjectInputStream(
                new ByteArrayInputStream(data))) {

        UserSettings settings =
                (UserSettings) input.readObject();

        return settings.getTheme();
    }
}

코드는 요청 본문을 ObjectInputStream에 전달하고 readObject()로 객체를 복원한다. 하지만 전달된 데이터가 서버에서 생성된 것인지, 중간에 변조되지 않았는지 확인하지 않는다.

코드에서는 결과를 UserSettings로 형 변환하지만 위험한 동작은 형 변환 전에 발생할 수 있다. readObject()가 직렬화 데이터에 기록된 클래스 정보를 확인하고 객체를 생성하는 과정에서 예상하지 못한 클래스가 먼저 처리될 수 있기 때문이다.

따라서 마지막에 특정 클래스로 형 변환한다는 이유만으로 안전하다고 볼 수 없다. 역직렬화를 시작하기 전에 허용할 데이터 형식과 객체 유형을 제한해야 한다.

객체 변조가 코드 실행으로 이어지는 원리

Java의 직렬화 데이터에는 객체의 속성뿐 아니라 복원에 필요한 클래스 정보와 객체 사이의 관계가 포함될 수 있다. 서버는 이 정보를 바탕으로 객체를 생성하고 필요한 메서드를 호출하며 상태를 복원한다.

공격자는 애플리케이션에 존재하는 여러 클래스가 특정 순서로 동작하도록 조작된 데이터를 만들려고 시도한다. 각각의 클래스가 정상적인 목적으로 만들어졌더라도 객체 생성, 속성 접근 또는 메서드 호출이 연결되면 예상하지 못한 작업이 실행될 수 있다.

이처럼 여러 클래스의 동작이 연속적으로 연결되는 구조를 일반적으로 가젯 체인이라고 한다. 공격자가 서버에 새로운 실행 파일을 직접 올리는 것이 아니라, 서버에 이미 설치된 클래스와 라이브러리의 기능을 조합해 위험한 동작을 유도하는 방식이다.

모든 역직렬화 취약점이 곧바로 원격 코드 실행으로 이어지는 것은 아니다. 실제 피해는 사용 중인 라이브러리, 클래스 구성, 서버 권한과 데이터 처리 방식에 따라 달라진다. 그러나 영향 범위를 예측하기 어렵기 때문에 신뢰할 수 없는 데이터에 대한 네이티브 역직렬화는 피하는 것이 원칙이다.

안전하지 않은 역직렬화로 발생할 수 있는 피해

취약점이 악용되면 서버에서 의도하지 않은 코드나 시스템 명령이 실행될 수 있다. 웹 서버 권한으로 내부 파일을 읽거나 변경하고, 추가 프로그램을 실행할 가능성도 있다.

객체의 속성값만 변조되는 경우도 있다. 사용자 권한, 상품 가격, 할인율과 같은 값이 직렬화 데이터에 포함돼 있고 서버가 이를 다시 검증하지 않는다면 일반 사용자가 관리자 권한을 얻거나 결제 정보를 변경할 수 있다.

복잡한 객체를 대량으로 생성하게 만들어 CPU와 메모리를 소모시키는 서비스 거부 공격으로 이어질 수도 있다. 따라서 코드 실행 가능성만 확인하지 말고 권한 변조, 데이터 위·변조와 자원 소모 문제도 함께 살펴봐야 한다.

DTO 기반으로 필요한 값만 받는 안전한 코드

가장 안전한 대응 방법은 안전한 대응 방법은 Java 네이티브 직렬화 객체를 외부에서 직접 받지 않는 것이다. JSON과 같이 구조가 단순한 데이터 형식을 사용하고, 업무에 필요한 필드만 정의한 DTO로 변환하는 것이 좋다.

public record UserSettingsRequest(

    @Pattern(regexp = "light|dark")
    String theme,

    @Min(10)
    @Max(50)
    int pageSize
) {}
@PostMapping(
    value = "/settings",
    consumes = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<String> saveSettings(
        @Valid @RequestBody
        UserSettingsRequest request) {

    settingsService.save(
        request.theme(),
        request.pageSize()
    );

    return ResponseEntity.ok("저장되었습니다.");
}

이 코드는 사용자가 보낼 수 있는 값을 themepageSize로 제한한다. 화면 주제는 light 또는 dark만 허용하고 페이지 크기도 지정된 범위 안에서만 처리한다.

클라이언트가 서버에서 사용할 클래스 이름을 결정하지 않으며, 전달된 값을 바로 권한이나 중요 객체로 사용하지도 않는다. 서비스 계층에서 필요한 데이터만 꺼내 새 객체를 생성하므로 복잡한 객체 구조가 외부 입력에 의해 만들어질 가능성이 줄어든다.

JSON 라이브러리를 사용한다고 자동으로 안전해지는 것은 아니다. 외부 입력값에 포함된 클래스 정보를 이용해 임의의 객체를 생성하는 다형성 타입 기능이나 기본 타입 지정 기능은 신중하게 사용해야 한다. 필요하지 않다면 비활성화하고 허용할 DTO를 명확하게 정해야 한다.

네이티브 역직렬화가 필요한 경우

기존 시스템과의 호환성 때문에 Java 네이티브 역직렬화를 즉시 제거하기 어려울 수 있다. 이 경우 역직렬화할 수 있는 클래스, 배열 크기, 객체 깊이와 참조 개수를 제한하는 필터를 적용해야 한다.

Java의 ObjectInputFilter를 사용하면 허용할 클래스와 데이터 구조의 한계를 설정할 수 있다. 그러나 허용 목록에 포함된 클래스가 실제로 안전한지 검토해야 하며, 필터를 적용했다는 이유로 외부 데이터를 무조건 신뢰해서는 안 된다.

데이터가 서버에서 생성된 후 외부를 거쳐 돌아온다면 전자서명이나 HMAC처럼 무결성을 확인할 수 있는 방법을 적용해야 한다. 검증에 실패한 데이터는 역직렬화하지 않고 즉시 거부해야 한다.

암호화만 적용하는 것은 충분하지 않을 수 있다. 암호화는 주로 내용을 숨기는 방법이므로 데이터가 변조되지 않았다는 보장도 필요하다. 인증된 암호화 방식이나 별도의 무결성 검증을 함께 사용해야 한다.

라이브러리와 의존성 관리

역직렬화 공격은 애플리케이션에 포함된 외부 라이브러리의 클래스를 이용할 수 있다. 따라서 사용하지 않는 라이브러리는 제거하고 사용하는 프레임워크와 의존성을 최신 보안 버전으로 유지해야 한다.

다만 라이브러리를 업데이트하는 것만으로 근본적인 문제가 해결되지는 않는다. 하나의 위험한 클래스가 제거되더라도 다른 클래스 조합이 발견될 수 있기 때문이다. 가장 중요한 대응은 신뢰할 수 없는 데이터를 범용 객체 역직렬화 함수에 전달하지 않는 것이다.

ObjectInputStream.readObject(), XML 객체 변환 기능, JSON의 다형성 타입 설정처럼 객체를 동적으로 생성하는 코드를 중심으로 데이터의 출처를 확인해야 한다.

최소 권한과 네트워크 접근통제

웹 애플리케이션은 최소 권한의 전용 계정으로 실행해야 한다. 역직렬화 과정에서 예상하지 못한 동작이 발생하더라도 서버 계정에 중요 파일 접근 권한이 없다면 피해 범위를 줄일 수 있다.

웹 서버에서 불필요한 외부 통신과 내부 관리망 접근도 차단하는 것이 좋다. 원격 코드 실행이 발생했을 때 추가 파일을 내려받거나 다른 내부 시스템으로 연결되는 것을 제한하는 방어선이 된다.

비정상적으로 큰 요청, 알 수 없는 직렬화 형식, 반복적인 역직렬화 오류와 예상하지 못한 클래스 생성 시도를 로그로 기록해야 한다. 애플리케이션 로그와 프로세스 실행 로그를 함께 확인하면 공격 흔적을 찾는 데 도움이 된다.

안전하지 않은 역직렬화 점검 항목

소스코드 점검에서는 먼저 ObjectInputStream, readObject(), readUnshared()처럼 Java 객체를 복원하는 기능을 검색한다. 해당 함수에 전달되는 데이터가 HTTP 요청, 쿠키, 세션 값, 메시지 큐 또는 업로드 파일에서 들어오는지 추적해야 한다.

JSON이나 XML 라이브러리에서는 외부 입력값으로 클래스 유형을 선택할 수 있는 설정이 활성화돼 있는지 확인한다. 사용자가 보낸 필드가 서버 객체에 자동으로 대입된 후 권한이나 중요 업무 처리에 사용되는지도 살펴봐야 한다.

직렬화 데이터에 대한 서명이나 무결성 검증이 역직렬화보다 먼저 수행되는지 확인한다. 검증 과정에서 오류가 발생했는데도 처리를 계속하는 코드가 없는지도 점검해야 한다.

의존성 목록에서는 사용하지 않는 오래된 라이브러리와 알려진 보안 문제가 있는 버전을 확인한다. 운영 환경에서는 웹 서버의 파일 권한, 실행 권한과 네트워크 접근 범위도 함께 점검해야 한다.

자주 묻는 질문

Base64로 인코딩하면 역직렬화 데이터가 안전한가요?

안전하지 않다. Base64는 암호화가 아니라 표현 형식을 바꾸는 인코딩 방식이다. 누구나 디코딩해 내용을 확인하거나 변경할 수 있으므로 별도의 무결성 검증이 필요하다.

JSON을 사용하면 역직렬화 취약점이 사라지나요?

단순한 DTO로 필요한 값만 받으면 위험을 크게 줄일 수 있다. 하지만 JSON 데이터에 포함된 클래스 정보를 이용해 임의의 객체를 생성하도록 설정하면 비슷한 문제가 발생할 수 있다. 다형성 타입 기능을 제한하고 입력값을 검증해야 한다.

특정 클래스로 형 변환하면 안전한가요?

형 변환은 역직렬화가 끝난 후 수행될 수 있다. 위험한 객체가 복원되는 과정에서 이미 메서드가 실행될 가능성이 있으므로 최종 형 변환만으로는 안전성을 보장할 수 없다.

데이터를 암호화하면 역직렬화해도 괜찮나요?

암호화만으로는 충분하지 않다. 데이터가 변조되지 않았는지 확인할 수 있는 무결성 검증이 필요하다. 또한 신뢰할 수 있는 데이터라도 불필요하게 범용 객체 역직렬화를 사용하는 것은 피하는 것이 좋다.

마무리

안전하지 않은 역직렬화 취약점은 외부에서 전달된 데이터를 서버 객체로 복원하는 과정에서 발생한다. 공격자는 객체의 속성과 클래스 관계를 조작해 권한을 변경하거나 서버에 존재하는 클래스의 동작을 연결하려고 시도할 수 있다.

가장 효과적인 대응 방법은 신뢰할 수 없는 데이터를 Java 네이티브 객체로 역직렬화하지 않는 것이다. JSON과 명확한 DTO를 사용해 필요한 값만 받고, 각 필드의 형식과 범위를 검증해야 한다.

안전한 역직렬화의 핵심은 사용자가 서버에서 생성할 객체의 종류와 내부 상태를 결정하지 못하게 하는 것이다. 허용할 데이터 구조를 최소화하고 무결성 검증, 의존성 관리, 최소 권한과 네트워크 접근통제를 함께 적용해야 한다.

핵심 키워드: 안전하지 않은 역직렬화, 역직렬화 취약점, 객체 변조, 원격 코드 실행, Java 역직렬화, ObjectInputStream, 가젯 체인, 역직렬화 대응 방법

댓글 남기기