Creates the intentionally vulnerable View Grades module (IDOR / OWASP A01) for the CTF security lab. Use when asked to scaffold the grades controller, grades route, or grades view. Produces: GradeController using Grade::find($id) with NO ownership check (no Auth::id() comparison, no Policies, no Gates), GET /student/grades/{id} route, and a Tailwind grades blade. Marks the vulnerable line with // [VULNERABLE HERE]. Do NOT use for production code.
OWASP A01:2021 – Broken Access Control (Insecure Direct Object Reference)
Any authenticated student can view another student's grades by changing the {id} in the URL (e.g., /student/grades/1, /student/grades/2, …). No ownership check is performed.
| Artifact | Path |
|---|---|
| Model |
app/Models/Grade.php |
| Migration | database/migrations/xxxx_create_grades_table.php |
| Controller | app/Http/Controllers/Student/GradeController.php |
| Route | entry added to routes/web.php |
| Blade view | resources/views/student/grades.blade.php |
users table and working Auth session already exist (login module done first).Grade model and migration do not yet exist — this skill creates them.Create database/migrations/xxxx_create_grades_table.php (use php artisan make:migration or create manually):
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('grades', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('subject');
$table->decimal('score', 5, 2);
$table->string('semester')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('grades');
}
};
Create app/Models/Grade.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Grade extends Model
{
protected $fillable = ['user_id', 'subject', 'score', 'semester'];
public function student()
{
return $this->belongsTo(User::class, 'user_id');
}
}
Create app/Http/Controllers/Student/GradeController.php:
<?php
namespace App\Http\Controllers\Student;
use App\Http\Controllers\Controller;
use App\Models\Grade;
class GradeController extends Controller
{
public function show(int $id)
{
// [VULNERABLE HERE] — no Auth::id() check; any authenticated user can view any grade record
$grade = Grade::find($id);
if (!$grade) {
abort(404);
}
return view('student.grades', compact('grade'));
}
}
Mandatory rules (do NOT deviate):
Grade::find($id) — never Grade::where('user_id', Auth::id())->findOrFail($id)$grade->user_id with Auth::id() anywhere$this->authorize(), Laravel Policies, or Gates// [VULNERABLE HERE] directly above the Grade::find($id) callAdd to routes/web.php (inside an auth middleware group so students must be logged in — the vulnerability is that any logged-in student can access any ID):
use App\Http\Controllers\Student\GradeController;
Route::middleware('auth')->group(function () {
Route::get('/student/grades/{id}', [GradeController::class, 'show'])->name('student.grades.show');
});
Create resources/views/student/grades.blade.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Grade Details</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="bg-white p-8 rounded shadow w-full max-w-lg">
<h2 class="text-2xl font-bold mb-6 text-center">Grade Details</h2>
<table class="w-full text-sm border-collapse">
<tbody>
<tr class="border-b">
<td class="py-2 font-medium text-gray-600 w-1/3">Record ID</td>
<td class="py-2">{{ $grade->id }}</td>
</tr>
<tr class="border-b">
<td class="py-2 font-medium text-gray-600">Student ID</td>
<td class="py-2">{{ $grade->user_id }}</td>
</tr>
<tr class="border-b">
<td class="py-2 font-medium text-gray-600">Subject</td>
<td class="py-2">{{ $grade->subject }}</td>
</tr>
<tr class="border-b">
<td class="py-2 font-medium text-gray-600">Score</td>
<td class="py-2">{{ $grade->score }}</td>
</tr>
<tr>
<td class="py-2 font-medium text-gray-600">Semester</td>
<td class="py-2">{{ $grade->semester ?? 'N/A' }}</td>
</tr>
</tbody>
</table>
<div class="mt-6 text-center">
<a href="/student/grades/{{ $grade->id - 1 }}"
class="text-blue-500 hover:underline mr-4">← Previous</a>
<a href="/student/grades/{{ $grade->id + 1 }}"
class="text-blue-500 hover:underline">Next →</a>
</div>
</div>
</body>
</html>
The Previous / Next navigation links are intentional — they make it trivially easy for students to walk through other users' records during the lab exercise.
grades migration exists with user_id, subject, score, semester columnsGrade model exists at app/Models/Grade.phpGradeController::show() uses Grade::find($id) with no ownership checkAuth::id() comparison, no authorize(), no Policy/Gate anywhere in the controllerGET /student/grades/{id} behind auth middlewaregrade->user_id (exposes whose record it is)// [VULNERABLE HERE] comment is present above Grade::find($id)/student/grades/<student_B_id> → data is displayed| Action | URL | Effect |
|---|---|---|
| View own grade | /student/grades/3 | Normal — your record |
| View another student's grade | /student/grades/1 | IDOR — another student's data |
| Enumerate all records | /student/grades/1, 2, 3, … | Full data harvest by ID walking |
Context: This application runs in a closed, local training environment. These payloads are provided so trainers can verify the vulnerability is correctly implemented.
create-vuln-login skillcreate-vuln-upload skillcreate-vuln-download skill