설명
이 취약점은 웹 서버가 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로 인용해 제어 문자가 로그 레코드 구조를 깨지 못하게 하고 비밀번호는 기록하지 않습니다.