본문 바로가기
개발/Java || Spring

여러분의 getter는 안녕하신가요?

by leedonggeun 2023. 7. 2.

저는 공공 클라우드 환경에서의 웹서비스를 개발 및 운영하고 있습니다.
그러다 보니 보안인증(CSAP) 유지를 위해 KISA에게 매년 인프라와 소스코드를 검사받습니다. (어릴 적 숙제 검사 느낌)

그중 소스 취약점에 대한 이야기인데요,
보안인증 중 처음 맡아본 소스 취약점 항목에서 생각지도 못했던 getter에서 문제가 발생했습니다.
바로 우리가 잘 알고 있는 객체지향 프로그래밍(OOP)의 특징 중 하나인 캡슐화에 대한 내용입니다.

취약점의 이름은 [Public 메서드부터 반환된 Private 배열]. 즉, Private으로 선언된 변수에 대해 Public으로 선언된 getter를 통해 배열에 대한 참조를 변경할 수 있다는 취약점입니다.

 

왜 우리는 캡슐화를 잘 지켰다고 생각했을까?

private으로 선언된 변수는 setter만 적절히 제어한다면 외부에서 변경할 수 없다고 생각했을 겁니다.
결론부터 얘기하자면, 일반적으론 맞지만 배열/리스트는 다른 케이스입니다.

예시를 통해 알아보죠.

public class Person {

  private int id;

  private String[] belongings;

  public void setId(int id) {
    this.id = id;
  }

  public int getId() {
    return id;
  }

  public void setBelongIngs(String[] belongings) {
    this.belongings = belongings;
  }

  public String[] getBelongIngs() {
    return belongings;
  }
}

이 Person이라는 객체는 주민번호(id)와 소지품(belongings)이라는 변수를 가지고 있고, 각각 getter와 setter가 있습니다.
주민번호와 소지품은 반드시 setter를 통해서만 변경이 가능합니다.

 

첫 번째 케이스

반드시 setter를 통해 객체를 변경할 수 있고, getter를 통해 객체의 정보를 확인할 수 있습니다.

Person person = new Person();
person.setId(930000);
person.setBelongIngs(new String[] {"지갑", "차키"});

System.out.println("[1] 사용자의 ID: " + person.getId());
System.out.println("[1] 사용자의 소지품: " + Arrays.toString(person.getBelongIngs()));

--------------------------------------------------------------------------------------

[1] 사용자의 ID: 930000
[1] 사용자의 소지품: [지갑, 차키]

 

두 번째 케이스

우리는 이번엔 주민번호와 소지품 변수를 getter를 통해 받아오고, 값을 변경해 볼 겁니다.
이후 만들어진 person 객체의 정보를 다시 한번 조회합니다.

Person person = new Person();
person.setId(930000);
person.setBelongIngs(new String[] {"지갑", "차키"});

// 주민번호
int interceptId = person.getId();
interceptId = 930001;

// 소지품
String[] interceptBelongIngs = person.getBelongIngs();
interceptBelongIngs[0] = "책";

System.out.println("[2] 사용자의 ID: " + person.getId());
System.out.println("[2] 사용자의 소지품: " + Arrays.toString(person.getBelongIngs()));



----------------------------------------------------------------------------------------

[2] 사용자의 ID: 930000
[2] 사용자의 소지품: [책, 차키]

어? 뭔가 이상하죠?

배열이 아닌 주민번호는 정상적으로 원래의 데이터를 참조하고 있습니다.
하지만 배열인 소지품 변수는 원래의 값인 [지갑, 차키]가 아닌 [책, 차키]로 출력되는 것을 확인할 수 있습니다.

즉, getter로 객체의 배열 자체를 넘겨주고 있기 때문에 우리는 객체 바깥에서도 수정이 가능했던 것입니다.
이처럼 Public 메서드를 통해 Private 변수를 변경할 수 있다면 정상적으로 캡슐화를 지키지 못한 것입니다.

 

그럼 어떻게 객체를 보호할 수 있을까?

답은 간단합니다. getter가 배열 자체를 넘겨주는 것이 아니라, 배열의 복사본을 리턴하면 해결됩니다.
다시 한번 Person 객체를 확인해 보겠습니다.

package kr.co.dglee.entity;

import java.util.Arrays;

public class Person {

  private int id;

  private String[] belongings;
  
  ...
  ..

  public String[] getBelongIngs() {
    return Arrays.copyOf(belongings, belongings.length);
  }
}

위처럼 getter에서 배열의 복사본을 넘겨주게 된다면 우리는 캡슐화를 통해 객체를 보호할 수 있습니다.

댓글