[Java] Java 기초 - 상위 클래스에서 새로운 변경자가 추가될 때 발생하는 보안 취약점

Hyunjun Kim·2025년 4월 2일
0

Data_Engineering

목록 보기
21/153

문제 상황
상위 클래스(User)의 password 필드를 변경할 때 보안 로직을 적용하는 하위 클래스(SecureUser)를 만들었다.

하지만 다음 릴리즈에서 상위 클래스에 새로운 변경자(setter)가 추가되면,
자식 클래스의 보안 로직을 거치지 않고 필드가 변경되는 취약점이 발생할 수 있다.

🔹 취약점이 발생하는 코드 예시

// ✅ 상위 클래스 (User)
class User {
    protected String username;
    protected String password; // 중요한 필드

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // 기존 비밀번호 변경 메서드
    public void setPassword(String password) {
        this.password = password;
    }
}

// ⚠ 보안 대책을 추가한 하위 클래스
class SecureUser extends User {
    public SecureUser(String username, String password) {
        super(username, password);
    }

    // ✅ 보안 로직을 적용한 setter (예: 비밀번호 해싱)
    @Override
    public void setPassword(String password) {
        System.out.println("[보안 로직] 비밀번호 해싱 후 저장");
        this.password = hashPassword(password);
    }

    private String hashPassword(String password) {
        return "**hashed**" + password;
    }
}

// ⚠ 다음 릴리즈에서 상위 클래스(User)에 새로운 setter가 추가됨
class UserV2 extends User {
    public UserV2(String username, String password) {
        super(username, password);
    }

    // ⚠ 새롭게 추가된 변경자 (자식 클래스에서는 알지 못함)
    public void resetPassword(String newPassword) {
        this.password = newPassword; // 보안 로직을 거치지 않고 변경됨
    }
}

public class Main {
    public static void main(String[] args) {
        SecureUser user = new SecureUser("Alice", "1234");

        // ✅ 보안 로직이 적용됨
        user.setPassword("newPass");  
        System.out.println("변경된 비밀번호: " + user.password); // **hashed**newPass

        // ⚠ UserV2에서 추가된 새로운 변경자는 SecureUser에서 감지하지 못함
        UserV2 insecureUser = new UserV2("Bob", "1234");
        insecureUser.resetPassword("weakPass");
        System.out.println("변경된 비밀번호: " + insecureUser.password); // weakPass (해싱되지 않음)
    }
}

문제 요약
1. SecureUser 클래스는 setPassword()를 오버라이드하여 보안 로직(비밀번호 해싱)을 적용했다.

  1. 하지만 다음 릴리즈에서 UserV2 클래스에 새로운 변경자(resetPassword())가 추가됨.

  2. resetPassword()는 보안 로직을 거치지 않고 직접 필드를 변경하기 때문에 보안 취약점 발생.

해결 방법
1. 상속을 피하고 Composition을 사용
SecureUserUser를 상속하는 대신, User 객체를 필드로 가지도록 설계

  1. final 키워드 사용
    password 필드를 private final로 선언하고, 변경 메서드를 제한

  2. 상위 클래스에서 변경자를 미리 고려하여 설계
    → 보안이 중요한 필드는 setter를 만들기 전에 충분한 검토가 필요

상속을 사용할 때 부모 클래스의 변경이 자식 클래스에 예기치 않은 영향을 줄 수 있다.
특히, 보안과 관련된 로직을 상속 구조에서 구현하면 위험할 수 있다. 이런 경우에는 상속(Inheritance)보다 구성(Composition)을 사용하는 것이 더 안전하다.

수정된 코드

// ✅ 일반 사용자(User) 클래스
class User {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // ✅ password는 private이므로 직접 접근 불가 (보안 강화)
    public String getUsername() {
        return username;
    }

    protected void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password; // ⚠ 실무에서는 보안상 직접 노출하지 않음
    }
}

// ✅ 보안 강화된 사용자 (Composition 사용)
class SecureUser {
    private final User user;

    public SecureUser(String username, String password) {
        this.user = new User(username, hashPassword(password)); // 초기 비밀번호도 해싱
    }

    // ✅ 안전한 비밀번호 변경 로직
    public void changePassword(String newPassword) {
        System.out.println("[보안 로직] 비밀번호 해싱 후 저장");
        user.setPassword(hashPassword(newPassword));
    }

    public String getUsername() {
        return user.getUsername();
    }

    public String getPassword() {
        return user.getPassword(); // ⚠ 실무에서는 getPassword를 제공하지 않는 것이 일반적
    }

    private String hashPassword(String password) {
        return "**hashed**" + password;
    }
}

public class Main {
    public static void main(String[] args) {
        SecureUser secureUser = new SecureUser("Alice", "1234");

        // ✅ 비밀번호 변경 시 반드시 보안 로직을 거치도록 강제됨
        secureUser.changePassword("newPass");

        System.out.println("변경된 비밀번호: " + secureUser.getPassword()); // **hashed**newPass
    }
}

해결된 점
1. SecureUser가 User를 직접 상속하지 않음 → 부모 클래스 변경으로부터 독립적
2. 비밀번호 변경 시 무조건 보안 로직을 거침 → 새로운 변경자가 추가되어도 안전
3. Encapsulation(캡슐화) 원칙 준수 → password를 private으로 보호
4. 보안 취약점을 원천 차단 → resetPassword() 같은 변경자가 추가되어도 영향을 받지 않음

profile
Data Analytics Engineer 가 되

0개의 댓글