설명
Go 웹 애플리케이션에서 text/template 패키지는 HTML 이스케이프(escape)를 자동으로 해주지 않습니다. 이 상태에서 사용자 입력이나 외부 데이터(Todo 제목, 댓글, 프로필 정보 등)를 그대로 HTML에 렌더링하면, 데이터 안에 포함된 <script>...</script> 같은 자바스크립트 코드가 그대로 브라우저에서 실행될 수 있습니다. 공격자는 저장된 데이터나 파라미터에 악성 스크립트를 넣어 다른 사용자의 브라우저에서 실행되도록 만들어 세션 탈취, 페이지 변조, 피싱 등 다양한 공격을 수행할 수 있습니다.
잠재적 영향
- 세션 탈취 및 계정 악용: 스크립트가 읽을 수 있는 쿠키·토큰을 탈취하거나 사용자의 권한으로 요청을 보낼 수 있습니다.
- 악성 스크립트 실행 (브라우저 내 코드 실행): 피해자 브라우저에서 임의의 자바스크립트가 실행되어 키로깅, 내부 API 호출, 브라우저 내 데이터 접근 등이 가능합니다.
- 페이지 변조 및 피싱 (UI 위조): 화면 내용을 조작해 가짜 로그인 폼, 결제 창 등을 띄워 사용자에게 민감 정보를 입력하도록 속일 수 있습니다.
- 데이터 조작 (클라이언트 측 데이터 위변조): DOM이나 화면상의 데이터가 임의로 변경되어 잘못된 정보 표시, 추가 요청 전송 등의 문제가 발생할 수 있습니다.
해결 방법
- HTML을 렌더링할 때는
text/template대신html/template를 사용하세요. 일반 텍스트 생성에text/template를 쓰는 것 자체는 문제가 아닙니다. - 템플릿은 신뢰한 코드로 고정하고, 외부 데이터는 일반 문자열로 전달해 문맥에 맞는 이스케이프를 적용하세요.
- 사용자 입력을
template.HTML,template.JS등 신뢰된 타입으로 강제 변환하지 마세요. - 추가 방어로 적절한 CSP를 적용해 스크립트 실행을 제한하세요.
- 입력 형식을 검증하되, 위험한 문자열을 걸러내는 방식만으로 출력 인코딩을 대신하지 마세요.
예시
변경 전
go
package main
import (
"html"
"log"
"net/http"
"text/template" // 취약: HTML 자동 이스케이프 미지원
)
// 단순 게시글 구조체
type Post struct {
Title string
Content string // 사용자가 자유롭게 입력하는 내용
}
// text/template 사용 (XSS 위험)
var tmpl = template.Must(template.New("post").Parse(`
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body>
<h1>{{.Title}}</h1>
<div>
내용: {{.Content}}
</div>
</body>
</html>
`))
func handler(w http.ResponseWriter, r *http.Request) {
// 예시: 쿼리 파라미터로부터 제목/내용을 받는다고 가정
title := r.URL.Query().Get("title")
content := r.URL.Query().Get("content")
p := Post{Title: title, Content: content}
if err := tmpl.Execute(w, p); err != nil {
log.Println(err)
http.Error(w, html.EscapeString("template error"), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/post", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
변경 후
go
package main
import (
"html"
"html/template" // 안전: HTML 자동 이스케이프 지원
"log"
"net/http"
)
// 단순 게시글 구조체
type Post struct {
Title string
Content string // 사용자가 자유롭게 입력하는 내용
}
// html/template 사용 (자동 이스케이프)
var tmpl = template.Must(template.New("post").Parse(`
<!DOCTYPE html>
<html>
<head><title>{{.Title}}</title></head>
<body>
<h1>{{.Title}}</h1>
<div>
내용: {{.Content}}
</div>
</body>
</html>
`))
func handler(w http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
content := r.URL.Query().Get("content")
p := Post{Title: title, Content: content}
if err := tmpl.Execute(w, p); err != nil {
log.Println(err)
http.Error(w, html.EscapeString("template error"), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/post", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
설명:
- 변경 전:
text/template패키지는 HTML에 특화된 이스케이프 기능이 없습니다. 위 취약 코드에서는{{.Content}}에 사용자가 입력한 값이 그대로 들어가며, 예를 들어content=<script>alert('xss')</script>와 같이 요청을 보내면 이 스크립트가 브라우저에서 그대로 실행됩니다. 즉, 템플릿이 HTML 문서를 생성하면서 신뢰할 수 없는 입력을 적절히 중화(neutralize)하지 않아 XSS 공격이 가능합니다. - 변경 후:
html/template가 신뢰한 템플릿의 문맥에 맞게 일반 문자열을 처리합니다. 이 예시의{{.Content}}는 텍스트 위치이므로<script>같은 입력도 태그가 아닌 텍스트로 표시됩니다. 외부 입력을 신뢰된 HTML 타입으로 강제 변환하지 않아야 합니다.