我可以配置 MySQL 的類型轉換以考慮 0 != ‘foo’ 嗎?
這篇文章指出了 MySQL 的以下安全問題:
USERS id | email | password_reset_token 1 | one@example.com | QTlXww)uV!Hdg3U6aGwKV2FPvAqzVgPx 2 | two@example.com | CZor5t7WbX#LTeqiG3v@6f3@z#)BfK*n
在這裡,我們有很好的隨機令牌,使用者必須證明他們擁有才能重置他們的帳戶。但是使用者設法送出了一個重置令牌
0
,所以我們執行這個查詢:SELECT * FROM `users` WHERE `email` = 'one@example.com' AND `password_reset_token` = 0
MySQL將 token 轉換
VARCHAR
為 anINT
以便進行比較。它認為VARCHAR
不以數字開頭的 a 等於 0。因此,攻擊者可以匹配任何非數字字元串並接管帳戶。即使重置令牌開始一些數字,情況也好不到哪裡去。MySQL 在進行此比較時會忽略除初始數字之外的所有字元,因此它認為
12blahblah3blah4
等於12
,這使得猜測變得更加容易。**我可以將 MySQL 配置為不進行這種類型轉換嗎?**例如,如果它施法
INT
而VARCHAR
不是反之,則這種攻擊將不起作用。筆記
如果使用
'0'
而不是執行查詢0
,則此方法不起作用。本文根據 Ruby on Rails 對 XML 的接受度來討論這個漏洞,其中一個type=integer
屬性說服 Rails 在查詢中發送一個實際的整數。該錯誤已在 Rails 中修復;它現在將所有請求參數轉換為字元串,因此它永遠不會使用整數建構查詢。但我仍然認為 MySQL 應該是可配置的以防止這種情況。
但是,問題是您提供了一個未引用的數值,因此 MySQL 嘗試將比較的另一側轉換為 a
DOUBLE
,以將其與您提供的數字參數進行比較……這會給您留下 0這相當於 0。mysql> select 'foo' = 0; +-----------+ | 'foo' = 0 | +-----------+ | 1 | +-----------+ 1 row in set, 1 warning (0.00 sec) mysql> show warnings; +---------+------+-----------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------+ | Warning | 1292 | Truncated incorrect DOUBLE value: 'foo' | +---------+------+-----------------------------------------+ 1 row in set (0.00 sec)
如果您在字元串上下文中提供 0,則計算結果為 false。
mysql> select 'foo' = '0'; +-------------+ | 'foo' = '0' | +-------------+ | 0 | +-------------+ 1 row in set (0.00 sec)
因此,您可以用來避免這種行為的一個選項是將您的輸入值轉換為
CHAR
…,在大多數情況下,它已經是…但如果不是,它會按照您的預期進行評估。mysql> select 'foo' = cast(0 as char); +-------------------------+ | 'foo' = cast(0 as char) | +-------------------------+ | 0 | +-------------------------+ 1 row in set (0.00 sec)
更新,更全面地回答這個問題:
我可以將 MySQL 配置為不進行這種類型轉換嗎?
不,似乎沒有任何方法可以通過配置來避免這種情況 - 只能通過將您的潛在數字字元串顯式轉換為字元串,您說過無法完成,因為您是使用 ORM。如果存在類似的情況,最有可能的地方是SQL Server Mode,並且在比較時,這些選項都不會修改此行為。
在Bug #63112中有一些關於隱式轉換的討論:
選擇橙色>香蕉;
這是對還是錯?好吧..通常香蕉比橙子長。因此,如果長度是標準,則上述陳述是錯誤的。但大多數情況下,橙子比香蕉重,所以如果以重量為標準,上述陳述是正確的。
在比較不同的類型(橙色與香蕉,字元串與整數)時,必須使用一些“標準”或“規則”。當 MySQL 比較不同的數據類型時,它們是
$$ both $$在比較之前在內部強制轉換為 DOUBLE。整數“1”轉換為雙“1” - 字元串“a”或“UUID()”轉換為雙“0”。 $$ … $$
請注意,此錯誤報告的範圍並不嚴格限於在上下文中隱式轉換,
UUID()
這裡的提及有點分散注意力,因為UUID()
可以返回以數字開頭的字元串並轉換為其他數字……但我認為它確實可以確認沒有替代退出,因為除了我們已經知道的“如果您想避免強制轉換,請不要使用不同的類型進行比較”之外沒有任何內容。它繼續:為了避免強制轉換為
DOUBLE
內部確保比較的值是相同的數據類型。在您的情況下,id
列是字元串,並且您比較的值也應該是字元串(不是 1 而是 ‘1’ - 不是 2 而是 ‘2’ 等)或在您的語句中使用cast()
/convert()
$$ … $$
關於你的建議,如果要進行隱式轉換,那麼數字應該變成一個字元串而不是字元串變成一個數字……我可以看到你的觀點,在一定程度上,對於嚴格的相等比較……但是有很多除了使用該邏輯之外的其他運算符
=
,出現了一系列全新的問題……mysql> SELECT CAST(100 AS CHAR) < '2'; +-------------------------+ | CAST(100 AS CHAR) < '2' | +-------------------------+ | 1 | +-------------------------+ 1 row in set (0.00 sec)
正確的!在字母意義上,字元串“100”“小於”字元串“2”……就像字元串“AXX”“小於”字元串“B”一樣。所以這個選擇肯定有它的缺點。
然而,還有第三種選擇,這似乎是最準確的邏輯(儘管我歡迎建設性的反對意見):如果以非數字字元開頭的字元串被轉換為
DOUBLE
(或任何其他數字類型,就此而言) ),該演員表的最正確結果似乎是NULL。不幸的是,這不是 MySQL 的方法。似乎這可以在源中修復,可能在
sql/field.cc
my_strntod() 返回的“0”在發生錯誤時拋出警告後似乎只是簡單地返回給呼叫者,但在源中嘗試這樣的黑客攻擊超出了我的專業範圍。