相關 PR 如下
以下的程式碼使用 8.x 的程式碼測試,這個問題
6.x
與7.x
版本同樣存在
原因
我們先看一下未修改前的程式碼表現,我們使用 laravel/framework
元件的 8.22.0
版本進行實現。
表結構如下
CREATE TABLE `test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(10) unsigned NOT NULL DEFAULT '0',
`type` int(10) unsigned NOT NULL DEFAULT '0',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
如果我們對入參都檢測的很清楚,比如以下情況
Route::get('/', function () {
DB::enableQueryLog();
$data = Test::query()->where('uid', 1)->where('type', 0)->get();
var_dump(count($data));
var_dump(DB::getQueryLog());
return 'Hello World.';
});
顯然結果是符合預期的
int(1)
array(1) {
[0]=>
array(3) {
["query"]=>
string(51) "select * from `test` where `uid` = ? and `type` = ?"
["bindings"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(0)
}
["time"]=>
float(10.07)
}
}
Hello World.
但,如果我們的 uid
是通過介面傳入進來,但又沒有對其進行檢測,那麼就會導致 uid
可能傳入一個陣列。
Route::get('/', function () {
try {
DB::enableQueryLog();
$data = Test::query()->where('uid', [1, 1])->where('type', 0)->get();
var_dump(count($data));
var_dump(DB::getQueryLog());
return 'Hello World.';
} catch (\Throwable $exception) {
return 'Server Error';
}
});
那麼結果就會天差地別
int(2)
array(1) {
[0]=>
array(3) {
["query"]=>
string(51) "select * from `test` where `uid` = ? and `type` = ?"
["bindings"]=>
array(3) {
[0]=>
int(1)
[1]=>
int(1)
[2]=>
int(0)
}
["time"]=>
float(9.24)
}
}
Hello World.
第一個查詢的 SQL 是 select * from test
where uid
= 1 and type
= 0
而後來的查詢卻是 select * from test
where uid
= 1 and type
= 1
當然,你可能認為這並沒有什麼,但如果你的 SQL
是更新操作呢,又如果你的 user_id
不幸放到了後面呢?那豈不是所有的使用者都可以改改自己的介面入參,修改到別人的資料?
Laravel 的修改
接下來,讓我們看一下 Laravel
的修改辦法,其實核心就是一個,那就是如果發現入參是 Array
,就主動使用 head
方法取 Array
的第一個元素。
讓我們更新 laravel/framework
元件到 8.24.0
再看。
int(1)
array(1) {
[0]=>
array(3) {
["query"]=>
string(51) "select * from `test` where `uid` = ? and `type` = ?"
["bindings"]=>
array(2) {
[0]=>
int(1)
[1]=>
int(0)
}
["time"]=>
float(11.33)
}
}
Hello World.
從結果上看,確實似乎解決了上面的問題,但實則引入了一個更加嚴重的隱患,將安全隱患變成了事故隱患。
可能存在的事故隱患
以下程式碼都是基於這次修改,我本人是不會這麼寫程式碼的
首先,我們假定,開發者已經知道了上述問題,而且他認為上述情況也是合理的,也是這麼用的。
那麼 Laravel 的這次修改,並不會報任何錯,但程式碼含義卻完全不同,本來使用者就想修改 type=1, user_id=1 的記錄,一旦框架去掉了後面的資料,那搞不好就修改成了 type=1, user_id=0 的記錄。
如果 user_id = 0 代表的含義是沒有使用者ID的記錄,豈不是一口氣將所有非使用者產生的訊息全部修改掉。。
而且最可怕的是,開發者並不知情,就算哪天發現了這個問題,資料也已經很難回滾了。
結論
所以,我認為還是應該直接丟擲錯誤
/**
* Get a scalar type value from an unknown type of input.
*
* @param mixed $value
* @return mixed
*/
protected function flattenValue($value)
{
if(is_array($value)){
throw new \InvalidArgumentException();
}
return $value;
}
那麼剛剛的程式碼就會出現以下情況
Server Error
寫在最後
Hyperf 是基於 Swoole 4.4+ 實現的高效能、高靈活性的 PHP 協程框架,內建協程伺服器及大量常用的元件,效能較傳統基於 PHP-FPM 的框架有質的提升,提供超高效能的同時,也保持著極其靈活的可擴充套件性,標準元件均基於 PSR 標準 實現,基於強大的依賴注入設計,保證了絕大部分元件或類都是 可替換 與 可複用 的。
框架元件庫除了常見的協程版的 MySQL 客戶端、Redis 客戶端,還為您準備了協程版的 Eloquent ORM、WebSocket 服務端及客戶端、JSON RPC 服務端及客戶端、GRPC 服務端及客戶端、Zipkin/Jaeger (OpenTracing) 客戶端、Guzzle HTTP 客戶端、Elasticsearch 客戶端、Consul 客戶端、ETCD 客戶端、AMQP 元件、Apollo 配置中心、阿里雲 ACM 應用配置管理、ETCD 配置中心、基於令牌桶演算法的限流器、通用連線池、熔斷器、Swagger 文件生成、Swoole Tracker、Blade 和 Smarty 檢視引擎、Snowflake 全域性ID生成器 等元件,省去了自己實現對應協程版本的麻煩。
Hyperf 還提供了 基於 PSR-11 的依賴注入容器、註解、AOP 面向切面程式設計、基於 PSR-15 的中介軟體、自定義程式、基於 PSR-14 的事件管理器、Redis/RabbitMQ 訊息佇列、自動模型快取、基於 PSR-16 的快取、Crontab 秒級定時任務、Translation 國際化、Validation 驗證器 等非常便捷的功能,滿足豐富的技術場景和業務場景,開箱即用。
本作品採用《CC 協議》,轉載必須註明作者和本文連結