From 2735db464ac3bdf15b7c1b8df1b53326097a62fe Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@suma-ev.de> Date: Tue, 15 Sep 2020 13:30:19 +0200 Subject: [PATCH] added browserverification --- app/Http/Controllers/HumanVerification.php | 81 +++++++++++++++++++ app/Http/Kernel.php | 1 + app/Http/Middleware/BrowserVerification.php | 62 ++++++++++++++ chart/templates/deployment.yaml | 6 ++ composer.json | 11 +-- config/metager/metager.php | 5 ++ resources/lang/de/429.php | 6 ++ resources/lang/en/429.php | 6 ++ resources/views/errors/404.blade.php | 6 ++ resources/views/errors/429.blade.php | 14 ++++ .../resultpage/verificationHeader.blade.php | 5 ++ routes/web.php | 3 +- 12 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 app/Http/Middleware/BrowserVerification.php create mode 100644 config/metager/metager.php create mode 100644 resources/lang/de/429.php create mode 100644 resources/lang/en/429.php create mode 100644 resources/views/errors/429.blade.php create mode 100644 resources/views/layouts/resultpage/verificationHeader.blade.php diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php index 0ff75e435..6f5462314 100644 --- a/app/Http/Controllers/HumanVerification.php +++ b/app/Http/Controllers/HumanVerification.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Captcha; use Carbon; +use Cookie; use Illuminate\Hashing\BcryptHasher as Hasher; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -270,4 +271,84 @@ class HumanVerification extends Controller HumanVerification::saveUser($user); return redirect('admin/bot'); } + + public function browserVerification(Request $request) + { + $key = $request->input("id", ""); + + // Verify that key is a md5 checksum + if (!preg_match("/^[a-f0-9]{32}$/", $key)) { + abort(404); + } + + Redis::connection("cache")->pipeline(function ($redis) use ($key) { + $redis->rpush($key, true); + $redis->expire($key, 30); + }); + + return response("", 200)->header("Content-Type", "text/css"); + } + + public static function block(Request $request) + { + $prefix = "humanverification"; + + $ip = $request->ip(); + $id = ""; + $uid = ""; + if (\App\Http\Controllers\HumanVerification::couldBeSpammer($ip)) { + $id = hash("sha1", "999.999.999.999"); + $uid = hash("sha1", "999.999.999.999" . $ip . $_SERVER["AGENT"] . "uid"); + } else { + $id = hash("sha1", $ip); + $uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid"); + } + + /** + * If the user sends a Password or a key + * We will not verificate the user. + * If someone that uses a bot finds this out we + * might have to change it at some point. + */ + if ($request->filled('password') || $request->filled('key') || Cookie::get('key') !== null || $request->filled('appversion') || !env('BOT_PROTECTION', false)) { + $update = false; + return $next($request); + } + + # Get all Users of this IP + $users = Cache::get($prefix . "." . $id, []); + + $user = []; + $changed = false; + if (empty($users[$uid])) { + $user = [ + 'uid' => $uid, + 'id' => $id, + 'unusedResultPages' => 0, + 'whitelist' => false, + 'locked' => true, + "lockedKey" => "", + "expiration" => now()->addWeeks(2), + ]; + $changed = true; + } else { + $user = $users[$uid]; + if (!$user["locked"]) { + $user["locked"] = true; + $changed = true; + } + } + + if ($user["whitelist"]) { + $user["expiration"] = now()->addWeeks(2); + } else { + $user["expiration"] = now()->addHours(72); + } + if ($changed) { + $userList = Cache::get($prefix . "." . $user["id"], []); + $userList[$user["uid"]] = $user; + Cache::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60); + } + return [$id, $uid]; + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 24a8c577a..cb14ae702 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -62,5 +62,6 @@ class Kernel extends HttpKernel 'referer.check' => \App\Http\Middleware\RefererCheck::class, 'humanverification' => \App\Http\Middleware\HumanVerification::class, 'useragentmaster' => \App\Http\Middleware\UserAgentMaster::class, + 'browserverification' => \App\Http\Middleware\BrowserVerification::class, ]; } diff --git a/app/Http/Middleware/BrowserVerification.php b/app/Http/Middleware/BrowserVerification.php new file mode 100644 index 000000000..463fff13b --- /dev/null +++ b/app/Http/Middleware/BrowserVerification.php @@ -0,0 +1,62 @@ +<?php + +namespace App\Http\Middleware; + +use Closure; +use GrahamCampbell\Throttle\Facades\Throttle; +use Illuminate\Support\Facades\Redis; +use \App\Http\Controllers\HumanVerification; + +class BrowserVerification +{ + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $bvEnabled = config("metager.metager.browserverification_enabled"); + if (empty($bwEnabled) || !$bvEnabled) { + return $next($request); + } + + // Check if throttled + $accept = Throttle::check($request, 8, 1); + if (!$accept) { + Throttle::hit($request, 8, 1); + abort(429); + } + header('Content-type: text/html; charset=utf-8'); + header('X-Accel-Buffering: no'); + ini_set('zlib.output_compression', 'Off'); + ini_set('output_buffering', 'Off'); + ini_set('output_handler', ''); + + ob_end_clean(); + + $key = md5($request->ip() . microtime(true)); + + echo (view('layouts.resultpage.verificationHeader')->with('key', $key)->render()); + flush(); + + $answer = boolval(Redis::connection("cache")->blpop($key, 5)); + + if ($answer === true) { + return $next($request); + } else { + $accept = Throttle::attempt($request, 8, 1); + if (!$accept) { + abort(429); + } + + # Lockout + $ids = HumanVerification::block($request); + } + + return redirect()->route('captcha', ["id" => $ids[0], "uid" => $ids[1], "url" => url()->full()]); + + } +} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 25bf29e5e..62ff4b8d2 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -63,6 +63,9 @@ spec: - name: blacklist-ad secret: secretName: metager-ad-blacklist + - name: metager-config + configMap: + name: metager containers: - name: {{ .Chart.Name }}-phpfpm image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" @@ -93,6 +96,9 @@ spec: initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} volumeMounts: + - name: metager-config + mountPath: /html/config + readOnly: true - name: mglogs-persistent-storage mountPath: /html/storage/logs/metager readOnly: false diff --git a/composer.json b/composer.json index 1bc55bcf8..51824d44b 100644 --- a/composer.json +++ b/composer.json @@ -8,17 +8,18 @@ ], "license": "MIT", "require": { - "laravel/framework": "5.8.*", "php": "^7.1.3", + "endclothing/prometheus_client_php": "^1.0", "fideloper/proxy": "^4.0", - "laravel/tinker": "^1.0", "globalcitizen/php-iban": "^2.6", + "graham-campbell/throttle": "^7.5", "jenssegers/agent": "^2.6", + "laravel/framework": "5.8.*", + "laravel/tinker": "^1.0", "mcamara/laravel-localization": "dev-master#13f418e481ed06f482e4fca87ec5ff67c2949373", "mews/captcha": "^2.2", "predis/predis": "^1.1", - "symfony/dom-crawler": "^4.1", - "endclothing/prometheus_client_php": "^1.0" + "symfony/dom-crawler": "^4.1" }, "require-dev": { "beyondcode/laravel-dump-server": "^1.0", @@ -69,4 +70,4 @@ "@php artisan key:generate --ansi" ] } -} \ No newline at end of file +} diff --git a/config/metager/metager.php b/config/metager/metager.php new file mode 100644 index 000000000..cb7825e6f --- /dev/null +++ b/config/metager/metager.php @@ -0,0 +1,5 @@ +<?php + +return [ + "browserverification_enabled" => true, +]; diff --git a/resources/lang/de/429.php b/resources/lang/de/429.php new file mode 100644 index 000000000..609117299 --- /dev/null +++ b/resources/lang/de/429.php @@ -0,0 +1,6 @@ +<?php + +return [ + 'title' => '429 - Zu viele Anfragen', + 'text' => '', +]; diff --git a/resources/lang/en/429.php b/resources/lang/en/429.php new file mode 100644 index 000000000..ccc7579bf --- /dev/null +++ b/resources/lang/en/429.php @@ -0,0 +1,6 @@ +<?php + +return [ + 'title' => '429 - Too many Requests', + 'text' => '', +]; diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index f4f87a305..0f928a9db 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -3,6 +3,12 @@ @section('title', 'Fehler 404 - Seite nicht gefunden') @section('content') + <style> + main#main-content { + align-items: center; + justify-content: center; + } + </style> <h1>{{ trans('404.title') }}</h1> <p>{{ trans('404.text') }}</p> @endsection diff --git a/resources/views/errors/429.blade.php b/resources/views/errors/429.blade.php new file mode 100644 index 000000000..4592b922a --- /dev/null +++ b/resources/views/errors/429.blade.php @@ -0,0 +1,14 @@ +@extends('layouts.subPages') + +@section('title', trans('429.title')) + +@section('content') + <style> + main#main-content { + align-items: center; + justify-content: center; + } + </style> + <h1>{{ trans('429.title') }}</h1> + <p>{{ trans('429.text') }}</p> +@endsection diff --git a/resources/views/layouts/resultpage/verificationHeader.blade.php b/resources/views/layouts/resultpage/verificationHeader.blade.php new file mode 100644 index 000000000..58e20f52f --- /dev/null +++ b/resources/views/layouts/resultpage/verificationHeader.blade.php @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="/index.css?id={{ $key }}"> diff --git a/routes/web.php b/routes/web.php index ce435e617..ede8a3b07 100644 --- a/routes/web.php +++ b/routes/web.php @@ -195,13 +195,14 @@ Route::group( return redirect(LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), '/')); }); - Route::match(['get', 'post'], 'meta/meta.ger3', 'MetaGerSearch@search')->middleware('humanverification', 'useragentmaster'); + Route::match(['get', 'post'], 'meta/meta.ger3', 'MetaGerSearch@search')->middleware('browserverification', 'humanverification', 'useragentmaster'); Route::get('meta/loadMore', 'MetaGerSearch@loadMore'); Route::post('img/cat.jpg', 'HumanVerification@remove'); Route::get('verify/metager/{id}/{uid}', ['as' => 'captcha', 'uses' => 'HumanVerification@captcha', 'middleware' => 'throttle:12,1']); Route::get('r/metager/{mm}/{pw}/{url}', ['as' => 'humanverification', 'uses' => 'HumanVerification@removeGet']); Route::post('img/dog.jpg', 'HumanVerification@whitelist'); + Route::get('index.css', 'HumanVerification@browserVerification'); Route::get('meta/picture', 'Pictureproxy@get'); Route::get('clickstats', 'LogController@clicklog'); -- GitLab