소프트웨어 개발보안 가이드 분석(2021) : 종료되지 않는 반복문 또는 재귀함수

재귀의 순환횟수를 제어하지 못하여 할당된 메모리나 프로그램 스택 등의 자원을 과다하게

사용하면 위험하다. 대부분의 경우, 귀납 조건(Base Case)이 없는 재귀 함수는 무한 루프에

빠져들게 되고 자원고갈을 유발함으로써 시스템의 정상적인 서비스를 제공할 수 없게 한다.

소프트웨어 개발보안가이드(2021), 한국인터넷진흥원

 

취약점 항목에 대한 이해

 

종료되지 않는 반복문이나 귀납 조건이 없는 재귀함수를 쓸 경우 무한 루프에 빠지게 되고 자원고갈을 유발함으로써 정상적인 서비스를 제공할 수 없게 되는 보안약점

이 문제는 자원 낭비는 물론, 프로그램의 비정상 종료를 초래할 수 있는 보안약점입니다. 재귀 함수와 반복문은 프로그래밍의 필수 요소지만, 잘못 사용되면 '종료되지 않는 로직'으로 이어질 수 있습니다. 재귀는 자기 자신을 호출하여 반복 작업을 수행하지만, 탈출 조건을 명확히 정의하지 않으면 메모리 오버플로우를 일으켜 시스템을 다운시킬 수 있습니다. 반복문도 마찬가지로, 반복을 멈출 명확한 조건이 없다면 CPU를 100% 사용하게 하여 시스템의 다른 작업을 방해할 수 있습니다.

공격 메커니즘과 사고 사례

종료되지 않는 반복문이나 재귀함수로 인한 실제 사고 사례는 프로그램 또는 서비스의 중단을 초래할 수 있으며, 때로는 보안 취약점이 될 수도 있습니다. 예를 들어, 2016년에 GitHub에서는 하나의 재귀적 작업이 프로그램의 예상치 못한 중단을 초래했습니다. 이 문제는 GitHub 내부의 특정 자동화 시스템에서 발생했으며, 결국 시스템 리소스가 고갈되어 서비스에 상당한 차질이 발생했습니다. 이 사고는 특히 GitHub와 같이 대규모 사용자가 의존하는 서비스에서 발생했기 때문에, 개발자 커뮤니티에 큰 영향을 미쳤으며, 재귀적 함수 호출이 제대로 관리되지 않을 때 얼마나 심각한 문제가 발생할 수 있는지를 잘 보여줍니다.

2016년 github DAO exploit 사태는 대표적인 재귀함수 사고 사례로 알려져있습니다.

이와 유사한 사례로, 여러 웹 어플리케이션들에서 입력 검증 실패로 인해 사용자가 작성한 스크립트에 의해 무한 반복문에 빠지게 되는 XSS 공격이 종종 보고되곤 합니다. 이러한 취약점은 사용자의 브라우저 내에서 무한히 코드를 실행하게 만들어, 브라우저나 시스템의 멈춤을 유발할 수 있습니다. 이러한 사고들은 서비스 제공자뿐만 아니라 사용자에게도 직접적인 피해를 줄 수 있기 때문에, 개발 과정에서 이러한 종류의 취약점에 대한 충분한 테스트와 검증이 매우 중요합니다.

 

취약한 코드 및 시큐어코딩 적용 예시

소프트웨어 개발보안가이드(2021)에서는 C코드의 예시가 하단과 같이 작성되어 있습니다.

#include <stdio.h>
int factorial(int i)
{
// 재귀함수 탈출 조건을 설정하지 않아 무한루프가 된다.
return i * factorial(i - 1);
}
int main()
{
int num = 5;
int result = factorial(num);
printf("%d! : %d\n", num, result);
return 0;
}

factorial 함수는 함수 내부에서 자신을 호출하는 재귀함수로, 재귀문을 빠져 나오는 조건을 정의하고 있지 않아 무한 재귀에 빠져 시스템 장애를 유발할 수 있습니다. 때문에 재귀 함수를 구현할 때는 아래와 같이 재귀문을 빠져 나오는 조건인 귀납조건(Base case)을 반드시 구현해야 합니다.

#include <stdio.h>
int factorial(int I)
{
// 재귀함수 사용 시에는 아래와 같이 탈출 조건을 사용해야 한다.
if (i <= 1) {
return 1;
}
return i * factorial(i – 1);
}
int main()
{
int num = 5;
int result = factorial(num);
printf("%d! : %d\n", num, result);
return 0;
}

또 다른 사례를 Java 코드로 예시 들어보겠습니다.

// 취약한 코드 예시
public class VulnerableRecursion {
    public static int recursiveSum(int n) {
        return n + recursiveSum(n - 1); // 탈출 조건이 없음
    }
}

// 안전한 코드 예시
public class SafeRecursion {
    public static int recursiveSum(int n) {
        if (n <= 0) {
            return 0; // 탈출 조건 설정
        }
        return n + recursiveSum(n - 1);
    }
}

위의 취약한 코드 예시에서는 n이 0 이하가 될 때까지 자기 자신을 호출하며, 스택 오버플로우를 유발할 수 있습니다. 안전한 코드 예시에서는 n이 0 이하일 때 재귀 호출을 멈추고 결과를 반환합니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 경쟁조건: 검사시점과 사용시점(TOCTOU)
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 오류 메시지 정보노출