설명
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은 예시에서 무조건 역할을 변경하므로 실제 권한 검사와 감사 로직은 별도로 구현해야 합니다.