평문 전송(민감 정보 노출)

Cleartext Transmission of Sensitive Information

설명

이 취약점은 웹 서버가 HTTP처럼 암호화되지 않은 프로토콜로 통신할 때 발생합니다. 클라이언트와 서버 사이의 요청·응답이 TLS(HTTPS)로 보호되지 않으면, 네트워크 상에서 데이터가 그대로(평문) 노출됩니다. 공격자는 같은 네트워크에 있거나 중간에 위치하여 패킷을 가로채는 방식(패킷 스니핑, 중간자 공격 등)으로 로그인 정보, 세션 쿠키, 개인정보 등 민감한 데이터를 쉽게 탈취할 수 있습니다.

잠재적 영향

  • 민감 정보 유출: 암호화되지 않은 채 전송되는 계정 정보, 세션 토큰, 개인정보 등이 네트워크 상에서 그대로 노출되어 공격자가 탈취할 수 있습니다.
  • 계정 탈취 및 세션 하이재킹: 공격자가 탈취한 아이디/비밀번호 또는 세션 쿠키를 사용해 사용자인 척 로그인하거나 세션을 가로챌 수 있습니다.
  • 데이터 변조: 공격자가 중간에 트래픽을 가로채 요청·응답 내용을 조작(예: 결제 금액 변경, 응답 내용 위조)할 수 있습니다.
  • 신뢰도 및 규제 위반: HTTPS 미사용으로 인해 서비스 신뢰도가 떨어지고, 개인정보보호 관련 법규나 보안 표준(예: PCI-DSS)을 위반할 수 있습니다.

해결 방법

  • HTTPS 제공: Go가 외부 TLS 연결을 직접 받는다면 http.ListenAndServeTLS를 사용하세요. 신뢰한 리버스 프록시가 TLS를 종료한다면 외부 HTTPS와 프록시·애플리케이션 사이의 보호 경로를 함께 확인하세요.
  • 올바른 인증서 사용: 신뢰할 수 있는 CA에서 발급된 인증서를 사용하거나, 개발·내부 환경에서는 적절히 관리되는 self-signed 인증서를 사용하세요.
  • HSTS 적용: HTTPS 응답에 Strict-Transport-Security를 설정하세요. 브라우저가 정책을 알기 전의 첫 HTTP 요청까지 보호하는 것은 아닙니다.
  • HTTP → HTTPS 리다이렉트: HTTP 포트(예: 80)는 HTTPS(예: 443) 안내에만 사용하세요. 리다이렉트 전에 전송된 비밀번호나 쿠키를 보호하지 못하므로 민감 요청은 처음부터 HTTPS로 보내야 합니다.
  • 민감 데이터 전송 최소화: 꼭 필요한 경우에만 민감 정보를 전송하고, 민감한 쿠키에는 Secure 플래그를 설정해 HTTPS 연결에서만 전송되도록 하세요.

예시

변경 전

go
package main

import (
    "log"
    "net/http"
)

func loginHandler(w http.ResponseWriter, r *http.Request) {
    // 예제 단순화: 실제로는 비밀번호 검증 등이 필요
    username := r.FormValue("username")
    password := r.FormValue("password")

    // 평문 HTTP로 전송되면 username, password가 그대로 노출될 수 있음
    log.Printf("login attempt: %s / %s", username, password)
    w.Write([]byte("ok"))
}

func main() {
    http.HandleFunc("/login", loginHandler)

    // 취약: TLS 없이 HTTP로만 서비스
    // 네트워크 상에서 요청/응답이 모두 평문으로 노출됨
    if err := http.ListenAndServe(":80", nil); err != nil {
        log.Fatal(err)
    }
}

변경 후

go
package main

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

func loginHandler(w http.ResponseWriter, r *http.Request) {
    // 실제 서비스에서는 반드시 추가적인 인증/검증 로직 필요
    username := r.FormValue("username")
    log.Printf("login attempt for username=%q", username)
    w.Write([]byte("ok"))
}

func main() {
    http.HandleFunc("/login", loginHandler)

    // 권장: 80 포트는 HTTPS로 리다이렉트만 처리
    go func() {
        if err := http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            target := (&url.URL{
                Scheme:   "https",
                Host:     "www.example.com", // 배포 설정의 고정 HTTPS 호스트
                Path:     r.URL.Path,
                RawQuery: r.URL.RawQuery,
            }).String()
            http.Redirect(w, r, target, http.StatusMovedPermanently)
        })); err != nil {
            log.Fatal(err)
        }
    }()

    // 안전: TLS 적용 (cert.pem, key.pem은 실제 인증서, 키 파일 경로)
    if err := http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil); err != nil {
        log.Fatal(err)
    }
}

설명:

  • 변경 전: http.ListenAndServe는 TLS 없이 일반 HTTP로 서버를 띄웁니다. 이 경우 /login 요청에 포함된 사용자 아이디, 비밀번호가 네트워크 상에서 암호화되지 않은 평문으로 전송됩니다. 같은 네트워크에 있는 공격자는 패킷 스니핑 도구를 사용해 쉽게 해당 값을 가로채 계정 탈취를 할 수 있습니다.
  • 변경 후: http.ListenAndServeTLS로 HTTPS를 제공하고, 80 포트의 리다이렉트 대상 호스트는 요청의 Host 헤더가 아니라 배포 설정의 고정 HTTPS 호스트를 사용합니다. 사용자 이름은 %q로 인용해 제어 문자가 로그 레코드 구조를 깨지 못하게 하고 비밀번호는 기록하지 않습니다.

참조