2020年5月27日

[資安] 跨站偽造請求(Cross Site Request Forger, CSRF, XSRF) 的說明與預防

Photo by Clint Patterson on Unsplash Photo by Clint Patterson on Unsplash
keywords: csrf, internet security, one-click-attack, session-riding, XSRF

Cross Site Request Forgery 是什麼

跨站偽造請求(cross-site request forgery)也稱為 one-click attacksession riding,通常縮寫為 CSRF(有時發音為 sea-surf) 或 XSRF,這是一種利用伺服器所信任的網站來發送惡意請求的攻擊;和 cross-site scripting (XSS) 不同,XSS 是透過在網站上輸入惡意程式碼的方式來進行攻擊,通常利用的是「使用者對目標網站」的信任;而 CSRF 則是攻擊者利用「目標網站對該信用者」的信任。透過 CSRF 攻擊有機會讓使用者在無意間修改受害者的帳號密碼,或將帳戶內的金額轉帳給攻擊者。
CSRF 通常有以下流程:
  • 使用者使用正常流程登入「目標網站」
  • 「惡意網站」利用目標網站對使用者的信任(credentials),例如 Cookies
  • 欺騙使用者到「惡意網站」後,誘使使用者點擊某個按鈕,但這個按鈕可能會送出表單,而該表單的請求對象是對到「目標網站」
  • 瀏覽器預設會把使用者在「目標網站」的 Cookie 連帶送出,因此雖然該請求是在「惡意網站」發出,但「目標網站」收到請求時因為帶有 credentials,所以會誤以為是合法的請求。

攻擊可能的樣子

使用者一旦點擊下方的連結,即會向「目標網站」送出 Post 請求:
<!--
 - Code from Rails Guides
 - https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
-->

<a
  href="http://www.harmless.com/"
  onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;"
  >To the harmless survey</a
>
或者使用者只要將滑鼠移過某一張圖片時,即透過 JavaScript 執行指令:
<!--
 - Code from Rails Guides
 - https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
-->
<img
  src="http://www.harmless.com/img"
  width="400"
  height="400"
  onmouseover="..."
/>

解決方式

現在多數的框架都支援去防範 CSRF 的攻擊。舉例來說,在 Ruby on Rails 的網頁應用程式中,由本站的表單或透過 AJAX 向伺服器發送的請求中都加上 security token(X-CSRF-Token),並於伺服器端驗證此 Token。由於這個 Token 只能在瀏覽本站時取得,因此攻擊者透過惡意網站試圖向伺服器發送請求時,雖然這個請求帶有認證過的 Cookie,但因為它並沒有帶有合法的 X-CSRF-Token,Rails 在處理此請求時會拋出錯誤。

參考

2020年5月26日

[資料結構] Binary Search Tree

keywords: data structure, recursive function, 遞迴函式, queue, 佇列
此系列筆記主要依照 [Udemy] Learning Data Structures in JavaScript from Scratch by Eric Traub 的課程脈絡加以整理,但部分程式碼是消化後以自己較易理解的方式重新撰寫,因此和原課程內容有些出入。
Imgur
  • Binary Search Tree 有一個很重要的概念是,它是有「階層」的概念在內的,並不是把所有數字放在同一層排序
  • 在建立 BST instance 時會把 root 的 value 一併帶入
  • 新增值到 BST 時,會判斷這個值比 root 大或小,再以同樣的邏輯分派到 BST 的左下側或右下側
  • 檢驗 BST 中包含是否包含某一值時,也是使用同樣的邏輯,先判斷這個值比 root 大還是小,接著依同樣的邏輯往左下側或右下側尋找
  • 找出最小值的方式是從 root 開始一路往左下找;找出最大值的方式則是從 root 開始一路往右下找
  • 要疊代整個 BST 元素的方式可以分成 Depth First TraversalBreadth First Traversal
  • 其中 Depth First Traversal 又可以分成:
    • 由小到大依序迭代(in-order)
    • 由上往下,由左至右(pre-order)
    • 由下往上,由左至右(post-order)
  • Breadth First Traversal 則是以水平的方式由上往下依序疊代所有內容

Depth First Traversal

在撰寫這個方法的時候,需要特別留意 call stack 中執行的順序:
Imgur

In-Order

