크로스 사이트 스크립팅 (XSS)

Cross-site Scripting (XSS)

설명

사용자 입력을 JavaScript 코드에 직접 이어 붙이면 공격자가 페이지에서 스크립트를 실행할 수 있습니다. template.JS는 값을 신뢰할 수 있는 JavaScript로 표시하는 타입이며, 입력을 검증하거나 이스케이프하지 않습니다.

잠재적 영향

  • 페이지에 접근 가능한 민감 정보를 읽거나 사용자 권한으로 요청을 보낼 수 있습니다.
  • 페이지 내용을 바꾸거나 가짜 입력 폼으로 정보를 수집할 수 있습니다.

해결 방법

  • 사용자 입력을 template.JS로 변환하거나 JavaScript 코드에 직접 이어 붙이지 마세요.
  • html/template에 일반 문자열을 전달해 출력 맥락에 맞게 이스케이프하세요.
  • 아래 예시처럼 데이터 속성으로 값을 전달하고, DOM 요소를 만든 뒤 JavaScript에서 읽으세요.

예시

변경 전

go
package main

import (
    "html/template"
    "net/http"
)

// 비취약 코드 예시가 아니라, 취약한 패턴 예시입니다.

func UnsafeJS(w http.ResponseWriter, r *http.Request) {
    // 사용자 입력 (쿼리 파라미터 id)
    userID := r.URL.Query().Get("id")

    // 취약: 사용자 입력을 문자열 더하기로 HTML/JS에 직접 삽입
    // template.JS로 변환해도 입력은 검증되거나 이스케이프되지 않음
    js := "var msg = '<h1>" + userID + "</h1>'; document.write(msg);"

    // ruleid: unescaped-data-in-js
    safeLike := template.JS(js) // 실제로는 안전하지 않음

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    _, _ = w.Write([]byte("<script>" + string(safeLike) + "</script>"))
}

변경 후

go
package main

import (
    "html/template"
    "net/http"
)

// 안전한 템플릿: 데이터는 {{.UserID}} 자리에 자동 escape되어 들어감
var pageTmpl = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Profile</title></head>
<body>
  <h1>User: {{.UserID}}</h1>
  <div id="user" data-userid="{{.UserID}}"></div>
  <script>
    // 데이터는 data-* 속성이나 JSON encoding 등을 통해 전달하는 것이 좋음
    const userId = document.querySelector('#user').dataset.userid;
    console.log('User:', userId);
  </script>
</body>
</html>
`))

type PageData struct {
    UserID string
}

func SafeJS(w http.ResponseWriter, r *http.Request) {
    userID := r.URL.Query().Get("id")

    // 필요시 서버에서 길이, 허용 문자 등 검증 수행 (예: 영문/숫자만 허용)
    // if !validUserID(userID) { ... }

    data := PageData{UserID: userID}

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    // html/template이 컨텍스트에 맞게 자동으로 escape 처리
    _ = pageTmpl.Execute(w, data)
}

설명: 변경 전에는 사용자 입력을 포함한 스크립트를 응답에 직접 씁니다. 변경 후에는 UserID를 템플릿의 텍스트와 속성 값으로 출력하고, JavaScript는 이미 생성된 요소에서 데이터를 읽습니다. 읽은 값을 다시 HTML이나 코드로 해석하지 마세요.

참조