# ORM && SQL injection ## 前言 自己的專案目前都是用 Nodejs + Express + 資料庫,所以很依賴框架,使用ORM感覺非常方便直覺,所以也沒有注意過ORM 跟SQL injection 的關聯及問題。 SQL injection 則是我在練習面試問題的時候注意到的,先不贅述。 最後兩者的關聯是在面試時,被主考官點到,才串聯起來的QQ,他提出,**你有想過 ORM 除了你覺得很直覺方便外,還有甚麼優缺點嗎?** 在討論ORM時,發現自己有些地方還不夠熟悉,因此,將那天討論的整理一下,並帶入自己的想法及後續查到的相關文章。 ## 目錄 ORM SQL injection 防範 SQL injection ## ORM ### 什麼是 ORM ORM全名是Object-Relational Mapping,中文是物件關係對映。就是將關聯式資料庫(Relational Database Management System)的資料,映射到物件(Object)之中,反之亦然。 關聯式資料庫(Relational Database)與物件導向的編程,有不少不契合的地方,電腦科學有一個專有名詞去形容關聯式資料庫與物件導向設計之間之不協調,也就是[Object-relational impedance mismatch](https://en.wikipedia.org/wiki/Object%E2%80%93relational_impedance_mismatch)。為數不少的物件導向概念例如接口、繼承等,在資料庫的世界,完全沒有相對應的概念。 **ORM的存在意義,就是為了撫平兩者中間的不協調,將資料庫中的資料,映射到記憶體的物件之中**。 ![](https://i.imgur.com/aE5F6H9.png) ORM 就像是工程師與資料庫中間的口譯人員,有了 ORM,我們就不用自己寫又臭又長的 SQL 了。常見的 ORM 像是 Mongoose (with MongoDB) 以及 Sequelize (with MySQL/PostgreSQL)。 舉例來說,我想找出特定餐廳的資料,以及與其關聯的相關資料,程式碼寫起來會像是下面這樣(需要先在 model 定義其關聯性): ``` Restaurant.findByPk( req.params.id, { include: [ Category, { model: User, as: 'FavoritedUsers' }, { model: User, as: 'LikedUsers' }, { model: User, as: 'CommentedUsers', } ], }) ``` 實際執行之後,你可以在 console 裡面看到 sequelize 幫我們產生的 SQL query string 長得像下面這樣 [view](https://gist.githubusercontent.com/tsungtingdu/e7172a694c275b0f267df49b4cd78036/raw/c4b4ebff4503a7953dbbefc7eabdc9705364a0a6/gistfile1.txt) 這就是 sequelize 默默幫我們做的事情。 ### 使用 ORM 的利弊 #### 優點: 1. 簡單使用:對於習慣操作物件的工程師來說,非常方便。 1. 提升安全性:使用 ORM 可以防止 SQL Injection 的問題。 #### 缺點: 1. 效能問題:在普通狀況下操作還算可以,但是在 high-volume low-latency 的環境下效能不佳 1. 在需要複雜 query 的情況下,ORM 可能會「誤判」使用者的操作,導致產出的結果與預期不同([可參考](https://medium.com/tds-note/orm-v-s-sql-91e003089a61)) 1. 無法支援所有 SQL 處理資料的方法,導致在某些情況下,最後還是得要自己寫 SQL 來達到自己想要的目標 ### 結論 ORM 雖然方便,但看起來有其局限性,因此還是得要學一下 SQL,才能夠應付 ORM 無法處理的狀況。 ### 推薦文章 [SQL三部曲:你不需要ORM](https://tecky.io/en/blog/SQL%E4%B8%89%E9%83%A8%E6%9B%B2:%E4%BD%A0%E4%B8%8D%E9%9C%80%E8%A6%81ORM/) ## SQL injection 當還是得要自己寫 SQL 來達到自己想要的目標時,就必須開始注意 SQL injection 的問題。 ### 怎麼發生 ? SQL Injection 的原理很簡單,利用網頁程式設計者忽略檢查使用者輸入內容造成攻擊,但只從一句話很難看出到底發生了什麼,下面用生活化的例子,帶大家快速了解整個流程。 ![](https://i.imgur.com/su1RAb6.png) ![](https://i.imgur.com/CkoYeYe.png) 網站登入流程 1. 用戶在頁面上輸入帳號密碼。 1. 前端將從頁面上將資料整理給後端。 1. 後段將資料整理成 SQL 格式,向資料庫詢問是否存在一個名稱欄位為 admin 密碼欄位為 password 的使用者,如果存在的話 ID 是多少。 1. 後端從資料庫找到這個使用者後判斷讓用戶登入。 1. 前端在頁面上顯示登入成功。 ### 攻擊流程 SQL Injection 攻擊的流程,示範如何不用密碼就可登錄網站。 ![](https://i.imgur.com/Kp7lFBq.png) 網站登入流程(SQL Injection) 1. 用戶輸入帳號密碼。 1. 前端將資料整理給後端。 1. **後端將資料整理成SQL語法格式,向資料庫詢問是否存在一個名稱欄位為 admin 或 1=1 和密碼為空的使用者,如果存在的話 ID 是多少**。 1. **後端從資料庫找不到這個使用者但因為 1 等於 1 所以讓讓用戶登入**。 1. 前端在頁面上顯示登入成功。 從流程中可以看出因為輸入中加上符號後組合成 SQL 語句,影響了步驟 3 的輸入與步驟 4 的判斷,因為與 1=1 取聯集導致查詢一定成立,所以即使密碼錯了也可以成功登入。明顯看出攻擊者透過截斷原本程式的語意,插入他希望執行的 SQL 語法,達成攻擊的效果。 ### SQL Injection 有哪些種類 ? 1. In-band SQL Injection 最常見和最容易被利用的一種。攻擊者藉由插入 SQL 語法來從資料庫收集資訊 2. Inference (Blind) SQL Injection 部分頁面輸入後並不像查尋頁面會直接顯示從資料庫來的資料,所以攻擊者需要靠其他資訊來攻擊,因為大多數狀況下要靠反複猜測來猜取資料內容,這類型攻擊又常被稱為盲測。 3. Out-of-band SQL Injection 主要是透過 SQL Injection 來攻擊與資料庫有串連的系統,像是 DNS Server、文件系統、電子郵件等等,所以攻擊方式取決於後面有串那些系統 ## 防範 SQL injection ### 解決方案(一) : 輸入過濾 過濾輸入是最常見的防範方法,藉由檢查 SQL 語法中有意義的符號來避免語意被竄改,但要特別注意兩點, 1. 必須在後端進行過濾,因為駭客有太多機會繞過前端檢查 2. 避免用黑名單的方式過濾,因為駭客會嘗試用合法的字元來組合出該被過濾掉的符號,用白名單可以將這個問題的發生機率降到最低,不過白名單只適合用在輸入較為**單純**的網站,如果網站的輸入較複雜或者必須包含符號,建議改用其他方法。 ### 解決方案(二) : 參數化 限制輸入僅含參數不含語法,接著在輸入後立刻轉換輸入格式。 以開頭的免密碼登入攻擊來舉例,實際上資料庫收到的 SQL 語句如下。 `SELECT ID FROM Accounts WHERE user ='admin or '1'=1' and 'pass'='';` 如果在先轉換輸入內容轉換為字串再放入 SQL 語句。 `SELECT ID FROM Accounts WHERE user="admin\' or \'1\'=\'1" and 'pass'="";` 這時會去檢查有沒有一個名稱為 admin' or '1'='1 的帳號,結果當然是找不到攻擊也失效,這種方法比較通用,不過要注意轉換失敗時的例外處理,部分程式語言會直接 crash,影響到網站服務。 ### 解決方案(三) : 資料庫套件 就是上述所說的ORM,此時就要注意安裝套件的安全性,避免用不安全的套件導致網站受到攻擊。 ## 參考 1. [秒懂 SQL Injection](https://tech-blog.cymetrics.io/posts/nick/sqli/) 2. [SQL injection 原理介紹與防範教學 - 工程師絕不能犯的低級錯誤!](https://blog.kennycoder.io/2020/01/09/SQL-injection-%E5%8E%9F%E7%90%86%E4%BB%8B%E7%B4%B9%E8%88%87%E9%98%B2%E7%AF%84%E6%95%99%E5%AD%B8-%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%B5%95%E4%B8%8D%E8%83%BD%E7%8A%AF%E7%9A%84%E4%BD%8E%E7%B4%9A%E9%8C%AF%E8%AA%A4/) ###### tags: `面試技術考題`