diff --git a/web/.env.example b/web/.env.example index 7f2cae8..56b0c19 100644 --- a/web/.env.example +++ b/web/.env.example @@ -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 diff --git a/web/app/Http/Controllers/Auth/DoubleSessionBlockController.php b/web/app/Http/Controllers/Auth/DoubleSessionBlockController.php new file mode 100644 index 0000000..a22a1b1 --- /dev/null +++ b/web/app/Http/Controllers/Auth/DoubleSessionBlockController.php @@ -0,0 +1,38 @@ +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.'); + } + } +} diff --git a/web/app/Http/Kernel.php b/web/app/Http/Kernel.php index e87cf66..3397e73 100644 --- a/web/app/Http/Kernel.php +++ b/web/app/Http/Kernel.php @@ -14,6 +14,8 @@ class Kernel extends HttpKernel * @var array */ 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 ], diff --git a/web/app/Http/Middleware/DoubleSessionProtector.php b/web/app/Http/Middleware/DoubleSessionProtector.php new file mode 100644 index 0000000..2fba5cb --- /dev/null +++ b/web/app/Http/Middleware/DoubleSessionProtector.php @@ -0,0 +1,59 @@ +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); + } +} diff --git a/web/app/Models/Session.php b/web/app/Models/Session.php new file mode 100644 index 0000000..58500de --- /dev/null +++ b/web/app/Models/Session.php @@ -0,0 +1,31 @@ + + */ + protected $hidden = [ + 'id', + 'ip_address', + 'payload', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'id' => 'string', + ]; +} diff --git a/web/app/Rules/GoogleRecaptcha.php b/web/app/Rules/GoogleRecaptcha.php new file mode 100644 index 0000000..a1e1060 --- /dev/null +++ b/web/app/Rules/GoogleRecaptcha.php @@ -0,0 +1,64 @@ + '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.'; + } +} diff --git a/web/config/session.php b/web/config/session.php index ee5451c..21917e8 100644 --- a/web/config/session.php +++ b/web/config/session.php @@ -18,7 +18,7 @@ return [ | */ - 'driver' => env('SESSION_DRIVER', 'file'), + 'driver' => 'database', /* |-------------------------------------------------------------------------- diff --git a/web/database/migrations/2022_05_05_203337_create_sessions_table.php b/web/database/migrations/2022_05_05_203337_create_sessions_table.php new file mode 100644 index 0000000..b5444bc --- /dev/null +++ b/web/database/migrations/2022_05_05_203337_create_sessions_table.php @@ -0,0 +1,37 @@ +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'); + } +}; diff --git a/web/resources/views/auth/ddos_blocked.blade.php b/web/resources/views/auth/ddos_blocked.blade.php new file mode 100644 index 0000000..f4882a3 --- /dev/null +++ b/web/resources/views/auth/ddos_blocked.blade.php @@ -0,0 +1,51 @@ +@php + $noFooter = true; + $noNav = true; +@endphp + +@extends('layouts.app') + +@section('title', 'Captcha') + +@section('extra-headers') + +@endsection + +@section('content') +
+ + + BOOP BOOP. BEEP? + + +
+
Are you a ROBOT?
+

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.

+ +
+ @if ($errors->any()) +
+
{{ $errors->first() }}
+
+ @endif + +
+ @csrf + + + +
+
+
+ + +
+
+
+
+ +

If this page continues to show up after you solve the captcha, please contact us at contact us at support@gtoria.net and we'll be happy to help.

+
+
+
+@endsection diff --git a/web/routes/web.php b/web/routes/web.php index 3b1865c..10fa8c2 100644 --- a/web/routes/web.php +++ b/web/routes/web.php @@ -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');