談談JS中的函式劫持

Jrain發表於2018-12-27

寫於 2016.09.21

說到劫持,第一反應可能是什麼不好的東西。函式劫持並不邪惡,關鍵是看使用的人。雖然這個概念在前端領域使用較少,但是在安全領域、自定義業務等場景下還是有一定的使用價值的。所以,這一篇文章將會和大家一起去了解一下JS中的函式劫持是什麼,有什麼用。

基本概念

函式劫持,顧名思義,即在一個函式執行之前把它劫持下來,新增我們想要的功能。當這個函式實際執行的時候,它已經不是原本的函式了,而是帶上了被我們新增上去的功能。這也是我們常見的鉤子函式的原理之一。

乍一看上去,這很像是函式的改寫。函式的改寫也可以理解為是函式劫持的一種,但是這種方式太噁心了。作為一個劫持者,在綁票獲得好處以後也應該遵守職業道德,把人原封不動地還回去,所以我們得在合適的地方把函式原本的功能給重新呼叫回來。

推而廣之,其實“劫持”這一概念我們經常會遇到,比方說某網站被運營商劫持了,在瀏覽該網站的時候會彈出運營商的廣告。

舉例分析

現在我們來舉個簡單的例子,劫持一下alert()函式,為它增添一點小小的功能:

let warn = alert
window.alert = (t) => {
    if (confirm('How are you?')) warn(t)
}

alert('Help me...!!!')
複製程式碼

可以開啟開發者工具嘗試一下這個例子,你會發現只有你在confirm裡面點選了OK,才會彈出Help me...!!!

接下來我們把這部分的內容封裝一下,成為一個通用的函式:

const hijack = (obj, method, fun) => {
  let orig = obj[method]
  obj[method] = fun(orig)
}
複製程式碼

首先我們定義了一個hijack函式,它會先把原函式給儲存下來,然後執行自定義函式,而原函式將會在自定義函式內部進行呼叫。

然後我們來劫持confirm()函式:

hijack(window, 'confirm', (orig) => {
  return (text) => {
    alert('HELP ME PLZ!!!')
    if (orig.call(this, text)) {
      alert('YOU SEEMS FINE AND I AM LEAVING, GOOD BYE!')
    } else {
      alert('HOLD ON! I AM COMING!!')
    }
  }
})
複製程式碼

這段函式的功能很簡單就不詳細說明了,直接呼叫confirm()你就知道了: codepen例子

反劫持

新建一個頁面,開啟你的開發者工具控制檯,輸入alert,你會看到這樣的輸出:

function alert() { [native code] }
複製程式碼

然後使用本文開頭的那段程式碼,把alert()劫持一下,再重新在控制檯輸入alert,你會看到這樣的輸出:

function (t) => {
    if (confirm('How are you?')) warn(t)
}
複製程式碼

通過上述的例子可以知道,要看一個函式是否被劫持了,只需要直接把它列印出來即可。針對系統原生的函式,[native code]即代表它是純淨無汙染的。

函式劫持的作用

除了為函式增加功能以外,還能夠利用函式劫持去追蹤惡意使用者的資訊。一般的XSS攻擊會先利用alert()等能夠輸出資訊的方法進行測試,這時候我們可以先對原生alert()進行劫持,向其輸入追蹤資訊的程式碼,最後才把原函式釋放出去。當惡意使用者在測試alert()的時候就會立即被我們追蹤,而他本人卻無從察覺。

後記

關於JS的函式劫持,也不是什麼新鮮的東西,只是在最近的工作中遇到了這個知識點感覺比較陌生,所以花了一些時間進行了研究,並把結果記錄下來。如果發現有什麼錯漏的地方歡迎指正!

感謝你的閱讀,歡迎關注我的專欄,我將不定期分享自己的學習體驗,開發心得,搬運牆外的乾貨。下次見啦!


參考資料:

相關文章