2024年最佳天气API
Laravel损坏对象级别授权指南
对象级授权失效 (BOLA) 是一种常见的网站漏洞。当 Web 应用程序或 API 未能正确检查用户权限时,就会发生这种情况。因此,攻击者可以在很少或没有网站权限的情况下访问敏感的网站数据,从而导致严重的安全漏洞。所有 Web 开发人员都需要了解 BOLA 以及如何预防它。
本文将研究 Laravel 应用程序中对象级授权 (BOLA) 问题的示例以及如何修复和预防这些问题。
对象级授权失效
当对象级授权起作用时,它会在允许用户访问信息之前检查用户的权限。我们称之为“对象级”,因为应用程序必须在客户端请求时对单个对象执行这种授权。
对象级授权的责任落在应用程序代码上。第三方服务和库负责身份验证,它们可能能够在识别用户后向应用程序提供有关用户的信息。但应用程序需要验证对特定对象的访问权限。
因此,BOLA 是指应用程序不验证访问权限或无法正确验证访问权限。这意味着用户可以读取、删除、创建或更新他们不应被允许的数据。
BOLA 问题很容易被利用。
许多攻击都是通过简单的 shell 脚本发起的,因此攻击者一经发现就会利用这些攻击。此外,由于问题的性质,很容易窃取大量数据。
让我们看一个例子来了解原因。
REST API 中的对象级授权损坏
一个简单的 API
让我们设计一个用于管理漫画书的 REST API。
API 使用如下所示的 JSON 对象来表示漫画:
{
"id":"1",
"publisher": "DC Comics",
"title":"Action Comics",
"issue":"100",
"price": "$10000"
"count": "10",
}
该 API 具有用于管理漫画书记录的端点。
- 使用 JSON 记录发布/comics/ 以将新漫画添加到库存。
- GET /comics/{ID} 根据 ID 检索书籍。
- GET /comics/ 不带参数检索所有漫画列表
- 使用 JSON 记录的PUT /comics/{ID} 用新信息更新现有漫画。
- DELETE /comics/{ID} 删除漫画
BOLA 问题
在典型的库存系统中,不同的用户具有不同的访问权限级别。
- 商店经理可以添加新的漫画、更改库存水平以及从数据库中删除漫画。
- 当销售人员售出一本书时,他们可以检索书籍并调整库存水平。
- 顾客可以检索有关漫画的信息。
如果您知道漫画书的 ID,则可以检索、更新或删除它。如果您知道使用 GET 调用 /comics/ 而不使用参数,则可以获取库存中所有漫画的列表及其 ID。这似乎不是一个大问题,但想象一下如果用户数据库中存在此漏洞会怎样。
使用此 API 设计,验证用户权限的责任落在了应用程序身上。仅检查用户是否具有有效会话是不够的。应用程序需要根据权限列表验证用户的名称。
Laravel 损坏对象级授权
让我们看看 Laravel 中的 BOLA。我们将使用一个实现上述 API 的应用程序。
一个简单的 Laravel Rest API
Laravel 使构建 CRUD API 变得非常简单。
由于我们使用 MySQL 作为存储漫画的后端,因此模型代码非常简单。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comic extends Model
{
use HasFactory;
}
Laravel 也使基本身份验证变得简单。通过将对控制器的调用与对Sanctum的调用包装在一起,可以轻松确保没有人在没有有效会话的情况下访问 API。
因此,我们的漫画 API 路由条目使用 Sanctum 中间件。同时,登录和注册 路由,以便您可以使用它们来创建会话。
<?php
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/comics', 'App\Http\Controllers\ComicController@index');
Route::middleware('auth:sanctum')->get('/comics/{comic}', 'App\Http\Controllers\ComicController@show');
Route::middleware('auth:sanctum')->post('comics', 'App\Http\Controllers\ComicController@store');
Route::middleware('auth:sanctum')->put('comics/{comic}', 'App\Http\Controllers\ComicController@update');
Route::middleware('auth:sanctum')->delete('comics/{comic}', 'App\Http\Controllers\ComicController@delete');
Route::post('login', [App\Http\Controllers\AuthController::class, 'login']);
Route::post('register', [App\Http\Controllers\AuthController::class, 'register']);
漫画控制器使用该模型来检索、更新、创建和删除 MySQL 中的项目。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Comic;
use Illuminate\Support\Facades\Log;
class ComicController extends Controller
{
public function index()
{
return Comic::all();
}
public function show(Request $request, $id)
{
return Comic::find($id);
}
public function store(Request $request)
{
$comic = Comic::create($request->all());
return response()->json($comic, 201);
}
public function update(Request $request, $id)
{
$comic = Comic::findOrFail($id);
$comic->update($request->all());
return response()->json($comic, 200);
}
public function delete(Request $request, $id)
{
$Comic = Comic::findOrFail($id);
$Comic->delete();
return response()->json(null, 204);
}
}
最后,我们需要一个控制器来创建新用户并让他们登录。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class AuthController extends Controller
{
public function login(Request $request)
{
if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){
$auth = Auth::user();
$success['token'] = $auth->createToken('LaravelSanctumAuth')->plainTextToken;
$success['name'] = $auth->name;
return $this->handleResponse($success, 'User logged-in!');
}
else{
return $this->handleError('Unauthorised.', ['error'=>'Unauthorised']);
}
}
public function register(Request $request)
{
$validated = $request->validate([
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
'confirm_password' => 'required|same:password',
]);
$input = $request->all();
$input['password'] = bcrypt($input['password']);
$user = User::create($input);
$success['token'] = $user->createToken('LaravelSanctumAuth')->plainTextToken;
$success['name'] = $user->name;
return $this->handleResponse($success, 'User successfully registered!');
}
}
测试服务
那么,让我们尝试一下这项新服务。
一位新用户想通过网络购买一些漫画。他们尝试通过 API 列出漫画。
GUI 不应该让您到达这一点,但由于 API 阻止了未经授权的请求,因此不会造成任何损害。
这个简单的服务返回了内部服务器错误,因为我们还没有将所有正确的状态连接在一起。在生产中,你会得到 401 而不是 500。
因此,我们的新客户走上了正确的道路并创建了新的登录。
现在他们有了登录名和令牌,所以他们就可以看漫画了!
我们设置的简单授权期望令牌作为Bearer Token返回。
成功!
在购物应用中列出所有商品不是问题,尤其是当 API 知道如何实现分页以避免 UI 崩溃时。但在管理用户数据的应用中,这会是个问题。
不管怎么说,那本绿巨人漫画看起来很贵,而且标题也不对!让我们来解决这个问题。
以下是记录的更新内容:
这是PUT 请求。我们将尝试使用客户的持有者令牌来更新漫画。
请求成功。服务器返回 200 并将新值回显给我们。
我们再检查一下。
哎呀。我们使用客户的凭证更改了库存。这破坏了对象级授权。
修复 Laravel BOLA
那么我们该如何解决这个问题呢?
我们需要验证用户是否有权提出请求,而不仅仅是他们是否具有有效的会话。
Laravel 使这变得简单。当 Sanctum 验证持有者令牌时,它会将用户信息添加到 与请求关联的 auth对象中。因此,我们可以在处理请求之前检查用户名。它存储在auth(‘sanctum’)->user() 中。
让我们允许销售人员更新漫画,这样他们就可以在销售漫画时调整数量,并且我们允许老板更新漫画,因为他们是老板。如果用户不是这些人之一,我们将返回 401。
public function update(Request $request, $id)
{
if (auth('sanctum')->user()->name == "theboss" || auth('sanctum')->user()->name == 'sales') {
$comic = Comic::findOrFail($id);
$comic->update($request->all());
return response()->json($comic, 200);
} else {
return response()->json(null, 401);
}
}
让我们用新用户的承载令牌来测试这一点。
我们得到了 401(未授权)状态。成功了!
对用户名等值进行硬编码从来都不是一个好主意。理想情况下,我们会将其添加到数据库架构中,但现在我们可以将其添加到配置文件中。
因此,让我们在config/app.php中的应用程序配置中添加两个数组 :
'administrators' => ['theboss'],
'sales' => ['sales'],
现在我们可以拥有多个管理员或销售帐户。
接下来,更新检查以使用配置值:
public function update(Request $request, $id)
{
if ( in_array(auth('sanctum')->user()->name, Config::get('app.administrators')) ||
in_array(auth('sanctum')->user()->name, Config::get('app.sales'))) {
$comic = Comic::findOrFail($id);
$comic->update($request->all());
return response()->json($comic, 200);
} else {
return response()->json(null, 401);
}
}
在更新项目之前,该函数会根据两个数组检查用户名。只有当请求者是管理员或销售人员的成员时,它才会允许请求通过。
我们现在也可以限制只有管理员才能删除项目:
public function delete(Request $request, $id)
{
if (in_array(auth('sanctum')->user()->name, Config::get('app.administrators'))) {
$Comic = Comic::findOrFail($id);
$Comic->delete();
return response()->json(null, 204);
} else {
return response()->json(null, 401);
}
}
因此,此应用程序现在具有简单的对象级授权。更好的解决方案可能是在 MySQL 中设置访问级别,甚至可能将其作为 Sanctum 信息的一部分返回。但是,主要的安全漏洞已修复,并且有权限的用户没有被硬编码。
总结
我们首先研究了对象级授权问题,以及用户如何利用它来窃取或更改他们本不应该能够窃取或更改的信息。然后,我们转向了 Laravel 在一个简单的 REST API 中破坏的对象授权。我们了解了漏洞的面貌并应用了一个简单的修复程序。