diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php index 0ff75e435ab6dd57adb1c3abbe60e926b109f429..6f546231408d1545732dac20c573e74b9ab5c0c6 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 24a8c577ad62a61ca1483aa960a79c646daf5593..cb14ae7025a3077233dfb5cd09cd401242a20a52 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 0000000000000000000000000000000000000000..463fff13b095ce295f6201f14bd789d708286e9e --- /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 25bf29e5e87ae243868f6199f8477b10c6f308db..62ff4b8d2cd2c8626cd11871b0802a4b9237a23a 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 1bc55bcf84f89cef661abeeb276151310059c14f..51824d44b9bda46b3b7046978aa43678d97808d0 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 0000000000000000000000000000000000000000..cb7825e6ff35e166caaad4bb108e456ae9960652 --- /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 0000000000000000000000000000000000000000..60911729971a159d9d5853a63ba9a28b4aaf49ee --- /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 0000000000000000000000000000000000000000..ccc7579bf259d84c33ab5efc69d11978529a0a7d --- /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 f4f87a3057bccc53b632151c4679f545cbdab650..0f928a9db9a03e120b1787b72b5b93c3ff09f5be 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 0000000000000000000000000000000000000000..4592b922a7e228614cd567fbd511d165be4fd9f7 --- /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 0000000000000000000000000000000000000000..58e20f52f293f1aff832677524db625684233fb1 --- /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 ce435e61790ff2b67a50b7e74afbb6b17abd8603..ede8a3b07ed480aba19b92a5734143280224b0c1 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');