위험한 함수(unsafe) 사용

위험한 함수(unsafe) 사용

설명

Go의 unsafe 패키지는 포인터 변환과 직접 메모리 접근을 위한 저수준 기능을 제공합니다. 타입·메모리 안전성을 우회하므로 잘못된 포인터 계산이나 타입 변환은 메모리 손상, 정보 노출, 비정상 종료로 이어질 수 있습니다. 실제 악용 가능성은 입력의 출처와 해당 코드의 동작에 따라 달라집니다.

잠재적 영향

  • 잘못된 주소에 쓰면 변수나 구조체가 손상되고 프로그램이 중단될 수 있습니다.
  • 의도하지 않은 메모리를 읽으면 토큰, 비밀번호, 키가 노출될 수 있습니다.
  • 공격자가 메모리 손상 경로를 제어할 수 있는 경우 임의 코드 실행으로 이어질 수 있습니다.
  • 메모리 배치와 수명에 대한 가정 때문에 유지보수와 검증이 어려워질 수 있습니다.

해결 방법

  • 일반적인 애플리케이션 로직에서는 unsafe 대신 타입이 있는 필드 접근과 안전한 표준 라이브러리 API를 사용하세요.
  • 꼭 필요한 경우 작은 함수로 격리하고 포인터 수명, 정렬, 범위에 대한 가정을 문서화하세요.
  • 경계 조건 테스트, 퍼징, 독립적인 코드 리뷰로 해당 가정을 검증하세요.
  • 권한 변경에는 별도의 인증·인가 검사를 구현하세요. unsafe를 제거하거나 함수로 감싸는 것만으로 권한 검증이 생기지는 않습니다.

예시

변경 전

go
package main

import (
    "fmt"
    "unsafe"
)

type User struct {
    ID   int
    Role int
}

func main() {
    u := User{ID: 1, Role: 0} // 0: 일반 사용자

    // 비순응 코드: unsafe를 이용한 임의 메모리 조작
    // 포인터를 uintptr로 바꿔 임의 오프셋을 더한 뒤 다시 포인터로 변환
    basePtr := unsafe.Pointer(&u)

    // 구조체 레이아웃을 고정이라고 가정하고 ID 다음에 Role이 온다고 믿고 수동 계산
    idSize := unsafe.Sizeof(u.ID)
    rolePtr := (*int)(unsafe.Pointer(uintptr(basePtr) + idSize))

    // 메모리 직접 수정: Role 값을 강제로 1(관리자)로 변경
    *rolePtr = 1

    fmt.Printf("User: ID=%d, Role=%d (관리자 권한으로 변조됨)\n", u.ID, u.Role)
}

변경 후

go
package main

import "fmt"

type Role int

const (
    RoleUser  Role = 0
    RoleAdmin Role = 1
)

type User struct {
    ID   int
    Role Role
}

// 권한 상승 등의 중요한 변경은 항상 명시적인 함수로만 수행
func PromoteToAdmin(u *User) {
    // 권한 변경 정책, 로깅, 감사(audit) 등을 이 안에 구현 가능
    u.Role = RoleAdmin
}

func main() {
    u := User{ID: 1, Role: RoleUser}

    // 비즈니스 로직을 통해서만 Role 변경
    PromoteToAdmin(&u)

    fmt.Printf("User: ID=%d, Role=%d\n", u.ID, u.Role)
}

설명:

  • 변경 전: 필드 오프셋을 직접 계산해 Role을 수정합니다. 표시된 두 int 필드의 배치 자체가 잘못되었다거나 이 프로그램만으로 외부 공격이 가능하다는 뜻은 아닙니다. 구조 변경 뒤에도 수동 계산이 유효한지 확인해야 하는 위험을 보여 줍니다.
  • 변경 후: u.Role에 직접 접근해 수동 주소 계산을 없앱니다. PromoteToAdmin은 예시에서 무조건 역할을 변경하므로 실제 권한 검사와 감사 로직은 별도로 구현해야 합니다.

참조