위험한 함수(unsafe) 사용

Use of Inherently Dangerous Function

설명

Go의 unsafe 패키지는 포인터를 임의로 변환하거나 메모리 주소를 직접 다룰 수 있게 해 주는 저수준 기능을 제공합니다. 하지만 이 기능은 Go 언어가 기본적으로 제공하는 타입 안전성과 메모리 안전성을 우회하기 때문에, 작은 실수로도 버퍼 오버플로우, 메모리 손상, 예측 불가능한 동작이 발생할 수 있습니다. 공격자는 이런 취약한 코드를 악용해, 잘못 계산된 포인터나 잘못 캐스팅된 타입을 통해 메모리를 덮어쓰거나, 민감한 데이터를 읽거나, 심한 경우 임의 코드 실행과 같은 심각한 공격을 유발할 수 있습니다.

잠재적 영향

  • 메모리 손상 (Memory Corruption): 잘못된 포인터 연산이나 타입 변환으로 다른 변수나 구조체의 메모리를 덮어써 프로그램이 비정상 동작하거나 크래시할 수 있습니다.

  • 정보 노출 (Information Disclosure): 의도하지 않은 메모리 영역을 읽게 되어, 인증 토큰, 비밀번호, 키 등 민감한 데이터가 노출될 수 있습니다.

  • 임의 코드 실행 가능성 (Arbitrary Code Execution 가능성): 메모리 손상이 누적되면 공격자가 제어 가능한 데이터로 함수 포인터/리턴 주소 등을 덮어써 임의의 코드를 실행할 수 있는 기반이 될 수 있습니다.

  • 디버깅 및 유지보수 어려움 (유지보수성 저하): unsafe를 사용하는 코드는 동작을 예측하기 어렵고, 작은 변경에도 치명적인 버그가 생기기 쉬워 장기적인 유지보수가 매우 어려워집니다.

해결 방법

  • 가능한 한 unsafe 패키지 사용 금지: 일반적인 비즈니스 로직, 웹 서비스, API 서버 코드에서는 unsafe를 사용하지 않습니다.

  • 표준 라이브러리/안전한 대체 수단 사용: 성능 최적화가 필요하더라도 우선적으로 Go 표준 라이브러리나 검증된 third-party 라이브러리의 안전한 API를 사용합니다.

  • 꼭 필요할 때만 좁은 범위로 사용: 시스템 프로그래밍 등으로 인해 반드시 unsafe가 필요하다면, 사용 부분을 최소화하고, 작은 헬퍼 함수로 캡슐화하여 재사용/검증이 쉽게 만듭니다.

  • 철저한 검증과 테스트: unsafe를 사용하는 부분에는 단위 테스트, 경계 조건 테스트, fuzzing 등을 적극 적용합니다.

  • 코드 리뷰 필수: unsafe가 등장하는 코드는 반드시 시니어 개발자나 보안 담당자의 코드 리뷰를 거치도록 개발 프로세스에 규정합니다.

  • 문서화: unsafe 사용 이유, 가정(assumption), 제한사항을 코드 주석과 개발 문서로 명확히 남깁니다.

취약한 코드 및 안전한 코드 예시

취약한 코드

안전한 코드

설명:

  • 취약한 코드: 위 비순응 코드는 Go의 unsafe 패키지를 사용해 구조체의 메모리를 직접 조작합니다.

  • unsafe.Pointeruintptr → 다시 unsafe.Pointer 변환을 통해 임의 주소 계산을 수행합니다.

  • 구조체 필드 배치를 개발자가 직접 가정하여 오프셋을 계산하기 때문에, 컴파일러 최적화나 구조체 정렬(padding) 변경이 있으면 잘못된 메모리 위치를 덮어쓸 수 있습니다.

  • Role 필드를 검증 없이 직접 수정하므로, 취약한 코드가 공격자에게 노출되면 권한 상승과 같은 심각한 보안 문제로 이어질 수 있습니다. 이처럼 unsafe 사용은 작은 실수나 환경 변화만으로도 메모리 손상, 예측 불가능한 동작을 야기해 매우 위험합니다.

  • 안전한 코드: 순응 코드는 unsafe 패키지를 전혀 사용하지 않고, 타입 시스템과 명시적인 함수 호출에 의존합니다.

  • 구조체의 필드는 일반적인 필드 접근(u.Role)으로만 수정하며, 포인터 연산이나 수동 오프셋 계산을 하지 않습니다.

  • PromoteToAdmin 같은 명시적 함수로 권한 변경을 수행하므로, 이 함수 안에 인증/인가 체크, 로깅, 감사 로직을 넣어 보안 정책을 강제할 수 있습니다.

  • 타입 안전성이 유지되며, 구조체 레이아웃 변경이 있어도 컴파일 단계에서 오류를 잡을 수 있어 유지보수성과 안전성이 높습니다. 이렇게 unsafe를 사용하지 않고도 대부분의 기능을 구현할 수 있으며, 시스템의 안정성과 보안 수준을 크게 향상시킬 수 있습니다.

참조

Last updated