diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php new file mode 100644 index 0000000000000000000000000000000000000000..fca51a103d21742545ca6693c6c0a276fb57e8e8 --- /dev/null +++ b/app/Http/Controllers/HumanVerification.php @@ -0,0 +1,68 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Http\Request; +use Validator; +use Input; +use DB; +use Carbon; + +class HumanVerification extends Controller +{ + public static function captcha(Request $request, $id, $url){ + if($request->getMethod() == 'POST'){ + $rules = ['captcha' => 'required|captcha']; + $validator = Validator::make($request->all(), $rules); + if($validator->fails()){ + return view('captcha')->with('title', 'Bestätigung notwendig')->with('id', $id)->with('url', base64_decode($url))->with('errorMessage', 'Bitte Captcha eingeben:'); + }else{ + # If we can unlock the Account of this user we will redirect him to the result page + $id = $request->input('id'); + $url = $request->input('url'); + + $user = DB::table('humanverification')->where('id', $id)->first(); + if($user !== null && $user->locked === "1"){ + DB::table('humanverification')->where('id', $id)->update(['locked' => false]); + return redirect($url); + }else{ + return redirect('/'); + } + } + } + return view('captcha')->with('title', 'Bestätigung notwendig')->with('id', $id)->with('url', base64_decode($url)); + } + + public static function remove(Request $request){ + if(!$request->has('mm')){ + abort(404, "Keine Katze gefunden."); + } + $id = md5($request->ip()); + if(HumanVerification::checkId($request, $request->input('mm'))){ + # Remove the entry from the database + DB::table('humanverification')->where('id', $id)->where('updated_at', '<', Carbon::NOW()->subSeconds(2) )->delete(); + } + return response(hex2bin('89504e470d0a1a0a0000000d494844520000000100000001010300000025db56ca00000003504c5445000000a77a3dda0000000174524e530040e6d8660000000a4944415408d76360000000020001e221bc330000000049454e44ae426082'), 200) + ->header('Content-Type', 'image/png'); + } + + public static function removeGet(Request $request, $mm, $password, $url){ + $url = base64_decode($url); + + # If the user is correct and the password is we will delete any entry in the database + $requiredPass = md5($mm . Carbon::NOW()->day . $url . env("PROXY_PASSWORD")); + if(HumanVerification::checkId($request, $mm) && $requiredPass === $password){ + # Remove the entry from the database + DB::table('humanverification')->where('id', $mm)->where('updated_at', '<', Carbon::NOW()->subSeconds(2) )->delete(); + } + return redirect($url); + } + + private static function checkId($request, $id){ + if(md5($request->ip()) === $id){ + return true; + }else{ + return false; + } + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 308d74c647cb52d7640473d45046b2e90f506d18..3d09c78a379345664050c61c227711dd9e9aaa0a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends HttpKernel protected $middleware = [ \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class, \Fideloper\Proxy\TrustProxies::class, + // \App\Http\Middleware\VerifyCsrfToken::class, ]; /** @@ -26,10 +27,6 @@ class Kernel extends HttpKernel protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - #\App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], @@ -37,6 +34,15 @@ class Kernel extends HttpKernel 'throttle:60,1', 'bindings', ], + + 'session' => [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], ]; /** @@ -54,5 +60,6 @@ class Kernel extends HttpKernel 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'referer.check' => \App\Http\Middleware\RefererCheck::class, + 'humanverification' => \App\Http\Middleware\HumanVerification::class, ]; } diff --git a/app/Http/Middleware/HumanVerification.php b/app/Http/Middleware/HumanVerification.php new file mode 100644 index 0000000000000000000000000000000000000000..b4f5e4b912b9084b4be399c66f92935ab054f133 --- /dev/null +++ b/app/Http/Middleware/HumanVerification.php @@ -0,0 +1,64 @@ +<?php + +namespace App\Http\Middleware; + +use Closure; +use DB; +use Carbon; + +class HumanVerification +{ + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle($request, Closure $next) + { + $id = md5($request->ip()); + + /** + * 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')){ + return $next($request); + } + + $user = DB::table('humanverification')->where($id, $id)->first(); + $createdAt = now(); + $unusedResultPages = 1; + $locked = false; + # If this user doesn't have an entry we will create one + if($user === null){ + DB::table('humanverification')->insert( + ['id' => $id, 'unusedResultPages' => 1, 'locked' => false, 'updated_at' => now()] + ); + $user = DB::table('humanverification')->where($id, $id)->first(); + }else if($user->locked !== "1"){ + $unusedResultPages = intval($user->unusedResultPages); + $unusedResultPages++; + # We have different security gates: + # 50, 75, 85, >=90 => Captcha validated Result Pages + # If the user shows activity on our result page the counter will be deleted + # Maybe I'll add a ban if the user reaches 100 + if($unusedResultPages === 50){ + $locked = true; + } + DB::table('humanverification')->where('id', $id)->update(['unusedResultPages' => $unusedResultPages, 'locked' => $locked, 'updated_at' => $createdAt]); + } + $request->request->add(['verification_id' => $id, 'verification_count' => $unusedResultPages]); + + + # If the user is locked we will force a Captcha validation + if($user->locked === "1"){ + return redirect('meta/verification/' . $id . '/' . urlencode(base64_encode(url()->full()))); + } + + return $next($request); + } +} diff --git a/app/MetaGer.php b/app/MetaGer.php index a9b0e341b0b63a4e86b099d4eb98a7217c7b4b04..4b792318a787e54d11d67e20c8af5ae7abed721b 100644 --- a/app/MetaGer.php +++ b/app/MetaGer.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Redis; use Jenssegers\Agent\Agent; use LaravelLocalization; use Log; +use Carbon; use Predis\Connection\ConnectionException; class MetaGer @@ -53,12 +54,13 @@ class MetaGer protected $urlsBlacklisted = []; protected $url; protected $languageDetect; + protected $verificationId; + protected $verificationCount; public function __construct() { # Timer starten $this->starttime = microtime(true); - # Versuchen Blacklists einzulesen if (file_exists(config_path() . "/blacklistDomains.txt") && file_exists(config_path() . "/blacklistUrl.txt")) { $tmp = file_get_contents(config_path() . "/blacklistDomains.txt"); @@ -252,6 +254,9 @@ class MetaGer #Adgoal Implementation $this->results = $this->parseAdgoal($this->results); + # Human Verification + $this->results = $this->humanVerification($this->results); + $counter = 0; $firstRank = 0; @@ -447,6 +452,22 @@ class MetaGer return $results; } + public function humanVerification($results){ + # Let's check if we need to implement a redirect for human verification + if($this->verificationCount > 10){ + foreach($results as $result){ + $link = $result->link; + $day = Carbon::now()->day; + $pw = md5($this->verificationId . $day . $link . env("PROXY_PASSWORD")); + $url = route('humanverification', ['mm' => $this->verificationId, 'pw' => $pw, "url" => urlencode(base64_encode($link))]); + $result->link = $url; + } + return $results; + }else{ + return $results; + } + } + public function authorize($key) { $postdata = http_build_query(array( @@ -1022,6 +1043,8 @@ class MetaGer $this->quicktips = true; } + $this->verificationId = $request->input('verification_id', null); + $this->verificationCount = intval($request->input('verification_count', '0')); $this->apiKey = $request->input('key', ''); $this->validated = false; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 87ffb05a9faf971475fc6ed15eaf763445ed3000..20d6563527e7613e46b36372c6b9ded21ebc9579 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -39,6 +39,8 @@ class RouteServiceProvider extends ServiceProvider $this->mapWebRoutes(); + $this->mapSessionRoutes(); + // } @@ -76,4 +78,21 @@ class RouteServiceProvider extends ServiceProvider require base_path('routes/api.php'); }); } + + /** + * Define the "session" routes for the application. + * + * These routes all receive session state, CSRF protection, etc. + * + * @return void + */ + protected function mapSessionRoutes() + { + Route::group([ + 'middleware' => 'session', + 'namespace' => $this->namespace, + ], function ($router) { + require base_path('routes/session.php'); + }); + } } diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json index 0a1b510ac572d1d2b156feb8933f2a9641cc6c98..9a80f170c79b4fa5b4838ff4983dbfc351c56bb6 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "laravel/framework": "5.5.*", "laravelcollective/html": "^5.2.0", "mcamara/laravel-localization": "^1.1", + "mews/captcha": "^2.2", "piwik/piwik-php-tracker": "^1.0", "predis/predis": "^1.1" }, diff --git a/config/app.php b/config/app.php index 30b6eaa039f389cb2e292832caffdadff4ed1e96..a060739ed517cd79a25f73731bdbb975645ee95a 100644 --- a/config/app.php +++ b/config/app.php @@ -182,6 +182,7 @@ return [ Jenssegers\Agent\AgentServiceProvider::class, Fideloper\Proxy\TrustedProxyServiceProvider::class, Collective\Html\HtmlServiceProvider::class, + Mews\Captcha\CaptchaServiceProvider::class, ], /* @@ -233,6 +234,8 @@ return [ 'Agent' => Jenssegers\Agent\Facades\Agent::class, 'Form' => Collective\Html\FormFacade::class, 'HTML' => Collective\Html\HtmlFacade::class, + 'Captcha' => Mews\Captcha\Facades\Captcha::class, + 'Carbon' => Carbon\Carbon::class, ], ]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000000000000000000000000000000000000..c66516e681014b2d55d8c5d098ddfee64d6fd9c5 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,45 @@ +<?php + +return [ + + 'characters' => '2346789abcdefghjmnpqrtuxyzABCDEFGHJMNPQRTUXYZ', + + 'default' => [ + 'length' => 5, + 'width' => 220, + 'height' => 66, + 'quality' => 90, + ], + + 'flat' => [ + 'length' => 6, + 'width' => 160, + 'height' => 46, + 'quality' => 90, + 'lines' => 6, + 'bgImage' => false, + 'bgColor' => '#ecf2f4', + 'fontColors'=> ['#2c3e50', '#c0392b', '#16a085', '#c0392b', '#8e44ad', '#303f9f', '#f57c00', '#795548'], + 'contrast' => -5, + ], + + 'mini' => [ + 'length' => 3, + 'width' => 60, + 'height' => 32, + ], + + 'inverse' => [ + 'length' => 5, + 'width' => 120, + 'height' => 36, + 'quality' => 90, + 'sensitive' => true, + 'angle' => 12, + 'sharpen' => 10, + 'blur' => 2, + 'invert' => true, + 'contrast' => -5, + ] + +]; diff --git a/database/migrations/2018_04_26_114745_create_humanverification_table.php b/database/migrations/2018_04_26_114745_create_humanverification_table.php new file mode 100644 index 0000000000000000000000000000000000000000..877cbf33b6c66ef41428285e1833150133dd0157 --- /dev/null +++ b/database/migrations/2018_04_26_114745_create_humanverification_table.php @@ -0,0 +1,33 @@ +<?php + +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; + +class CreateHumanverificationTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('humanverification', function (Blueprint $table) { + $table->string('id'); + $table->integer('unusedResultPages'); + $table->boolean('locked'); + $table->date('updated_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('humanverification'); + } +} diff --git a/readme.md b/readme.md index 864f3363735b17e3ebc1a5895a2b78402d72602f..49f6f125da7d61a4dbb84a04cccc6232013f3868 100644 --- a/readme.md +++ b/readme.md @@ -15,6 +15,7 @@ * php-xml * php-zip * php-redis + * php-gd * sqlite3 * redis-server * Die Perl-Pakete diff --git a/resources/assets/js/scriptResultPage.js b/resources/assets/js/scriptResultPage.js index dcac046b4f664a1d3734224856fa8c279767ec42..b4cf3893f7f39cc61507442446fcc9b556060b30 100644 --- a/resources/assets/js/scriptResultPage.js +++ b/resources/assets/js/scriptResultPage.js @@ -133,11 +133,9 @@ function clickLog () { } function botProtection () { - if ($('meta[name=pqr]').length > 0) { - var link = atob($('meta[name=pqr]').attr('content')); - var hash = $('meta[name=pq]').attr('content'); - document.location.href = link + '&bot=' + hash; - } + $(".result").find("a").click(function(){ + $.post('/img/cat.jpg', { mm: $("meta[name=mm]").attr("content")}); + }); } function popovers () { diff --git a/resources/views/captcha.blade.php b/resources/views/captcha.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..b6a26af7949b7171f848115460f3de8fd0a82d4c --- /dev/null +++ b/resources/views/captcha.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.subPages') + +@section('title', $title ) + +@section('content') + <h1>Entschuldigen Sie die Störung</h1> + <p>Wir haben Grund zur Annahme, dass von Ihrem Anschluss verstärkt automatisierte Abfragen abgeschickt wurden. + Deshalb bitten wir Sie, die nachfolgende Captcha Abfrage zu beantworten.</p> + <p>Sollten Sie diese Nachricht häufiger sehen oder handelt es sich dabei um einen Irrtum, schicken Sie uns gerne eine Nachricht über unser <a href="/kontakt">Kontaktformular</a>.</p> + <p>Nennen Sie uns in diesem Fall bitte unbedingt folgende Vorgangsnummer: {{ $id }} + <p>Wir schauen uns den Vorgang dann gerne im Detail an.</p> + <form method="post"> + {{ csrf_field() }} + <input type="hidden" name="url" value="{!! $url !!}"> + <input type="hidden" name="id" value="{{ $id }}"> + <p>{!! captcha_img() !!}</p> + @if(isset($errorMessage)) + <p><font color="red">{{$errorMessage}}</font></p> + @endif + <p><input type="text" name="captcha"></p> + <p><button type="submit" name="check">OK</button></p> + </form> + <p>Hinweis: Zum Zwecke der Autorisierung wird auf dieser Seite ein Session Cookie gesetzt. +@endsection \ No newline at end of file diff --git a/resources/views/layouts/resultPage.blade.php b/resources/views/layouts/resultPage.blade.php index b6cd09a7e592fc3ad3d1eccb31ee1a2a1164df2d..b1d3dae2051c3e52e61a3251130ae05b43322025 100644 --- a/resources/views/layouts/resultPage.blade.php +++ b/resources/views/layouts/resultPage.blade.php @@ -8,6 +8,8 @@ <meta name="p" content="{{ getmypid() }}" /> <meta name="q" content="{{ $eingabe }}" /> <meta name="l" content="{{ LaravelLocalization::getCurrentLocale() }}" /> + <meta name="mm" content="{{ Request::input('verification_id') }}" /> + <meta name="mn" content="{{ Request::input('verification_count') }}" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE" /> <meta http-equiv="language" content="{!! trans('staticPages.meta.language') !!}" /> diff --git a/routes/session.php b/routes/session.php new file mode 100644 index 0000000000000000000000000000000000000000..fc776d36b8f302ab40982233e822d371809f5fbe --- /dev/null +++ b/routes/session.php @@ -0,0 +1,7 @@ +<?php +# In this File we collect all routes which require a session or other cookies to be active + + +Route::get('captcha/api/{config?}', '\Mews\Captcha\CaptchaController@getCaptchaApi')->middleware('session'); +Route::get('captcha/{config?}', '\Mews\Captcha\CaptchaController@getCaptcha')->middleware('session'); +Route::match(['get', 'post'], 'meta/verification/{id}/{url}', 'HumanVerification@captcha'); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 7cf901bc957a72911c664c76387ba78988d2a39d..20035702612027348ab579505a2123ebe66d093e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,7 +10,6 @@ | to using a Closure or controller method. Build something great! | */ - Route::group( [ 'prefix' => LaravelLocalization::setLocale(), /*, @@ -140,7 +139,10 @@ Route::group( Route::get('settings', 'StartpageController@loadSettings'); - Route::match(['get', 'post'], 'meta/meta.ger3', 'MetaGerSearch@search'); + Route::match(['get', 'post'], 'meta/meta.ger3', 'MetaGerSearch@search')->middleware('humanverification'); + Route::post('img/cat.jpg', 'HumanVerification@remove'); + Route::get('r/metager/{mm}/{pw}/{url}', ['as' => 'humanverification', 'uses' => 'HumanVerification@removeGet']); + Route::get('meta/picture', 'Pictureproxy@get'); Route::get('clickstats', 'LogController@clicklog'); Route::get('pluginClose', 'LogController@pluginClose'); diff --git a/storage/app/.gitignore b/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/storage/app/public/MetaGer-release.apk b/storage/app/public/MetaGer-release.apk old mode 100644 new mode 100755 diff --git a/storage/app/public/zitate.txt b/storage/app/public/zitate.txt old mode 100644 new mode 100755 diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore old mode 100644 new mode 100755