의미만 들어보면 리플렉션은 보안적인 문제가 있을 가능성이 있어보이는데, 실제로 그렇게 생각하시나요? 만약 그렇다면, 어떻게 방지할 수 있을까요?

네, 실제로 보안 문제가 존재합니다. private 접근 제어를 런타임에 우회할 수 있다는 것 자체가 보안 취약점이 될 수 있습니다.

어떤 보안 문제가 있나?

1. 캡슐화 파괴

class BankAccount {
    private int balance = 1000000;  // private으로 보호
    private String password = "secret123";
}

// 리플렉션으로 private 필드 강제 접근
Field balanceField = BankAccount.class.getDeclaredField("balance");
balanceField.setAccessible(true);  // private 무력화
balanceField.set(account, 999999999);  // 잔액 임의 변경 X

Field passwordField = BankAccount.class.getDeclaredField("password");
passwordField.setAccessible(true);
String password = (String) passwordField.get(account);  // 비밀번호 탈취 X

2. 싱글톤 파괴

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() { return INSTANCE; }
}

// 리플렉션으로 private 생성자 강제 호출
Constructor<Singleton> constructor =
    Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();  // 두 번째 인스턴스 생성 X

System.out.println(Singleton.getInstance() == instance2);  // false

3. 직렬화 취약점

// 역직렬화 시 리플렉션으로 생성자 없이 객체 생성 가능
// → 악의적인 바이트스트림으로 임의 객체 생성 공격 가능
ObjectInputStream ois = new ObjectInputStream(maliciousStream);
Object obj = ois.readObject();  // 리플렉션 기반 역직렬화 X

방지 방법

1. 싱글톤 - 열거형(Enum) 사용

// O Enum은 리플렉션으로 생성 자체가 불가능
public enum Singleton {
    INSTANCE;

    public void doSomething() { ... }
}

// 시도해도 예외 발생
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.newInstance();  // IllegalArgumentException: Cannot reflectively create enum objects

2. 생성자에서 방어 코드

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private static boolean created = false;

    private Singleton() {
        // 두 번째 생성 시도 차단
        if (created) {
            throw new RuntimeException("리플렉션으로 생성 불가");  // O
        }
        created = true;
    }
}

3. SecurityManager 설정 (Java 17 이하)

// 리플렉션 접근 자체를 정책으로 제한
System.setSecurityManager(new SecurityManager() {
    @Override
    public void checkPackageAccess(String pkg) {
        if (pkg.startsWith("com.example.secure")) {
            throw new SecurityException("접근 불가 패키지");  // O
        }
    }
});

4. Java 9+ 모듈 시스템

// module-info.java
module com.example {
    // exports만 선언된 패키지만 외부 접근 가능
    exports com.example.api;

    // opens 없으면 리플렉션 접근 불가
    // opens com.example.internal;  // 선언 안 하면 리플렉션 차단 O
}

// 모듈로 보호된 클래스에 리플렉션 시도
Field f = ProtectedClass.class.getDeclaredField("secret");
f.setAccessible(true);  // InaccessibleObjectException 발생 O

5. 역직렬화 방어

// O ObjectInputFilter로 허용 클래스만 역직렬화
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filterInfo -> {
    Class<?> clazz = filterInfo.serialClass();
    if (clazz != null && !allowedClasses.contains(clazz)) {
        return ObjectInputFilter.Status.REJECTED;  // 허용 목록 외 차단
    }
    return ObjectInputFilter.Status.ALLOWED;
});

정리

취약점방지 방법
싱글톤 파괴Enum 사용 또는 생성자 방어 코드
private 필드 접근Java 9+ 모듈 시스템
역직렬화 공격ObjectInputFilter 화이트리스트
패키지 접근SecurityManager (17 이하)

결국 리플렉션의 보안 문제는 "강력한 기능에는 그만큼의 책임이 따른다" 는 것입니다. 프레임워크 내부에서는 필수적이지만, 일반 애플리케이션 코드에서 setAccessible(true)를 남발하는 것은 설계가 잘못됐다는 신호로 봐야 합니다.

CSLOG에서 보기