
소프트웨어 개발보안 가이드 분석(2021) : 잘못된 세션에 의한 데이터 정보 노출
다중 스레드 환경에서는 싱글톤(Singleton) 객체 필드에 경쟁조건(Race Condition)이 발생할 수
있다. 따라서, 다중 스레드 환경인 Java의 서블릿(Servlet) 등에서는 정보를 저장하는 멤버 변수가
포함되지 않도록 하여, 서로 다른 세션에서 데이터를 공유하지 않도록 해야 한다.
소프트웨어 개발보안가이드(2021), 한국인터넷진흥원
취약점에 대한 이해 : 객체 지향 프로그래밍(OOP)와 캡슐화
객체 지향 프로그래밍(OOP)은 소프트웨어 개발에서 가장 널리 사용되는 패러다임 중 하나입니다. OOP의 핵심 원칙 중 하나인 캡슐화는 데이터와 그 데이터를 처리하는 메소드를 하나의 단위로 묶어 관리하는 것을 말합니다.

이 원칙은 클래스의 데이터를 외부로부터 보호하고, 객체의 상태를 직접적으로 변경하지 못하게 함으로써 데이터의 안정성과 신뢰성을 높입니다. 그러나 이 원칙이 잘못 적용되거나 무시될 때, 심각한 보안 취약점이 발생할 수 있습니다. 본문에서는 Java 기반의 웹 애플리케이션 개발 시 발생할 수 있는 "잘못된 세션에 의한 데이터 정보 노출" 취약점에 대해 논의합니다.
공격 메커니즘
"잘못된 세션에 의한 데이터 정보 노출"은 캡슐화 원칙에 어긋나는 코딩 관행으로 인해 발생합니다. 특히, 웹 애플리케이션에서 싱글톤 패턴의 객체나 정적 변수들이 여러 세션 간에 공유될 때 이 취약점이 두드러집니다. 이로 인해 한 사용자의 데이터가 다른 사용자에게 노출될 수 있으며, 이는 정보 유출로 이어질 수 있습니다.

이 다이어그램은 Controller 내의 멤버 변수 사용이 어떻게 다른 세션 간의 정보 노출로 이어질 수 있는지를 시각적으로 잘 보여줍니다. 요청 간에 공유되어서는 안 되는 데이터는 멤버 변수에 저장되어서는 안 되며, 대신 각 요청의 스코프 내에서만 유효한 지역 변수로 처리되거나 ThreadLocal을 사용하여 안전하게 관리되어야 합니다.
취약한 코드 및 시큐어코딩 적용 방안
안전하지 않은 코드 사례
▶ JSP에서의 변수 공유 문제
<%! // JSP 선언부에서의 멤버 변수 선언
String username = "/";
%>
위 예시처럼 JSP 선언부에 변수를 선언하면, 해당 변수는 JSP 페이지에 접근하는 모든 사용자 간에 공유됩니다. 이는 데이터 정보 노출의 위험을 증가시킵니다.
▶ Controller에서의 멤버 변수 공유 문제
@Controller
public class ExampleController {
private int sharedData; // 멤버 변수로 선언되어 스레드 간에 공유됨
}
위와 같이 Controller에 멤버 변수를 사용하면, 다중 스레드 환경에서 동기화 문제로 인해 데이터 노출이 발생할 수 있습니다.
안전한 코드 사례
▶ JSP에서의 안전한 변수 선언
<% // 서블릿 선언부에서의 로컬 변수 선언
String localData = "/";
%>
JSP의 서블릿 선언부에서 변수를 로컬로 선언하면, 각 요청에 대해 독립적인 변수가 생성되어 데이터 공유 문제가 발생하지 않습니다.
▶ Controller에서의 지역 변수 사용
@Controller
public class SafeExampleController {
public void safeMethod() {
int localData; // 지역 변수로 선언
}
}
메소드 내에서 지역 변수를 사용하면 스레드 간의 데이터 공유를 방지할 수 있습니다.
ThreadLocal을 사용한 시큐어코딩 적용 방안
ThreadLocal은 자바에서 제공하는 기능으로, 각 스레드에 데이터의 복사본을 제공하여 다른 스레드와의 데이터 공유 문제를 해결할 수 있습니다. 이를 활용하면, 웹 애플리케이션에서 발생할 수 있는 "잘못된 세션에 의한 데이터 정보 노출" 취약점을 방지할 수 있습니다. 아래는 ThreadLocal을 사용하여 사용자 세션의 정보를 안전하게 관리하는 Java 코드 예시입니다.
public class UserSessionManager {
// ThreadLocal 객체를 사용하여 각 스레드별로 사용자 세션 정보를 별도로 관리합니다.
private static final ThreadLocal<UserSession> userSessionThreadLocal = new ThreadLocal<>();
// 사용자 세션을 ThreadLocal에 저장하는 메소드
public static void setUserSession(UserSession userSession) {
userSessionThreadLocal.set(userSession);
}
// 현재 스레드의 사용자 세션 정보를 가져오는 메소드
public static UserSession getUserSession() {
return userSessionThreadLocal.get();
}
// 사용자 세션 정보를 삭제하는 메소드. 요청 처리가 완료될 때 호출해야 합니다.
public static void clearUserSession() {
userSessionThreadLocal.remove();
}
}
class UserSession {
// 사용자 세션 관련 정보를 저장하는 클래스
private String userId;
private String userName;
// 필요한 세션 관련 정보를 여기에 추가합니다.
// 생성자, Getter, Setter 등 필요한 메소드 구현
}
- 세션 정보 설정: 사용자가 로그인하거나 세션 정보가 필요한 시점에서 UserSessionManager.setUserSession(userSession)을 호출하여 ThreadLocal에 사용자 세션 정보를 저장합니다.
- 세션 정보 사용: 사용자의 요청을 처리하는 동안 UserSessionManager.getUserSession()을 호출하여 현재 스레드의 사용자 세션 정보에 접근합니다.
- 세션 정보 제거: 요청 처리가 완료된 후 UserSessionManager.clearUserSession()을 호출하여 ThreadLocal에서 현재 스레드의 사용자 세션 정보를 제거합니다. 이는 메모리 누수를 방지하는 중요한 단계입니다.
※ (주의) ThreadLocal을 사용할 때는 항상 사용 후 해당 스레드의 로컬 변수를 정리하는 것이 중요합니다. 이를 통해 메모리 누수와 같은 문제를 방지할 수 있습니다.