Link Search Menu Expand Document

2. XSS 跨站腳本攻擊

2-1 什麽是 XSS 跨站腳本攻擊?

我們在實戰應用「Rich Editor 編輯器」有介紹過 XSS 跨站腳本攻擊:如果用戶可以自己張貼內容到網站上,並且網站會顯示出來的話,就有可能有 XSS 漏洞。惡意用戶會張貼 JavaScript 代碼上來,那麽這麽當其他用戶瀏覽到這一頁時,瀏覽器就會盲目的執行 JavaScript 代碼。

讓我們攻擊看看,請用 hacker@example.org 登入,然後針對一則活動留言,貼上以下 JavaScript 代碼:

<script>
$.ajax({
  url: $(location).attr('href') + "/comments",
  method: "POST",
  data: {  comment : { content: "啊哈哈哈~你看看你! (σ゚∀゚)σ゚∀゚)σ゚∀゚)σ" } },
  dataType: "JSON"
})
</script>

image

留言完之後,接下來任何人看到這一頁,就會執行這段 Ajax 代碼,也跟著留言一次。請多重新整理頁面幾次看看。

你也可以測試看看其他人瀏覽會怎麽樣?你可以用另一個瀏覽器(例如 Safari),或是用 Chrome 的 Incognito Windows(無痕模式,這會開啟一個乾凈沒有 Cookie 的新視窗),然後改用 admin@example.org 登入,然後瀏覽剛剛那一個活動…

image

image

admin 也會自動張貼留言了….

2-2 如何防禦 XSS?

要防禦說起來簡單,就是顯示每個用戶貼文的地方都要脫逸(或叫做轉義) HTML,打開 app/views/events/show.html.erb

-  <%= raw comment.content %>
+  <%= comment.content %>

Rails 預設就會脫逸(轉義) HTML 了,用 raw comment.contentcomment.content.html_safe 反而是告訴 Rails 不要脫逸 HTML。拿掉 raw 之後,就會脫逸顯示出原代碼,例如 < 會變成 &lt;

image image

不過這樣會完全防止任何 HTML 標籤,例如我們張貼以下這張圖片:

<img src="http://placehold.it/200x200" />

image

一樣被脫逸了 :(

image

如果我們還是希望用戶可以輸入 HTML,就像在實戰應用「Rich Editor 編輯器」的需求,請改用 sanitize 白名單過濾,只允許部分的 HTML 標籤:

-  <%= comment.content %>
+  <%= sanitize comment.content %>

image

2-3 另一個不小心的漏洞

請點主選單的修改個人資料,這個地方會不會有漏洞呢?

image

回到任一個活動頁面:

image

發現竟然有漏洞,這段 JavaScript 被執行了。

讓我們找找看漏洞出現在哪裡,原來是在留言顯示用戶名稱的地方,沒有做好脫逸 :(

    <div class="panel-heading">
      <%= user_avatar_link(comment.author) %>
    </div>
require 'digest/md5'

module UsersHelper

  def user_avatar_link(user)
    # https://cn.gravatar.com/
    email_md5 = Digest::MD5.hexdigest(user.email)
    gravatar_url = "https://www.gravatar.com/avatar/#{email_md5}"

    str = "<div class ='user-link'>" + link_to(image_tag(gravatar_url), user_path(user)) + " " + user.display_name + "</div>"

    str.html_safe
  end

end

這個 user_avatar_link helper 的目的是輸出用戶的 Gravater 照片的連結,以及用戶的顯示名稱,而問題就出在 str.html_safe 太過放縱了。

但是如果你把 str.html_safe 這行拿掉,你會發現整個 Helper 的輸出都被脫逸了 :( 不是我們想要的結果。

image

這是因為 Rails 會追蹤每個字符串是否是安全的,如果不安全的話,輸出時會自動脫逸。不安全的字符串 + 安全的字符串,會變成不安全的。由於字符串 "<div class ='user-link'>" 預設是不安全的,因此後來累加字符串進來,整個 str 都是不安全的,所以 Rails 最後會讓整個 str 都脫逸,造成圖片 img 標籤也無法顯示。

2-4 解法辦法

解法辦法一

小心翼翼地在不需要脫逸的地方加上 .html_safe

require 'digest/md5'

module UsersHelper

  def user_avatar_link(user)
    # https://cn.gravatar.com/
    email_md5 = Digest::MD5.hexdigest(user.email)
    gravatar_url = "https://www.gravatar.com/avatar/#{email_md5}"

-   str = "<div class ='user-link'>" + link_to(image_tag(gravatar_url), user_path(user)) + " " + user.display_name + "</div>"
-   str.html_safe

+   "<div class ='user-link'>".html_safe + link_to(image_tag(gravatar_url), user_path(user)) + " " + user.display_name + "</div>".html_safe
  end

end

image

解法辦法二

可以用 Rails 內建的 content_tag helper 產生標籤,結果一樣,只是寫起來會比字符串相加好看一點:

require 'digest/md5'

module UsersHelper

  def user_avatar_link(user)
    # https://cn.gravatar.com/
    email_md5 = Digest::MD5.hexdigest(user.email)
    gravatar_url = "https://www.gravatar.com/avatar/#{email_md5}"

-   "<div class ='user-link'>".html_safe + link_to(image_tag(gravatar_url), user_path(user)) + " " + user.display_name + "</div>".html_safe

+   content_tag(:div,
      link_to(image_tag(gravatar_url), user_path(user)) + tag("") + user.display_name ,
      :class => "user-link" )
  end

end

2-5 小結

由於 Rails 預設會脫逸 HTML 的關系,所以 Rails 對 XSS 是有了基本的防禦。如果不是 Rails 這種的框架幫你預設脫逸的話,網頁上只要顯示用戶的輸出,都有可能是個安全漏洞。

不過即使有 Rails 的保護,你也有可能在字符串相加的過程中,不小心太過放縱而造成漏洞,就像這一節所示範的。這時候就必須小心處理那些是安全的字符串、那些是用戶輸入的不安全字符串。


Copyright © 2010-2021 Wen-Tien Chang All Rights Reserved.