安全限制


JavaScript 可以在瀏覽器執行程式,這就牽涉到許多安全性問題,使用者下載了網頁,下載了 JavaScript 程式碼偷偷地在執行,有些動作必須加以限制,以免使用者的電腦遭竊取資料、讀寫或破壞,最基本的,瀏覽器不允許 JavaScript 對客戶端電腦作檔案存取的動作。

安全還有其他許多因素要考量,舉例來說,在過去,瀏覽器上可以設狀態列文字,不過這也會有安全疑慮,因為在某些情況下,使用者可以透過 JavaScript 來變造狀態列訊息來欺瞞使用者,狀態列文字的設定有所疑慮,事實上,新的瀏覽器也不再包括狀態列了。

使用 JavaScript 可以另開視窗,不過沒辦法開啟太小的視窗,這是個很理所當然的考量,因為若可以開很小的視窗,使用者沒注意到它,那在這個小視窗中就可以偷偷摸摸作些壞事了。

在瀏覽器中基於安全,<input>typefile 時,你設定的 value 會被瀏覽器忽略,只能由使用者親自選取檔案。你透過 JavaScript 來設定檔案選取框的 value 值也沒用。

那麼,如果想要設定一個按鈕,讓使用者可以清除選取的檔案要怎麼作?透過 JavaScript 將 value 設為空字串是沒用的。不過可以重新要求瀏覽器建立新的選取框。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

      <div id="fileupload"><input name="upload" type="file"></div>
      <button id="clear">Clear</button>

<script type="text/javascript">
    document.getElementById('clear').onclick = function() {
        let fileupload = document.getElementById('fileupload');
        fileupload.innerHTML = fileupload.innerHTML;
    };
</script>

</body>
</html>

按此觀看執行結果

在上例中,若選取了檔案,並按下 Clear 按鈕時,取得的 innerHTML 會是靜態的 <input name="upload" type="file">,當它被指定回 innerHTML,瀏覽器會重新剖析,等於要求瀏覽器丟掉舊的 DOM,根據指定的 HTML 建立新的 DOM,在使用者的觀感中,自然就像是清除了選取框的內容。

再舉使用 JavaScript 取得檔案大小為例,在 Firefox、Chrome 中,可以透過 files.item(0).size 特性來取得檔案大小。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

      <input id="upload" name="upload" type="file"><br>
      <button id="size">Size</button><br>
      <span id="console"></span>  

<script type="text/javascript">
    document.getElementById('size').onclick = function() {
        let size = document.getElementById('upload').files.item(0).size;
        document.getElementById('console').innerHTML = 'Size: ' + size;
    };
</script>  

</body>
</html>

按此觀看執行結果

舊版 Internet Explorer 無法使用以上方法,在古早的 Internet Explorer 6 是透過建立 ImageActiveXObjectScripting.FileSystemObject)來取得檔案大小,但因安全性問題,在後來的 Internet Explorer 就行不通了。

類似的限制還有 iframe。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <iframe id="page" name="page" src="https://openhome.cc"></iframe>
    <span id="console"></span>

<script type="text/javascript">
    window.onload = function() {    
        alert(window.frames['page'].document.body.innerHTML);
    };
</script>  

</body>
</html>

你可以取得 iframe 的內容,不過僅限與這個文件有相同來源的其他網頁才可以,例如,放在我的網站 就可以,放在 JS Bin 就不行。

若沒有這個限制,如果你是在內部網域,我指定你內部網域中某個內部網站的網址,就可以取得網頁的內容,之後再上傳,也就是說,沒有這個限制,我就可以偷你內部網站中的東西。

如果使用 Ajax,建立的 Ajax 非同步物件也會有類似的限制,預設只能使用非同步物件來取得與文件相同來源的其他網頁,不同來源的文件在過去是直接被禁止的,而新的規範中,必須伺服器有支援相對應的協議才可以。

這稱之為同源策略(Same-origin policy)。文件相同來源指的是,與目前文件的 URL 協定、主機與埠號相同的其他文件,才可以被 JavaScript 取得,只要 URL 協定、主機與埠號其中一個不同,就不可以取得。

例如若目前文件來源為 http://caterpillar.onlyfun.net:80/Gossip/demo.html。那麼以下視為不同源…

  • https://caterpillar.onlyfun.net:80/Gossip/demo.html(協定不同)
  • http://openhome.cc:80/Gossip/demo.html(主機資訊不同,即使實體機器是同一台)
  • http://192.168.0.1:80/Gossip/demo.html(視為主機資訊不同)
  • http://caterpillar.onlyfun.net:8080/Gossip/demo.html(埠號不同)

<script>src 可以指定外部 URL,來自不同 URL 的 .js 檔案,可以在同一個頁面中運作,呼叫彼此變數或函式,但它們可取得的頁面必須是與目前頁面相同來源,而不是與 .js 檔案相同來源。

子網域的文件,預設是不同源,但可以透過 document.domain 設定子網域為同源,document.domain 預設就是文件來源,例如若是 http://caterpillar.onlyfun.net:80/Gossip/demo.html,則 document.domain 就是 'caterpillar.onlyfun.net'。預設無法存取 ooo.onlyfun.net、xxx.onlyfun.net 的文件,可以將 document.domain 設定為 'onlyfun.net',如此來自 ooo.onlyfun.net、xxx.onlyfun.net 的文件就會被視為同源,注意! document.domain 只能設為頂層網域,不能設其他網域。

以上簡介的是一些常見的 JavaScript 安全議題,實際上還有更多,甚至有些瀏覽器沒限制的,也可能被防毒軟體或防火牆防堵。