從最底部開始,先把左邊的輸出,在輸出右邊的,如此所有的值就會由小到大依序排列。輸出的內容會是:10, 20, 30, 35, 45, 50, 59, 60, 70, 85, 100, 105。
Imgur

Pre-Order

  • 適合使用在想要 Copy Tree 時使用
  • 由上往下,由左至右,依序輸出所有內容。以下圖為例,會輸出 50, 30, 20, 10, 45, 35, 70, 60, 59, 100, 85, 105:
Imgur

Post-Order

  • 適合使用在刪除節點時
  • 由下往上,由左至右。以下圖為例,會輸出 10, 20, 35, 45, 30, 59, 60, 85, 105, 100, 70, 50:
Imgur

Breadth First Traversal

適合用在有階層性的資料,例如公司的組織架構,如此可以快速地找出管理職有誰,員工有誰等等:
Imgur
實作的方式會使用到 queue 作法:
Imgur

Big O Notation

二元搜尋樹的優點在於:
  • 可以快速的找到項目
  • 可以快速的新增和移除項目
  • 它是屬於 O (log n) 的演算法
但使用它有一個很重要的前提是資料必須盡量平均分散在左右兩邊,讓它長成一個樹狀結構,如果資料都是集中在某一側的話,則不適合使用 Binary Search Tree。這種資料結構常用在「字典」、「電話簿」、「使用者資料」等等。

Sample Code

Started Template

/* eslint-disable */
function BST(value) {
  this.value = value;
  this.left = null;
  this.right = null;
}

// 在 BST 中插入值
BST.prototype.insert = function insert(value) {
  /* ... */
};

// 判斷 BST 中是否包含此值,回傳 true/false
BST.prototype.contains = function contains(value) {
  /* ... */
};

// depthFirstTraversal 可以用來疊代 Binary Search Tree 中的所有元素
const ORDER_TYPE = {
  IN_ORDER: 'IN_ORDER', // 將所有元素打平後由左至右排列
  PRE_ORDER: 'PRE_ORDER', // 由上往下,由左至右
  POST_ORDER: 'POST_ORDER', // 由下往上,由左至右
};

BST.prototype.depthFirstTraversal = function depthFirstTraversal(
  iteratorFunc,
  order = ORDER_TYPE.IN_ORDER
) {
  /* ... */
};

// 疊代 BST 中的所有元素
BST.prototype.breadthFirstTraversal = function breadthFirstTraversal(
  iteratorFunc
) {
  /* ... */
};

// 取得 BST 中的最小值
BST.prototype.getMinVal = function getMinVal() {
  /* ... */
};

// 取得 BST 中的最大值
BST.prototype.getMaxVal = function getMaxVal() {
  /* ... */
};

module.exports = {
  BST,
  ORDER_TYPE,
};

參考

[資料結構] Hash Table

keywords: data structure
此系列筆記主要依照 [Udemy] Learning Data Structures in JavaScript from Scratch by Eric Traub 的課程脈絡加以整理,但部分程式碼是消化後以自己較易理解的方式重新撰寫,因此和原課程內容有些出入。
Imgur

觀念

  • Hash Table 本身是一個陣列,裡面的每一個元素都是帶有 key-value 的物件,稱作 Buckets
  • 透過自訂的 hash 函式,可以決定新增的資料要放在 Hash Table 中的哪個位置。
  • 當有不同的內容放到相同編號的 Buckets 時,會發生所謂的 collision,這時候相同 Bucket 內的資料,會透過 Linked List 的方式串接在一起。
  • 當 insert 了相同 key 的資料時,會有更新的效果。
  • 其中會包含的方法有:
    • hash
    • insert
    • get, retrieveAll

Big O Notation

  • Hash Table 常用在儲存使用者的 Email、使用者資料。
  • 缺點:除非在同一個 bucket 內,否則資料(Node)之間不會彼此參照。

Constant Time - O(1)

  • 插入資料
  • 搜尋資料

Sample Code

Started Template

// size 會決定在 hashTable 中有多少 buckets
function HashTable(size) {
  this.buckets = Array(size);
  this.numberBuckets = this.buckets.length;
}

// 每一個 key-value pair 稱作 Node
function HashNode({ key, value, next = null }) {
  this.key = key;
  this.value = value;
  this.next = next;
}

