설명
사용자 입력을 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이나 코드로 해석하지 마세요.