1. Private 배열 반환의 위험성
CWE-495는 클래스 내부의 private으로 선언된 배열이나 가변 객체(Mutable Object)를 public 메서드를 통해 직접 반환할 때 발생합니다. 자바에서 배열은 참조 타입이므로, 메서드가 원본 배열의 참조 주소를 반환하면 외부 호출자는 이 주소를 통해 클래스 내부의 데이터를 직접 수정할 수 있게 됩니다. 이는 객체의 상태를 외부에서 변경할 수 없도록 설계한 캡슐화(Encapsulation) 원칙을 완전히 무너뜨리며, 시스템의 무결성을 해치는 결과를 초래합니다.
2. 흔히 발생하는 취약한 패턴
주로 클래스 내부의 설정 값이나 권한 목록을 제공할 때 실수하기 쉽습니다.
-
배열 직접 반환:
private String[] roles;를 정의하고public String[] getRoles() { return roles; }와 같이 그대로 내보내는 경우. -
가변 컬렉션 노출:
List,Map등 외부에서 수정 가능한 객체를get메서드로 직접 반환하는 경우. -
얕은 복사(Shallow Copy): 배열의 주소값만 복사하여 전달함으로써 결과적으로 원본 데이터에 접근 가능하게 만드는 경우.
3. 실무적 대응: 방어적 복사(Defensive Copy)
데이터를 외부로 전달할 때는 반드시 원본과의 연결 고리를 끊어야 합니다.
-
방어적 복사 수행: 배열을 반환할 때
clone()메서드나System.arraycopy()를 사용하여 데이터만 복사된 새로운 배열을 생성해 반환합니다. -
불변 객체로 변환: 컬렉션의 경우
Collections.unmodifiableList()등을 사용하여 수정이 불가능한 뷰(View)를 제공합니다. -
필요한 값만 전달: 배열 전체를 넘기기보다 특정 인덱스의 값만 제공하거나, 복사된 리스트를 반환하는 방식을 고려합니다.
4. CWE-495 대응 및 안전한 데이터 반환 자바 코드 예시
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class UserSecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(UserSecurityConfig.class);
// [보안 위험] private 배열이지만 직접 반환 시 외부에서 수정 가능
private String[] userRoles = {"USER", "GUEST"};
/* [위험한 코드]
public String[] getUserRoles() {
return userRoles; // 참조 주소가 노출됨 (CWE-495)
}
*/
// [CWE-495 조치] 방어적 복사를 통해 새로운 배열을 반환
public String[] getUserRoles() {
if (this.userRoles == null) {
return new String[0];
}
// 원본 배열을 복제하여 반환함으로써 내부 데이터를 보호함
return this.userRoles.clone();
}
public void printRoles() {
logger.debug("Current Internal Roles: {}", Arrays.toString(userRoles));
}
}
코멘트: "Private이니까 안전하겠지"라는 생각은 자바의 참조 매커니즘 앞에서 무용지물입니다. 외부에서 내가 관리하는 배열의 값을 임의로 바꾼다면, 권한 체크 로직이나 시스템 설정이 순식간에 우회될 수 있습니다. logger.debug()를 통해 내부 상태를 모니터링하되, 데이터를 내보낼 때는 항상 "복사본을 준다"는 원칙을 지키십시오. 이것이 캡슐화를 완성하는 시큐어 코딩의 핵심입니다.
댓글 달기