설명
template.HTMLAttr는 문자열을 신뢰할 수 있는 HTML 속성으로 취급하며 이스케이프하지 않습니다. 사용자 입력으로 속성 전체를 조합한 뒤 이 타입으로 변환하면, 공격자가 따옴표를 이용해 속성을 벗어나 이벤트 핸들러 등을 삽입할 수 있습니다.
잠재적 영향
- 공격자가 피해자의 페이지에서 JavaScript를 실행할 수 있습니다.
- 페이지에 접근 가능한 정보의 유출, 화면 변조, 사용자 권한을 이용한 요청으로 이어질 수 있습니다.
해결 방법
- 속성 이름과 구조는 템플릿에 고정하고, 값만 일반 문자열로 전달하세요.
- 사용자 입력을
template.HTMLAttr로 변환하지 말고html/template의 맥락별 이스케이프를 사용하세요. - 값의 용도에 맞게 허용 문자와 길이를 제한하세요.
예시
변경 전
go
package main
import (
"html/template"
"net/http"
)
func unsafeHandler(w http.ResponseWriter, r *http.Request) {
// 쿼리 파라미터로부터 사용자 입력 수신
color := r.URL.Query().Get("color") // 예: "red"를 기대
// 비준수: 사용자 입력을 직접 HTML attribute로 조합
// 공격 예: color="red" onclick="alert(1)"
attr := "style=\"color:" + color + "\""
// template.HTMLAttr는 이스케이프를 하지 않음
safeAttr := template.HTMLAttr(attr)
// 이 attr를 템플릿에 그대로 사용
t := template.Must(template.New("page").Parse(`
<html><body>
<p {{.}}>Hello</p>
</body></html>`))
// 악성 입력이 그대로 attribute로 들어가 브라우저에서 실행될 수 있음
_ = t.Execute(w, safeAttr)
}
변경 후
go
package main
import (
"html/template"
"net/http"
"regexp"
)
var pageTmpl = template.Must(template.New("page").Parse(`
<html><body>
<p style="color:{{.Color}}">Hello</p>
</body></html>`))
// 문자 종류와 길이를 제한하는 정규식 (유효한 CSS 색상인지까지 검증하지는 않음)
var colorRe = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,20}$`)
func safeHandler(w http.ResponseWriter, r *http.Request) {
color := r.URL.Query().Get("color")
if !colorRe.MatchString(color) {
// 유효하지 않은 값이면 기본값 사용
color = "black"
}
// html/template에 일반 문자열로 전달하면
// 템플릿 엔진이 context에 맞게 자동 이스케이프
data := struct {
Color string
}{Color: color}
if err := pageTmpl.Execute(w, data); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
설명: 변경 전에는 red" onclick="alert(1) 같은 입력이 새로운 이벤트 속성을 만들 수 있습니다. 변경 후에는 Color를 일반 문자열로 전달해 CSS 값에 맞는 필터링과 이스케이프를 적용합니다. 정규식은 문자 종류와 길이를 제한하며, 모든 허용 값이 유효한 CSS 색상임을 보장하지는 않습니다.