2-6. 캡슐화 및 API 오용 가이드

데이터의 은닉(캡슐화) 문제와 부적절한 API 사용으로 인한 취약점 가이드를 **Java**와 **Python** 코드를 통해 학습합니다.


제6절: 캡슐화

1. 잘못된 세션에 의한 데이터 정보노출

세션에 민감한 정보(비밀번호, 중요 권한 등)를 저장하거나, 세션 만료 시간을 부적절하게 설정하여 정보가 노출됩니다.

Java - 취약한 코드세션 정보 노출
/* 취약한 코드 (Java Servlet) */
HttpSession session = request.getSession();
// 세션에 민감 정보를 저장 (예: 비밀번호)
session.setAttribute("userPassword", password); 
// 세션 만료 시간을 너무 길게 설정 (예: 1시간)
session.setMaxInactiveInterval(60 * 60); 
Java - 안전한 코드Fix: 세션 관리
/* 안전한 코드 (Java Servlet) */
HttpSession session = request.getSession(false); // 기존 세션 사용
if (session != null) {
    // 세션에 식별자나 권한 정보만 저장
    session.setAttribute("userId", userId); 
    // 세션 만료 시간은 짧게 설정 (예: 5분)
    session.setMaxInactiveInterval(5 * 60);
}
// 로그아웃 시 session.invalidate()를 반드시 호출해야 합니다.

2. 제거되지 않고 남은 디버그 코드

개발 단계에서 사용한 테스트 계정, 하드코딩된 비밀번호, 상세 디버그 출력 코드가 프로덕션 환경에 남아 정보가 노출됩니다.

Python - 취약한 코드하드코딩된 인증정보
# 취약한 코드 (Python)
import logging
# ... (생략)

# 디버깅 목적으로 하드코딩된 비밀번호 (배포 후 제거 누락)
DEFAULT_ADMIN_PASS = "dev_1234!" 

def check_login(user, password):
    if user == "dev_admin":
        print(f"DEBUG: Attempting login with password: {password}")
        return True
    # ...
Python - 안전한 코드Fix: 환경 변수/조건부 로깅
# 안전한 코드 (Python)
import os 
import logging

# 1. 민감 정보는 환경 변수에서 로드
ADMIN_PASS = os.environ.get("ADMIN_SECRET")

def check_login_secure(user, password):
    # 2. 디버그 로그는 환경 설정에 따라 조건부로 출력
    if os.environ.get("ENV") == "DEBUG":
        logging.debug(f"Attempting login for user: {user}") 
    # ...
    return authenticate(user, password, ADMIN_PASS)

3. Public 메서드로부터 반환된 Private 배열

클래스의 Private 배열을 Public Getter 메서드가 참조 형태로 반환하면, 외부에서 배열 값을 직접 수정할 수 있어 객체의 불변성이 깨집니다.

Java - 취약한 코드참조 반환
/* 취약한 코드 (Java) */
public class UserInfo {
    private String[] privileges = {"VIEW", "EDIT"};

    public String[] getPrivileges() {
        // 내부 배열의 참조(주소)를 직접 반환
        return privileges; 
    }
}
// 외부에서 UserInfo.getPrivileges()[0] = "DELETE"; 로 수정 가능
Java - 안전한 코드Fix: 방어적 복사/불변객체
/* 안전한 코드 (Java) */
public class UserInfoSecure {
    private String[] privileges = {"VIEW", "EDIT"};

    public String[] getPrivileges() {
        // 1. 방어적 복사 (Defensive Copy)를 통해 새로운 배열 반환
        return privileges.clone(); 
    }

    public List getPrivilegesImmutable() {
        // 2. 불변 List로 반환 (권장)
        return Collections.unmodifiableList(Arrays.asList(privileges));
    }
}

4. Private 배열에 Public 데이터 할당

생성자나 Setter를 통해 외부에서 받은 배열(Public)을 Private 배열에 직접 할당하면, 외부에서 원본 배열을 수정하여 Private 배열의 값을 변경할 수 있습니다. (방어적 복사가 누락된 경우)

Java - 취약한 코드참조 할당
/* 취약한 코드 (Java) */
public class Settings {
    private String[] internalSettings;

    public Settings(String[] externalSettings) {
        // 외부 배열의 참조를 내부 변수에 직접 할당 (방어적 복사 누락)
        this.internalSettings = externalSettings; 
    }
}
// 외부에서 externalSettings의 내용을 수정하면 internalSettings도 같이 수정됨
Java - 안전한 코드Fix: 방어적 복사
/* 안전한 코드 (Java) */
public class SettingsSecure {
    private String[] internalSettings;

    public SettingsSecure(String[] externalSettings) {
        // 방어적 복사 (Defensive Copy)를 통해 새로운 배열 생성 후 할당
        this.internalSettings = externalSettings.clone(); 
    }
    // 이제 외부 배열 수정 시 내부 배열에 영향을 미치지 않음
}

제7절: API 오용

1. DNS lookup에 의존한 보안결정

IP 주소 대신 도메인 이름(DNS)을 사용하여 보안 결정(예: 방화벽/접근 제어)을 내리면, 공격자가 DNS 스푸핑 등을 통해 우회할 수 있습니다.

Python - 취약한 코드DNS 기반 인증
# 취약한 코드 (Python)
import socket

def check_access(ip_address):
    # IP 주소로 도메인 이름을 역조회하여 접근 결정 (위험!)
    try:
        hostname = socket.gethostbyaddr(ip_address)[0]
    except socket.error:
        hostname = None
        
    # DNS 룩업 결과에 보안 결정 의존 (공격자는 DNS 스푸핑 가능)
    if hostname and hostname.endswith(".internal-corp.com"):
        return True 
    return False
Python - 안전한 코드Fix: IP 기반 인증
# 안전한 코드 (Python)
import ipaddress

TRUSTED_NETWORKS = ['192.168.1.0/24', '10.0.0.0/8']

def check_access_secure(ip_address):
    try:
        target_ip = ipaddress.ip_address(ip_address)
    except ValueError:
        return False

    # 1. 도메인 대신 IP 주소 또는 IP 대역을 사용하여 보안 결정
    for network in TRUSTED_NETWORKS:
        if target_ip in ipaddress.ip_network(network):
            return True
    return False

2. 취약한 API 사용

보안상 안전하지 않다고 알려진 레거시 함수(예: 난수 생성기, 비활성화된 암호화 함수)나, 부적절한 인증 함수를 사용하여 취약점이 발생합니다.

Java - 취약한 코드약한 난수/암호
/* 취약한 코드 (Java) */
// 1. 예측 가능한 난수 생성기 사용 (암호학적으로 부적합)
Random r = new Random();
int token = r.nextInt(); 

// 2. MD5, SHA-1 등 취약한 해시 알고리즘 사용
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(password.getBytes());
Java - 안전한 코드Fix: 안전한 API 사용
/* 안전한 코드 (Java) */
// 1. 보안적으로 강력한 난수 생성기 사용 (SecureRandom)
SecureRandom sr = new SecureRandom();
byte[] tokenBytes = new byte[32];
sr.nextBytes(tokenBytes);

// 2. 강력한 해시 알고리즘 사용 (SHA-256 이상)
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 또는 비밀번호 해싱을 위해 Bcrypt, Scrypt 등의 라이브러리 사용