// 用來產生一個數值,此數值不能超過 buckets 的 size
HashTable.prototype.hash = function hash(key) {/* ... */};

// 用來新增 Node 到 HashTable 中
// 若該 bucket 已經有資料,則透過 Linked List 的概念,放到 next 中
// 若該 key 已經存在,則進行更新
HashTable.prototype.insert = function insert({ key, value }) {/* ... */};

// 根據 key 取得 value
HashTable.prototype.get = function get(key) {/* ... */};

// 取得所有 Buckets 中的 Node
HashTable.prototype.retrieveAll = function retrieveAll() {/* ... */};

module.exports = HashTable;

參考

[資料結構] Linked List

此系列筆記主要依照 [Udemy] Learning Data Structures in JavaScript from Scratch by Eric Traub 的課程脈絡加以整理,但部分程式碼是消化後以自己較易理解的方式重新撰寫,因此和原課程內容有些出入。

資料結構(Data Structure)是什麼

  • 不同的資料結構有不同的強弱項,有的在儲存(storing)和紀錄(recording)資料的速度較快,有的則是在搜尋(searching)和提取(retrieving)上比較快。
  • 資料結構的使用會影響到效能表現(performance)和程式運行的效率

Linked List

Imgur
  • Linked List 中,會紀錄該 Linked 的 headtail
  • 每一個 Node 則會紀錄它的 value, prevnext
  • 其中會包含的方法為:
    • addToHead, addToTail
    • removeHead, removeTail
    • search, indexOf

Big O Notation

Constant Time - O (1)

  • 新增/移除 Head
  • 新增/移除 Tail

Linear Time - O (n)

  • 搜尋元素是否存在
  • 搜尋元素的 index 位置

Memory Manage Benefits

使用 Linked List 的資料結構可以更有效率的使用記憶體,因為每一個元素都是獨立,彼此之間是透過 next 和 prev 來關聯在一起:
Imgur

Sample Code

Started Template

/* eslint-disable */
function LinkedList() {
  this.head = null;
  this.tail = null;
}

function Node({ value, next, prev }) {
  this.value = value;
  this.next = next;
  this.prev = prev;
}

// 輸入 value 後,把該值添加到 Linked List 的最前方
LinkedList.prototype.addToHead = function addToHead(value) {
  /* ... */
};

// 輸入 value 後,把該值添加到 Linked List 的最後方
LinkedList.prototype.addToTail = function addToTail(value) {
  /* ... */
};

// 移除 Linked List 的第一個 Node,若 Head 存在則回傳被移除的值,否則回傳 null
LinkedList.prototype.removeHead = function removeHead() {
  /* ... */
};

// 移除 Linked List 的最後一個 Node,若 Head 存在則回傳被移除的值,否則回傳 null
LinkedList.prototype.removeTail = function removeTail() {
  /* ... */
};

// 輸入 value 後,搜尋此 LinkedList 中是否有此值
// 找不到的話回傳 null,否則回傳找的的值
LinkedList.prototype.search = function search(searchValue) {
  /* ... */
};

// 列出所有等同於 searchValue 的 indexes
LinkedList.prototype.indexOf = function indexOf(searchValue) {
  /* ... */
};

module.exports = {
  LinkedList,
  Node,
};

參考

2020年5月25日

[掘竅] 為什麼要使用 rel="noreferrer noopener",談 target="_blank" 的安全性風險

在網頁撰寫的過程中,經常當我們要另開視窗時,很容易使用 <a target="_blank"> 這樣的寫法,但如果你有使用 ESLint 的話,它會建議你在 a 標籤中要加上 rel="noreferrer noopener",也就是:
<a href="https://www.google.com" target="_blank" rel="noreferrer noopener">
  Google
</a>
之所以要加上這行,是因為當瀏覽器使用 target="_blank" 來打開新視窗時,新的視窗所在的網頁是有辦法透過 window.opener 這個物件來操作你原本的頁面。
舉例來說,當你在 A 站點了超連結另開新視窗到 B 站時,B 站可以在它的頁面中執行:
window.opener.location = 'https://www.google.com';
這時候你會發現你在 A 站的網頁默默轉址到了 Google 的頁面。
這種做法主要是利用一般人只會注意新開的視窗(B 站),而忽略了原有的視窗(A 站),但若不進一步處理, 新開的視窗是有機會可以修改到原視窗內所瀏覽的網址的。
因此,若你使用的 target="_blank" 的話,eslint-plugin-react 都會建議你要加上 rel="noreferrer noopener",以確保使用者當前瀏覽的頁面,不會因為開新視窗後被另開的這個網站給影響。

