Study/Effective Java
Finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.
공29
2023. 5. 19. 06:53
교재 : Effective java, 강의 : inflearn 백기선
📎 Effective java 42p
finalizer 공격 원리는 간단하다. 생성자나 직렬화 과정 (readObject와 readResolve 메서드)에서 예외가 발생하면, 이 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다.
Finalizer 공격 예제
accountId가 푸틴일 경우 계정 생성을 막는 Account 클래스 정의
public class Account {
private String accountId;
public Account(String accountId) {
this.accountId = accountId;
if (accountId.equals("푸틴")) {
throw new IllegalArgumentException("푸틴 계정을 막습니다.");
}
}
public void transfer(BigDecimal amount, String to) {
System.out.printf("transfer %f from %s to %s\n", amount, accountId, to);
}
}
class AccountTest {
@Test
void 일반_계정() {
// 일반 계정은 돈을 보낼 수 있다.
Account account = new Account("keesun");
account.transfer(BigDecimal.valueOf(10.4),"hello");
}
@Test
void 푸틴_계정() {
// 푸틴 계정은 돈을 보낼 수 없다.
Account account = new Account("푸틴");
account.transfer(BigDecimal.valueOf(10.4),"hello");
}
}
Finalizer를 사용하면 푸틴 계정도 돈을 보낼 수 있게끔 할 수 있다.
Account를 상속하면서 finalizer를 오버라이딩하고 transfer를 호출한다.
public class BrokenAccount extends Account {
public BrokenAccount(String accountId) {
super(accountId);
}
@Override
protected void finalize() throws Throwable {
this.transfer(BigDecimal.valueOf(100), "keesun");
}
}
class AccountTest {
@Test
void 푸틴_계정() throws InterruptedException {
// 푸틴 계정은 돈을 보낼 수 없다.
Account account = null;
try {
account = new BrokenAccount("푸틴");
} catch (Exception exception) {
System.out.println("이러면???");
}
System.gc();
Thread.sleep(3000L); // waiting GC...
}
}
출력
이러면???
transfer 100.000000 from 푸틴 to keesun
GC로 인해 finalize가 실행되면서 푸틴 계정이 돈을 보내는 메서드가 호출되었다.
Finalizer 공격을 방어하는 방법
1. final 클래스로 만든다.
public final class Account {
// ...생략
}
누구도 하위 클래스를 만들 수 없으니 이 공격에서 안전하다.
2. finalizer() 메서드를 오버라이딩한 다음 final을 붙여서 하위클래스에서 오버라이딩 할 수 없도록 막는다.
public class Account {
private String accountId;
public Account(String accountId) {
this.accountId = accountId;
if (accountId.equals("푸틴")) {
throw new IllegalArgumentException("푸틴은 계정을 막습니다.");
}
}
public void transfer(BigDecimal amount, String to) {
System.out.printf("transfer %f from %s to %s\n", amount, accountId, to);
}
@Override
protected final void finalize() throws Throwable {
// 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.
}
}
하위 클래스에서 finalize를 오버라이딩 할 수 없기 때문에 이 공격을 막을 수 있다.