설명
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쿠키가 실제로 전송됩니다.