이전 포스팅에서는 서비스 거부(DoS) 취약점의 개념과 잘못된 코드 구현으로도 서비스 장애가 발생할 수 있다는 점을 살펴보았습니다.

Oracle Secure Coding Guidelines for Java SE에서는 이러한 문제를 예방하기 위한 다섯 가지 DoS 보안 지침을 제시하고 있습니다. 이번 포스팅에서는 그중 첫 번째 DoS 보안 지침인 DOS-1: Beware of activities that may use disproportionate resources에 대해 학습한 내용을 정리했습니다.


1. DOS-1: Beware of activities that may use disproportionate resources

Oracle의 첫 번째 DoS 보안 지침인 DOS-1: Beware of activities that may use disproportionate resources는 과도한 시스템 자원 소비를 유발할 수 있는 작업에 주의해야 한다는 내용을 담고 있습니다.

애플리케이션은 요청을 처리하기 위해 CPU, 메모리, 디스크 등의 시스템 자원을 사용합니다. 하지만 외부에서 전달받은 데이터의 크기나 요청의 복잡도에 비해 지나치게 많은 자원이 소비될 수 있습니다.

예를 들어 압축 파일은 압축을 해제하는 과정에서 데이터의 크기가 크게 증가할 수 있습니다. 만약 압축 해제 후 생성되는 데이터의 크기를 제한하지 않고 처리한다면 메모리나 디스크 공간이 고갈되어 서비스 장애로 이어질 수 있습니다.

또 다른 예로 정규표현식을 이용한 문자열 검증이 있습니다. 단순한 문자열 검증 요청이라도 정규표현식이 비효율적으로 작성되어 있다면 특정 입력값을 처리하는 과정에서 과도한 연산이 반복되어 CPU 자원을 장시간 점유할 수 있습니다.

이처럼 외부에서 전달받은 데이터의 크기나 요청의 복잡도만으로 실제 처리 과정에서 소비되는 시스템 자원을 예측하기는 어렵습니다. 따라서 데이터 처리 과정에서 발생할 수 있는 자원 소비량을 고려하고, 적절한 제한을 두는 것이 중요합니다.


2. Java 코드로 살펴보는 자원 소비 문제

과도한 자원 소비 문제는 단순한 코드에서도 발생할 수 있습니다. 이를 이해하기 위해 외부에서 전달받은 값으로 메모리 할당량을 결정하는 Java 코드를 살펴보겠습니다.

public byte[] createBuffer(int size) {
    return new byte[size];
}

위 코드는 전달받은 size만큼 바이트 배열을 생성하여 반환합니다. 예를 들어 size가 1024라면 약 1KB의 메모리를 할당하고, 1048576이라면 약 1MB의 메모리를 할당하게 됩니다.

그런데 외부에서 전달받은 size를 아무런 제한 없이 그대로 사용하고 있습니다.

공격자가 의도적으로 매우 큰 값을 전달하면 애플리케이션은 그 크기만큼 메모리를 할당하려고 시도합니다. 이 과정에서 가용 힙 메모리가 부족하면 OutOfMemoryError가 발생하여 해당 요청이 실패할 수 있습니다. 또한 이러한 요청이 동시에 발생하거나 반복되면 메모리 부족이나 GC 부담으로 인해 다른 요청의 처리에도 영향을 줄 수 있습니다.

따라서 외부에서 전달받은 입력값을 검증하지 않고, 시스템 자원 사용에 직접 반영하는 것은 DoS 취약점으로 이어질 수 있습니다.


3. 안전한 코드 작성 방법

앞서 살펴본 코드의 문제는 외부에서 전달받은 입력값을 검증하지 않고 메모리 할당량으로 사용한다는 것입니다.

이를 예방하려면 먼저 입력값이 허용된 범위에 있는지 검증해야 합니다.

public byte[] createBuffer(int size) {
    final int MAX_SIZE = 1024 * 1024;

    if (size < 0 || size > MAX_SIZE) {
        throw new IllegalArgumentException("Invalid size");
    }

    return new byte[size];
}

위 코드는 배열의 크기를 최대 1MB로 제한합니다. 외부에서 전달받은 size가 음수이거나 허용된 최대 크기를 초과하면 메모리 할당을 시도하지 않습니다.

하지만 입력값 검증만으로 모든 자원 고갈 문제를 예방할 수 있는 것은 아닙니다.

예를 들어 압축 파일의 크기가 작더라도 압축을 해제하는 과정에서 데이터의 크기가 크게 증가할 수 있습니다. 따라서 압축 파일의 크기를 제한하는 것뿐 아니라 압축 해제 과정에서 실제로 생성되는 데이터의 누적 크기도 제한해야 합니다.

이처럼 과도한 자원 소비를 예방하기 위해서는 다음 두 가지를 함께 고려해야 합니다.

  • 입력값 검증: 외부에서 전달받은 데이터가 허용된 형식이나 크기, 범위에 있는지 확인합니다.

  • 자원 사용량 제한: 데이터 처리 과정에서 발생하는 자원 소비량을 고려하여 처리 크기, 실행 시간, 동시 작업 수 등에 적절한 제한을 둡니다.

또한 개별 요청의 자원 사용량이 제한되어 있더라도 여러 요청이 동시에 발생하면 시스템 전체의 자원이 고갈될 수 있습니다. 따라서 동시 요청 수와 처리 시간 등도 함께 고려해야 합니다.

결국 DOS-1 지침의 핵심은 외부 입력값을 검증하는 것에서 그치지 않고, 실제 처리 과정에서 소비되는 시스템 자원까지 적절하게 통제하는 것이라고 이해했습니다.


4. 다음 포스팅

다음 포스팅에서는 Oracle Secure Coding Guidelines for Java SE의 두 번째 DoS 보안 지침인 DOS-2: Release resources in all cases에 대해 살펴보겠습니다.

파일이나 데이터베이스 커넥션과 같은 시스템 자원을 사용한 후 적절하게 반환하지 않았을 때 어떤 문제가 발생할 수 있는지 알아보고, 이를 예방하는 Java 코드 작성 방법을 정리해 보려고 합니다.