HttpOnly 미설정 쿠키 노출

HttpOnly가 없는 민감한 쿠키의 스크립트 접근 위험

설명

HttpOnly 미설정 쿠키 노출은 인증 정보나 세션 ID 같은 민감한 값을 담고 있는 쿠키에 HttpOnly 옵션을 설정하지 않았을 때 발생하는 취약점입니다. HttpOnly가 없으면 JavaScript 같은 클라이언트 측 스크립트가 document.cookie를 통해 쿠키 값을 읽을 수 있습니다. 공격자는 XSS(크로스 사이트 스크립팅) 취약점을 이용해 악성 스크립트를 삽입하고, 그 스크립트로 사용자의 세션 쿠키를 탈취하여 세션 하이재킹(사용자 계정 가로채기)을 할 수 있습니다.

잠재적 영향

  • 세션 탈취 (세션 하이재킹): XSS를 통해 공격자가 document.cookie로 세션 쿠키를 읽어 사용자의 로그인 세션을 가로챌 수 있습니다.
  • 계정 탈취 (Account Takeover): 세션을 탈취한 공격자가 해당 사용자의 권한으로 민감 정보 조회, 설정 변경, 결제 등 모든 행동을 수행할 수 있습니다.
  • 권한 오남용 및 사기 거래: 관리자 계정이나 높은 권한 계정의 쿠키가 탈취되면 시스템 설정 변경, 대량 데이터 유출, 사기성 거래 수행이 가능해집니다.

해결 방법

  • 세션·인증 관련 쿠키에 HttpOnly: true를 설정하세요.
  • 민감한 쿠키에는 HttpOnly와 HTTPS 전용 전송을 위한 Secure를 함께 적용하세요.
  • 쿠키 생성·삭제·갱신용 공통 함수에서 필요한 보안 속성을 기본값으로 설정하세요.
  • XSS 취약점 자체도 제거하세요. HttpOnly는 쿠키 값을 읽는 것을 막지만, 악성 스크립트가 사용자의 인증된 요청을 보내는 것까지 막지는 않습니다.

예시

쿠키 속성을 비교하는 예시입니다. 사용자 인증과 서버 측 세션 저장은 구현되지 않았으며, 실제 로그인 처리에는 두 기능이 필요합니다. HTTPS 예시의 인증서와 키도 배포 환경에 맞게 준비하세요.

변경 전

go
package main

import (
    "crypto/rand"
    "encoding/base64"
    "log"
    "net/http"
    "time"
)

func newSessionID() (string, error) {
    token := make([]byte, 32)
    if _, err := rand.Read(token); err != nil {
        return "", err
    }
    return base64.RawURLEncoding.EncodeToString(token), nil
}

// 취약한 예: 세션 쿠키에 HttpOnly를 설정하지 않음
func setSessionCookie(w http.ResponseWriter, sessionID string) {
    cookie := http.Cookie{
        Name:    "session_id",
        Value:   sessionID,
        Path:    "/",
        // HttpOnly, Secure 누락
        Expires: time.Now().Add(30 * time.Minute),
    }
    http.SetCookie(w, &cookie)
}

func handler(w http.ResponseWriter, r *http.Request) {
    sessionID, err := newSessionID()
    if err != nil {
        http.Error(w, "session creation failed", http.StatusInternalServerError)
        return
    }
    setSessionCookie(w, sessionID)
    w.Write([]byte("ok"))
}

func main() {
    http.HandleFunc("/login", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

변경 후

go
package main

import (
    "crypto/rand"
    "encoding/base64"
    "log"
    "net/http"
    "time"
)

func newSessionID() (string, error) {
    token := make([]byte, 32)
    if _, err := rand.Read(token); err != nil {
        return "", err
    }
    return base64.RawURLEncoding.EncodeToString(token), nil
}

// 안전한 예: 공통 헬퍼로 HttpOnly, Secure를 강제
func setSecureSessionCookie(w http.ResponseWriter, sessionID string) {
    cookie := http.Cookie{
        Name:     "session_id",
        Value:    sessionID,
        Path:     "/",
        Expires:  time.Now().Add(30 * time.Minute),
        HttpOnly: true,          // JS에서 접근 불가
        Secure:   true,          // HTTPS에서만 전송
        SameSite: http.SameSiteLaxMode,
    }
    http.SetCookie(w, &cookie)
}

func handler(w http.ResponseWriter, r *http.Request) {
    sessionID, err := newSessionID()
    if err != nil {
        http.Error(w, "session creation failed", http.StatusInternalServerError)
        return
    }
    // 인증된 사용자와 sessionID의 연관 정보는 서버 측 세션 저장소에 보관합니다.
    setSecureSessionCookie(w, sessionID)
    w.Write([]byte("ok"))
}

func main() {
    http.HandleFunc("/login", handler)
    log.Fatal(http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil))
}

설명:

  • 변경 전: 변경 전 코드에서는 인증/세션을 위한 session_id 쿠키를 만들 때 HttpOnly 필드를 설정하지 않았습니다. 이 경우 브라우저의 JavaScript 코드가 document.cookie를 통해 session_id 값을 읽을 수 있습니다. 애플리케이션에 XSS 취약점이 하나라도 존재하면, 공격자가 악성 스크립트를 주입하여 사용자의 세션 쿠키를 탈취하고, 이를 이용해 세션 하이재킹 및 계정 탈취 공격을 수행할 수 있습니다. 또한 Secure 플래그도 없어 HTTP 트래픽에서 쿠키가 평문으로 노출될 위험이 커집니다.
  • 변경 후: 암호학적으로 안전한 난수로 세션 ID를 발급합니다. 주석에 표시한 인증된 사용자와의 연결·저장은 별도로 구현해야 합니다. 공통 함수에서 HttpOnly, Secure, SameSite를 강제하며 서버도 ListenAndServeTLS로 HTTPS를 제공하므로 Secure 쿠키가 실제로 전송됩니다.

참조