디버그 엔드포인트 노출 (pprof Debug Exposure)

Debug Code Exposure

설명

net/http/pprof를 임포트하면 http.DefaultServeMux에 /debug/pprof/ 핸들러가 자동 등록됩니다. 이름을 붙인 일반 임포트도 동일합니다. 이 기본 mux를 http.ListenAndServe(":8080", nil)처럼 모든 인터페이스에서 제공하고 별도 접근 제어가 없다면, 해당 포트에 도달할 수 있는 사용자가 인증 없이 프로파일 정보를 조회할 수 있습니다. 공격자는 /debug/pprof, /debug/pprof/goroutine, /debug/pprof/heap 등의 엔드포인트를 호출하여 현재 실행 중인 goroutine, 메모리 상태, 내부 경로, 일부 요청 정보 등 민감한 런타임 정보를 수집할 수 있습니다. 이 정보는 직접적인 코드 실행 취약점이 아니더라도, 추가 공격(예: 취약한 핸들러 식별, 비밀 값 추측, 서비스 구조 분석)에 큰 도움을 줄 수 있습니다.

잠재적 영향

  • 디버그 정보 노출: pprof가 제공하는 goroutine, heap, threadcreate 등의 정보로 내부 구조와 실행 상태가 외부에 드러날 수 있습니다.
  • 시스템 정보 노출: 서버의 메모리 사용량, 처리 패턴, 일부 에러 메시지 경로 등 인프라 정보가 유출될 수 있습니다.
  • 추가 공격에 활용(정보 수집): 공격자가 이 정보를 바탕으로 취약한 엔드포인트, 성능 병목, 비정상 동작 등을 분석해 후속 공격(DoS, 취약 API 공략 등)을 정교하게 준비할 수 있습니다.

해결 방법

  • pprof는 localhost나 루프백 주소에 바인딩하고, 외부 서비스는 기본 mux 대신 별도의 http.NewServeMux()를 사용하세요.
  • 필요한 pprof 핸들러는 내부용 mux에 등록하세요. 임포트만으로 기본 mux에도 등록된다는 점을 고려하세요.
  • mTLS, VPN 또는 내부 프록시 등으로 접근을 제한하세요. 비밀번호 인증을 사용한다면 검증된 TLS도 적용하세요.
  • 운영에서 필요하지 않은 pprof는 임포트와 서버 설정에서 제거하고, 필요한 진단만 제한적으로 활성화하세요.
  • 방화벽, 보안 그룹과 Ingress에서 외부 접근을 차단하고, 프록시가 내부 pprof를 다시 공개하지 않는지 확인하세요.

예시

변경 전

go
package main

import (
    "log"
    "net/http"

    _ "net/http/pprof" // pprof를 전체 서버에 자동 등록
)

func main() {
    // 기본 mux(nil)에 pprof 및 다른 핸들러가 함께 등록됨
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    // 취약: 모든 인터페이스(:8080)에 노출, /debug/pprof도 함께 외부에서 접근 가능
    log.Fatal(http.ListenAndServe(":8080", nil))
}

변경 후

go
package main

import (
    "log"
    "net/http"
    "net/http/pprof"
)

// pprof용 내부 전용 서버 설정 예시
func startInternalPprofServer() {
    mux := http.NewServeMux()

    // 내부 서버에 사용할 mux에 pprof 핸들러 등록
    mux.HandleFunc("/debug/pprof/", pprof.Index)
    mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
    mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

    // 내부 주소로만 바인딩 (예: localhost 전용)
    go func() {
        log.Println("internal pprof listening on localhost:6060")
        if err := http.ListenAndServe("localhost:6060", mux); err != nil {
            log.Println("pprof server stopped:", err)
        }
    }()
}

func main() {
    // 내부용 pprof 서버 시작 (운영에서는 방화벽/보안 그룹으로 접근 통제)
    startInternalPprofServer()

    // 기본 mux에는 pprof가 자동 등록되므로 서비스용 mux를 따로 사용
    publicMux := http.NewServeMux()
    publicMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    // 외부 서비스 포트는 pprof를 등록하지 않은 mux로 제공
    log.Fatal(http.ListenAndServe(":8080", publicMux))
}

설명:

  • 변경 전: 기본 mux에 등록된 pprof를 애플리케이션과 같은 포트에서 제공합니다. 그 포트에 도달할 수 있는 사용자는 별도 인증 없이 프로파일 정보를 조회할 수 있습니다.
  • 변경 후: 임포트로 기본 mux에 등록되는 핸들러는 그대로 있지만, 외부 서버는 별도의 publicMux만 사용합니다. 프로파일링 mux는 localhost:6060에서만 제공합니다. 로컬 사용자나 같은 네트워크 네임스페이스의 프로세스, 프록시를 통한 재노출도 고려해 접근을 통제하세요.

참조