MongoDB, no SQL injection?

spacewander發表於2014-12-16

最近發生一件事, Ruby-China Mongodb注入可導致盜用管理員(他人)身份發帖引起了我的興趣。

具體內容可以移步到連結去看。隨便給出對應的pr地址:https://github.com/ruby-china/ruby-china/commit/ff19cc1fb6d9ea8b0109d7f62741f6652009ff3d

於是我搜尋了相關資料,以搬運工的身份寫下這篇文章。

MongoDB, no SQL injection?

有人的地方就有江湖,有DB的地方就有injection。SQL資料庫如此,No SQL資料庫亦是如此。考慮到No SQL資料庫用的不是SQL,這裡使用SQL injection是否有點不太恰當?不過總不能說是No SQL injection吧XD。

言歸正傳,OWASP上面有一篇文章, https://www.owasp.org/index.php/Testing_for_NoSQL_injection, 講到MongoDB的注入問題。另外,MongoDB官方文件FAQ中也提到對於SQL injection的防範方式。其他地方提到的資料基本上大同小異。

根據上面兩份資料的內容,以及其他在網上找到的內容:

  1. No SQL不代表安全。由於No SQL使用的查詢語言很多是過程式語言而非宣告式語言,No SQL隱碼攻擊的危害甚至比傳統的SQL資料庫更大。

  2. 不要草率地通過拼接使用者輸入的方式來生成查詢語句。這個就不需要舉例吧,瞭解MongoDB查詢語言的人,應該可以想象到會是什麼結果。

  3. 好訊息:MongoDB會在driver中將查詢字串編碼成BSON格式。類似於,:{這樣的奇奇怪怪的符號會導致BSON構造出錯。這樣cracker在注入時需要花費更多的心思了。

  4. 壞訊息:$.不在上述符號之列。

  5. 又一個好訊息:Mongoose宣稱,為了提高安全性,它會根據Schema來強制轉型輸入的內容,因此像$xxx這樣的變數會被轉化成預定義的型別。如果你還是放心不下,參照 http://stackoverflow.com/questions/15917400/how-dangerous-is-a-mongo-query-which-is-fed-directly-from-a-url-query-string 裡面的回答,自己寫驗證函式。

  6. 最後是壞訊息。在下列四個函式中,你可以直接傳遞字串作為引數。記住它們的樣子,見到它們可以高呼“Eval is evil!”

具體是幹什麼的,請參考對應文件。

注意$where,這個跟MongoDB ORM(姑且這麼稱呼)中提供的where呼叫不一樣。這個$where可以接受任意查詢表示式,並且返回其結果,相當於特殊的eval。其中Mongoose也特別提到了它:

  • ####NOTE:
  • Only use $where when you have a condition that cannot be met using other MongoDB operators like $lt.
  • Be sure to read about all of its caveats before using.

https://github.com/LearnBoost/mongoose/blob/master/lib/query.js

其實,Ruby China事件其中的主要問題跟MongoDB注入無關……

前面說了一大堆,關於MongoDB注入的問題,但是Ruby China事件的主要問題,跟MongoDB無關……(XD請各位看官先吸口氣)

好了,主要的問題是:param的值一定是字串麼?

回到Ruby China的pr上來,這個pr主要是新增

token = params[:token] || oauth_token
+
+      # 防 mongodb 注入
+      token = token.to_s

@current_user ||= User.where(private_token: token).first

顯然,token的值不一定是字串!那麼,這個引發了血案的token原本是什麼值呢?

我特意用Rails測試了一下:
(注意,因為Rails是通過Rack來解析HTTP引數的,而基本上Ruby Web框架也都是使用Rack,所以下面的結果應該適用於其他Ruby框架)

...
p params[:page]
...

http://localhost:3000/page[$gt]=1
輸出{"$gt"=>"1"}

現在大家知道傳給User.where的token到底是什麼值吧!

繼續測試:
http://localhost:3000/page=[1,2]
輸出[1,2],嗯…

繼續繼續:
http://localhost:3000/page=1&page=2
輸出2

限於時間和精力,我只測試了Rails的情況。不過根據那篇烏雲文章所言,PHP和Node對於HTTP引數的處理方式也跟Rails差不多。

其實,這種攻擊方式並不新鮮。它甚至有一個學名,叫做HTTP Parameter Pollution,簡稱HPP。原理就是利用不同的伺服器和服務端框架對HTTP引數的處理方式不同,提交奇奇怪怪的HTTP引數,來繞過邏輯判斷。

所以說,搞了半天,最後還是回到一個老問題上了,永遠不要相信使用者的一切輸入!

總結

  1. No SQL也會有SQL injection。
  2. 最好不要拼接查詢語句,因為漏洞什麼的,你永遠也想不到會從哪裡冒出來。
  3. 注意MongoDB查詢語句中的危險分子。
  4. 永遠不要相信使用者的輸入。無論什麼輸入,都要在服務端程式中過一下,哪怕是轉化成字串這樣的操作也好。

相關文章