DDoS prevention middleware and recaptcha validation rules added. Still need to implement recaptcha into the existing auth views.

This commit is contained in:
Graphictoria 2022-05-05 23:12:15 -04:00
parent 608b071ee8
commit f8407994cc
10 changed files with 292 additions and 1 deletions

View File

@ -9,6 +9,9 @@ LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
RECAPTCHA_SITE_KEY=ClientKeyHere
RECAPTCHA_SECRET_KEY=ServerKeyHere
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Session;
use Illuminate\Http\Request;
class DoubleSessionBlockController extends Controller
{
public function create()
{
return response()
->view('auth.ddos_blocked', [], 403);
}
public function store()
{
request()->validate([
'g-recaptcha-response' => [new \App\Rules\GoogleRecaptcha]
]);
$record = Session::where('id', session()->getId())->first();
if($record) {
$record->bypass_block_screen = true;
$record->save();
$returnUrl = request()->input('ReturnUrl');
if(!$returnUrl)
$returnUrl = '/';
return redirect(urldecode($returnUrl), 302);
} else {
return redirect()->back()->withErrors('Could not unblock. Try again.');
}
}
}

View File

@ -14,6 +14,8 @@ class Kernel extends HttpKernel
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\DoubleSessionProtector::class, // Prevents DDoS attacks.
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
@ -37,6 +39,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\DoubleSessionProtector::class, // Prevents DDoS attacks.
\App\Http\Middleware\DailyReward::class
],

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use App\Models\Session;
class DoubleSessionProtector
{
protected function handlePage(Request $request, Closure $next) {
if($request->route()->getName() != 'ddos.bypass' && !$request->isMethod('post')) {
return redirect()
->to(route('ddos.bypass', ['ReturnUrl' => urlencode('/'.$request->path())]), 302);
}
return $next($request);
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$record = Session::where('id', session()->getId())->where('bypass_block_screen', true)->first();
if($record) {
if($request->route()->getName() == 'ddos.bypass') {
return redirect('/', 302);
}
return $next($request);
}
/* */
$record = Session::where('ip_address', $request->ip());
if($record->exists()) {
foreach($record->get() as $session) {
if($session->id != session()->getId())
return $this->handlePage($request, $next);
}
}
if($request->route()->getName() == 'ddos.bypass') {
$returnUrl = $request->input('ReturnUrl');
if(!$returnUrl)
$returnUrl = '/';
return redirect('/', 302);
}
return $next($request);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Session extends Model
{
use HasFactory;
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'id',
'ip_address',
'payload',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'id' => 'string',
];
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use GuzzleHttp\Client;
class GoogleRecaptcha implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the error messages for the defined validation rules.
*
* @return array
*/
public function messages()
{
return [
'g-recaptcha-response.required' => 'Please solve the captcha.'
];
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$client = new Client();
$response = $client->post('https://www.google.com/recaptcha/api/siteverify',
[
'form_params' => [
'secret' => env('RECAPTCHA_SECRET_KEY'),
'remoteip' => request()->ip(),
'response' => $value
]
]
);
$body = json_decode((string)$response->getBody());
return $body->success;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'Please solve the captcha.';
}
}

View File

@ -18,7 +18,7 @@ return [
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
'driver' => 'database',
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
$table->boolean('bypass_block_screen')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,51 @@
@php
$noFooter = true;
$noNav = true;
@endphp
@extends('layouts.app')
@section('title', 'Captcha')
@section('extra-headers')
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
@endsection
@section('content')
<div class="container m-auto">
<x-card>
<x-slot name="title">
BOOP BOOP. BEEP?
</x-slot>
<x-slot name="body">
<div class="p-2 mb-2 d-flex flex-column justify-content-center">
<h5>Are you a ROBOT?</h5>
<p>We've detected unusual activity coming from your network. To protect ourselves from DDoS attacks and other forms of web abuse, we've blocked your request. Solve the captcha below to regain access to the website.</p>
<div class="mt-3">
@if ($errors->any())
<div class="px-3 mb-10">
<div class="alert alert-danger graphictoria-alert graphictoria-error-popup">{{ $errors->first() }}</div>
</div>
@endif
<form method="POST" action="{{ route('ddos.bypass') }}">
@csrf
<input type="hidden" name="ReturnUrl" value="{{ request()->input('ReturnUrl') }}" />
<div class="d-flex">
<div class="g-recaptcha mb-2 mx-auto" data-sitekey="{{ env('RECAPTCHA_SITE_KEY') }}"></div>
</div>
<button class="btn btn-primary px-5" type="submit">Continue</button>
</form>
</div>
</div>
</x-slot>
<x-slot name="footer">
<p class="text-muted">If this page continues to show up after you solve the captcha, please contact us at contact us at <a href="mailto:support@gtoria.net" class="fw-bold text-decoration-none">support@gtoria.net</a> and we'll be happy to help.</p>
</x-slot>
</x-card>
</div>
@endsection

View File

@ -2,6 +2,7 @@
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\ConfirmablePasswordController;
use App\Http\Controllers\Auth\DoubleSessionBlockController;
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\Auth\EmailVerificationPromptController;
use App\Http\Controllers\Auth\NewPasswordController;
@ -30,6 +31,10 @@ Route::get('/my/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
Route::get('request-blocked', [DoubleSessionBlockController::class, 'create'])
->name('ddos.bypass');
Route::post('request-blocked', [DoubleSessionBlockController::class, 'store']);
Route::middleware('guest')->group(function () {
Route::get('register', [RegisteredUserController::class, 'create'])
->name('register');