Java Finalize Attack

석현·2024년 6월 11일
0

Insight

목록 보기
16/43

동료 직원 중 한 분이 백기선님의 Finalize Attack YouTube 영상을 추천해 주셔서 보게 되었는데, 매우 흥미로운 내용을 다루고 있어 포스팅하게 되었습니다. 백기선님은 송금 관련 Finalize Attack을 재미있게 설명해 주셨는데, 저는 해당 내용을 제 코드로 작성해서 포스팅해 보겠습니다.

Finalize Attack이란?

Finalize Attack은 finalize() 메서드를 악용하여 객체의 수명 주기 동안 민감한 정보를 노출시키거나, 객체의 상태를 변경하거나, 보안에 취약점을 만들어내는 공격 기법입니다. 기본적으로 finalize() 메서드는 객체가 더 이상 참조되지 않고 가비지 컬렉터에 의해 수거되기 직전에 호출됩니다. 이를 이용해 공격자는 객체의 finalize() 메서드를 오버라이드하여 변경할 수 있습니다.

Example

FinalizeAttackExample.java

public class FinalizeAttackExample {
    // 싱글톤 인스턴스를 저장하기 위한 정적 변수
    protected static FinalizeAttackExample instance;
    private String importantField;

    // 생성자: 중요한 필드를 초기화하며, 필드가 null이거나 비어 있으면 예외를 발생시킵니다.
    public FinalizeAttackExample(String importantField) {
        if (importantField == null || importantField.isEmpty()) {
            throw new IllegalArgumentException("Important field must not be null or empty");
        }
        this.importantField = importantField;
    }

    // 객체의 finalize 메서드를 분리하여 FinalizeAttackFinalizer 클래스에서 오버라이드할 수 있도록 합니다.
    public static FinalizeAttackExample getInstance(String importantField) {
        try {
            return new FinalizeAttackExample(importantField);
        } catch (IllegalArgumentException e) {
            // 객체 생성에 실패하면 이전 instance를 반환합니다.
            return instance;
        }
    }
}

FinalizeAttackFinalizer.java

public class FinalizeAttackFinalizer extends FinalizeAttackExample {
    // 생성자
    public FinalizeAttackFinalizer(String importantField) {
        super(importantField);
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        // finalize 메서드에서 instance를 재할당하여 객체를 다시 사용 가능하게 합니다.
        // 이로 인해 객체가 가비지 컬렉션 후에도 재활용될 수 있습니다.
        FinalizeAttackExample.instance = this;
    }
}

main.java

public class Main {
    public static void main(String[] args) {
        FinalizeAttackExample example1 = FinalizeAttackExample.getInstance("Valid Field");
        FinalizeAttackExample example2 = FinalizeAttackExample.getInstance(""); // 예외 발생

        System.out.println(example1 == example2); // true를 출력합니다.
    }
}

위 코드에서는 importantField가 null이거나 비어 있을 때 IllegalArgumentException을 발생시킵니다. 생성자에서 예외가 발생하더라도 finalize() 메서드에서 instance를 재할당하여 객체를 다시 사용할 수 있게 만듭니다. 이는 객체가 완전히 초기화되지 않은 상태에서 다시 사용될 수 있음을 의미하며, 보안상 문제가 될 수 있을것 같습니다!

해결 방법

클래스에 final 키워드를 사용하면 상속을 방지할 수 있습니다. 따라서 공격자가 finalize() 메서드를 오버라이드할 수 없습니다. 혹은 finalize()를 final 키워드를 사용하여 상속을 방지 할 수 있습니다

public final class SecureExample {
    private String importantField;

    // 생성자: 중요한 필드를 초기화하며, 필드가 null이거나 비어 있으면 예외를 발생시킵니다.
    public SecureExample(String importantField) {
        if (importantField == null || importantField.isEmpty()) {
            throw new IllegalArgumentException("Important field must not be null or empty");
        }
        this.importantField = importantField;
    }

    // 기타 메서드
}

or
// 백기선님의 코드 참고
public class SecureExample {
    private String importantField;

    // 생성자: 중요한 필드를 초기화하며, 필드가 null이거나 비어 있으면 예외를 발생시킵니다.
    public SecureExample(String importantField) {
        if (importantField == null || importantField.isEmpty()) {
            throw new IllegalArgumentException("Important field must not be null or empty");
        }
        this.importantField = importantField;
    }

    // final 키워드를 사용하여 finalize 메서드를 오버라이드할 수 없게 합니다.
    @Override
    protected final void finalize() throws Throwable {
        // 자원 해제 코드
        System.out.println("Finalize called");
    }

    // 기타 메서드
}

결론

Java의 finalize() 메서드는 유용할 수 있지만, Finalize Attack과 같은 보안 취약점을 초래할 수 있습니다. 따라서 필요에 따라 finalize() 메서드의 사용하고, 불필요한 경우 제거 또는 더 안전한 자원 정리 방법을 사용하는 것이 좋습니다. Cleaner (Java 9 부터)와 같은 새로운 기능을 활용하여 애플리케이션의 안정성과 보안을 강화할 수 있다고 하는데 나중에 기회가 되면 포스팅 해보는걸로 하겠습니다 :)

profile
Learner

0개의 댓글