XSS - 跨站脚本攻击

大多数开发者已经听说过 XSS,但是其中的大部分人从没有尝试使用 XSS 去攻击 Web 程序。

跨站脚本攻击在2003年就已经在 OWASP Top 10,到现在依然是一个常见的漏洞。 在 2013 version 有相当详细的XSS介绍:攻击维度,安全薄弱点,技术影响还有商业影响等。

简单来说就是:

在把输入数据输出到页面之前,如果你能不确保所有用户提供的输入被正确转义,或者不通过服务器端输入验证来验证它是否安全,那么你的 Web 程序将受到威胁。 (source)

Go,就像任何其他多用途编程语言一样,尽管 html/ template 包的文档很清楚,但还是有很多东西会混淆而且让你更加容易受到XSS的攻击。你在网上可以很简单的找到使用 net/ httpio 包写的 “hello world” 的例子,但是你没有意识到你已经受到了 XSS 攻击。

思考一下下面这段代码:

  1. package main
  2. import "net/http"
  3. import "io"
  4. func handler (w http.ResponseWriter, r *http.Request) {
  5. io.WriteString(w, r.URL.Query().Get("param1"))
  6. }
  7. func main () {
  8. http.HandleFunc("/", handler)
  9. http.ListenAndServe(":8080", nil)
  10. }

这个代码片段创建了一个 HTTP Server,跑在 8080 端口(main()),处理到根目录的请求(/)。

请求处理函数 handler() 从查询参数里面获取到 param1 参数的值,然后写到响应流里面(w)。

由于 Content-Type HTTP 响应头没有明确定义,所以将使用遵循 WhatWG spec 规范的 Go 默认值 http.DetectContentType

因此,如果 param1 等于 “test”,那么 HTTP 的响应头 Content-Type 会被设置成 text/plain

Content-Type: text/plain

但是如果 param1 等于 “<h1>”,那么 Content-Type 将会被设置成 text/html

Content-Type: text/html

到这里,你可能会认为如果 param1 等于任何一个 HTML tag,那么 Content-Type 都会被设置成 text/html,事实上不是这样的。当 param1 等于 “<h2>”, “<span>” 或者 “<form>” 的时候,Content-Type 的值是 plain/text 而不是 text/html

现在,我们让 param1 等于 <script>alert(1)</script>

根据 WhatWG spec 规范,如果 HTTP 响应头 Content-Type 的值是 text/html,那么 param1 的值将会被渲染,这就是 XSS - 跨站脚本攻击。

XSS - Cross-Site Scripting

在与 Google 谈论这种情况后,他们告诉我们说:

实际上,自动设置 content-type,打印 html 是很方便而且有意义的,我们希望开发者自己可以使用 html/template 包做适当的转义。

Google 表示,开发者有责任清洗和保护自己的代码。我们完全同意,但是允许自动设置 Content-Type 而不是用 text/plain 作为默认的,在以安全性为优先的语言里面这不是最好的做法。

我们需要清楚一点,text/plain 和(或) text/template 包并不会让你远离 XSS,因为他不会清洗用户的输入。

  1. package main
  2. import "net/http"
  3. import "text/template"
  4. func handler(w http.ResponseWriter, r *http.Request) {
  5. param1 := r.URL.Query().Get("param1")
  6. tmpl := template.New("hello")
  7. tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
  8. tmpl.ExecuteTemplate(w, "T", param1)
  9. }
  10. func main() {
  11. http.HandleFunc("/", handler)
  12. http.ListenAndServe(":8080", nil)
  13. }

使 param1 等于”<h1>”,那么 Content-Type 将会被设置成 text/html,这点会让你受到 XSS 的威胁。

XSS while using text/template package

text/template 包替换成 html/template 包的话,你已经开始安全前行了。

  1. package main
  2. import "net/http"
  3. import "html/template"
  4. func handler(w http.ResponseWriter, r *http.Request) {
  5. param1 := r.URL.Query().Get("param1")
  6. tmpl := template.New("hello")
  7. tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
  8. tmpl.ExecuteTemplate(w, "T", param1)
  9. }
  10. func main() {
  11. http.HandleFunc("/", handler)
  12. http.ListenAndServe(":8080", nil)
  13. }

param1 等于 “<h1>” 时,不仅仅是 Content-Type 的值会被设置成 text/plain

Content-Type: text/plain while using html/template package

而且 param1 的值也会被正确的编码,然后返回给浏览器。

No XSS while using html/template package