參考

2020年5月1日

[Mobile] 給開發者用:將 Android 裝置畫面投影到 MAC / PC 上的工具(scrcpy)

keywords: screen sharing, screen mirroring, miracast, android, screen recording
💡 備註:如果你是 iOS 裝置要投放到 Mac 的話則非常簡單,透過內建的 QuickTime 就可以了,只需選擇「File -> New Movie Recording」後,將來源選到 iOS 裝置即可,可參考圖一圖二的說明。
最近因為有把手機(Android)的操作畫面投影到電腦上的需求,試了幾套不同的工具後,發現許多工具都需要在手機和電腦端同時裝 App,除了會有較明顯的時間差外,大多都是透過 Android 內建的「投放」功能來達到這個效果。
但這裡因為某些理由,我不能使用原生的投放功能來投放螢幕,而是需要直接把手機畫面顯示在電腦上,找這找著找到這套很好用的 Android 投放手機螢幕工具,稱作 scrcpy
這套工具除了可以投放螢幕外,還可以錄製螢幕、傳輸檔案、並直接由電腦操作手機,最重要的是操作流暢度非常好!
但這套工具比較是給開發者的, 因為需要透過終端機下一些指令,當然你也可以照著打就好了。關於使用的方式在 scrcpy 的 Github 上已經有蠻清楚的說明,可以使用在 Linux, Mac 或 Windows 上,有興趣的可以直接到 scrcpy 的 Github 查看文件。
這裡簡單說明一下步驟,各步驟詳細的作法都可以再額外 Google 或於文件中查看:

手機端操作

  1. 啟用開發人員選項:先進到手機的「設定 -> 關於手機」然後點擊「Build Number(版本號碼)」7 次後即可開啟「開發者」功能(應該會看到提示文字)。
  2. 進入「開發人員選項」:接著回到設定頁面,在設定頁面中應該會多出「開發人員選項(Developer options)」的項目,點擊進去。
  3. 啟用「USB 偵錯」功能:進到開發人員選項後,找到「USB 偵錯(USB Debugging)的項目後開啟它
  4. 將手機透過傳輸線與電腦連接

電腦端操作(Mac)

這裡以 Mac 為例,下面指令都是在終端機輸入:
💡 Windows 的安裝方式可以參考 Github 上的說明。
  1. 安裝 Homebrew(如果還沒裝過)
# 如果還沒安裝過 homebrew 需要先安裝
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
  1. 透過 Homebrew 下載 scrcpy
$ brew install scrcpy                         # 下載 scrcpy
$ brew cask install android-platform-tools    # 下載 android 工具
  1. 手機連接電腦後,輸入
$ scrcpy
這時候就可以看到手機的畫面出現在電腦上了,而且相當流暢!
scrcpy

使用無限(wireless)方式進行連線

  1. 先將手機和電腦連接到同一個 Wifi 網路
  2. 取得手機的 IP 位置,點選設定 -> Wifi 網路,即可看到如圖的畫面
  3. 將手機與電腦使用傳輸線連結,接著在終端機輸入
$ adb tcpip 5555
$ adb connect <DEVICE_IP>:5555  # 填入裝置連上的 WIFI IP
  1. 把手機和電腦的傳輸線拔掉
  2. 執行 scrcpy
$ scrcpy
  1. 由於是透過無線傳輸,若想要得到最好的體驗,建議可以降低解析度和 bit-rate:
$ scrcpy --bit-rate 2M --max-size 800  # scrcpy -b2M -m800 縮寫
⚠️ 若想改回使用 USB 傳輸線連接,可以輸入 adb usb

更多功能:錄製螢幕、操作手機、檔案傳輸

scrcpy 這個工具除了可以投放螢幕外,也可以錄製螢幕,更可以直接在電腦上操作你的手機,還可以把檔案直接拖到手機內,真的超級方便的,而且流暢度非常高,其他更多的功能或參數設定,都可以到 scrcpy 查看!

參考