Commit f7bea183 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

pcso

parent 9c613d7b
......@@ -32,6 +32,12 @@ class HumanVerification extends Controller
}
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($request->filled("bv_key") && Cache::has($request->input("bv_key"))) {
$bv_data = Cache::get($request->input("bv_key"));
if (is_array($bv_data) && \array_key_exists("js_picasso", $bv_data)) {
$human_verification->__construct($bv_data["js_picasso"]);
}
}
if (!$human_verification->isLocked()) {
return redirect($redirect_url);
......@@ -42,6 +48,7 @@ class HumanVerification extends Controller
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('uid', $human_verification->uid)
->with('id', $human_verification->id)
->with('bv_key', $request->input("bv_key", ""))
->with('url', $redirect_url)
->with('correct', $captcha["key"])
->with('image', $captcha["img"])
......@@ -61,6 +68,12 @@ class HumanVerification extends Controller
$redirect_url = url("/");
}
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($request->filled("bv_key") && Cache::has($request->input("bv_key"))) {
$bv_data = Cache::get($request->input("bv_key"));
if (is_array($bv_data) && \array_key_exists("js_picasso", $bv_data)) {
$human_verification->__construct($bv_data["js_picasso"]);
}
}
$lockedKey = $request->post("c", "");
......@@ -68,7 +81,14 @@ class HumanVerification extends Controller
$validator = validator()->make(request()->all(), $rules);
if (empty($lockedKey) || $validator->fails()) {
return redirect(route('captcha_show', ["url" => $redirect_url, "e" => ""]));
$params = [
"url" => $redirect_url,
"e" => "",
];
if ($request->filled("bv_key")) {
$params["bv_key"] = $request->input("bv_key");
}
return redirect(route('captcha_show', $params));
} else {
\App\PrometheusExporter::CaptchaCorrect();
# Generate a token that makes the user skip Humanverification
......@@ -95,8 +115,8 @@ class HumanVerification extends Controller
# If we can unlock the Account of this user we will redirect him to the result page
# The Captcha was correct. We can remove the key from the user
# Additionally we will whitelist him so he is not counted towards botnetwork
$human_verification->verifyUser();
$human_verification->unlockUser();
$human_verification->verifyUser();
return redirect($url);
}
......@@ -133,6 +153,13 @@ class HumanVerification extends Controller
}
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($request->filled("bv_key") && Cache::has($request->input("bv_key"))) {
$bv_data = Cache::get($request->input("bv_key"));
if (\is_array($bv_data) && \array_key_exists("js_picasso", $bv_data)) {
$human_verification->__construct($bv_data["js_picasso"]);
}
Cache::forget($request->input("bv_key"));
}
if ($request->input("mm") === $human_verification->uid) {
$human_verification->verifyUser();
}
......@@ -148,6 +175,13 @@ class HumanVerification extends Controller
$requiredPass = md5($mm . Carbon::NOW()->day . $url . config("metager.metager.proxy.password"));
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($request->filled("bv_key") && Cache::has($request->input("bv_key"))) {
$bv_data = Cache::get($request->input("bv_key"));
if (\is_array($bv_data) && \array_key_exists("js_picasso", $bv_data)) {
$human_verification->__construct($bv_data["js_picasso"]);
}
Cache::forget($request->input("bv_key"));
}
if ($mm === $human_verification->uid && $requiredPass == $password) {
$human_verification->verifyUser();
}
......@@ -155,25 +189,21 @@ class HumanVerification extends Controller
return redirect($url);
}
private static function saveUser($user)
public function botOverview(Request $request)
{
$userList = Cache::get(HumanVerification::PREFIX . "." . $user["id"], []);
if ($user["whitelist"]) {
$user["expiration"] = now()->addWeeks(2);
} else {
$user["expiration"] = now()->addHours(72);
$picasso_hash = null;
if ($request->filled("pcso")) {
$picasso_hash = $request->input("pcso");
}
$userList[$user["uid"]] = $user;
Cache::put(HumanVerification::PREFIX . "." . $user["id"], $userList, now()->addWeeks(2));
}
public function botOverview(Request $request)
{
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($picasso_hash !== null) {
$human_verification->__construct($picasso_hash);
}
return view('humanverification.botOverview')
->with('title', "Bot Overview")
->with('picasso_hash', $picasso_hash)
->with('ip', $request->ip())
->with('userList', $human_verification->getUserList())
->with('user', $human_verification->getUser())
......@@ -184,7 +214,9 @@ class HumanVerification extends Controller
public function botOverviewChange(Request $request)
{
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($request->filled("pcso")) {
$human_verification->__construct($request->input("pcso"));
}
if ($request->filled("locked")) {
if (\boolval($request->input("locked"))) {
$human_verification->lockUser();
......@@ -214,7 +246,7 @@ class HumanVerification extends Controller
if ($bvData === null) {
$bvData = [];
}
$bvData["css_loaded"] = true;
$bvData["css_loaded"] = now();
Cache::put($key, $bvData, now()->addSeconds(30));
}
return response(view('layouts.resultpage.verificationCss', ["url" => route("bv_verificationimage", ["id" => $key])]), 200)->header("Content-Type", "text/css");
......@@ -225,18 +257,20 @@ class HumanVerification extends Controller
$key = $request->input("id", "");
// Verify that key is a md5 checksum
if (!preg_match("/^[a-f0-9]{32}$/", $key)) {
if (!preg_match("/^[a-f0-9]{32}$/", $key) || !$request->filled("c")) {
abort(404);
}
$picasso_hash = $request->input('c');
$bvData = Cache::get($key);
if ($bvData === null) {
$bvData = [];
}
$bvData["js_loaded"] = true;
$bvData["js_loaded"] = now();
$bvData["js_picasso"] = $picasso_hash;
Cache::put($key, $bvData, now()->addSeconds(30));
return response("", 200)->header("Content-Type", "application/javascript");
return response()->file(\public_path("img/1px.png", ["Content-Type" => "image/png"]));
}
public function verificationImage(Request $request)
......@@ -252,7 +286,7 @@ class HumanVerification extends Controller
if ($bvData === null) {
$bvData = [];
}
$bvData["css_image_loaded"] = true;
$bvData["css_image_loaded"] = now();
Cache::put($key, $bvData, now()->addSeconds(30));
return response()->file(\public_path("img/1px.png", ["Content-Type" => "image/png"]));
......
......@@ -63,33 +63,15 @@ class BrowserVerification
ini_set('output_handler', '');
ob_end_clean();
$mgv = $request->input('mgv', "");
if (!empty($mgv)) {
if ($request->filled("mgv")) {
$key = $request->input('mgv', "");
// Verify that key is a md5 checksum
if (!preg_match("/^[a-f0-9]{32}$/", $mgv)) {
if (!preg_match("/^[a-f0-9]{32}$/", $key)) {
\app()->make(QueryTimer::class)->observeEnd(self::class);
abort(404);
}
$bvData = null;
$start_time = now();
$wait_time_seconds = 5;
do {
$bvData = Cache::get($mgv);
if ($bvData !== null) {
if ((array_key_exists("css_loaded", $bvData) && $bvData["css_loaded"] === true) ||
(array_key_exists("js_loaded", $bvData) && $bvData["js_loaded"] === true)
) {
break;
} else {
$wait_time_seconds = 2;
}
}
\usleep(50 * 1000);
} while (now()->diffInSeconds($start_time) < $wait_time_seconds);
if ($bvData !== null) {
$search_settings = \app()->make(SearchSettings::class);
$search_settings->jskey = $mgv;
$search_settings->header_printed = false;
if ($this->waitForBV($key)) {
\app()->make(SearchSettings::class)->header_printed = false;
\app()->make(QueryTimer::class)->observeEnd(self::class);
return $next($request);
} else {
......@@ -102,25 +84,20 @@ class BrowserVerification
}
$key = md5($request->ip() . microtime(true));
Cache::put($key, [
"start" => now()
], now()->addMinutes(30));
echo (view('layouts.resultpage.verificationHeader')->with('key', $key)->render());
flush();
$bvData = null;
$start_time = now();
$wait_time_seconds = 2;
do {
$bvData = Cache::get($key);
if ($bvData !== null) {
echo (view('layouts.resultpage.resources')->render());
flush();
$search_settings = \app()->make(SearchSettings::class);
$search_settings->jskey = $key;
$search_settings->header_printed = true;
\app()->make(QueryTimer::class)->observeEnd(self::class);
return $next($request);
}
} while (now()->diffInSeconds($start_time) < $wait_time_seconds);
if ($this->waitForBV($key)) {
echo (view('layouts.resultpage.resources')->render());
flush();
\app()->make(QueryTimer::class)->observeEnd(self::class);
\app()->make(SearchSettings::class)->header_printed = true;
return $next($request);
}
$params = $request->all();
$params["mgv"] = $key;
......@@ -133,6 +110,45 @@ class BrowserVerification
\app()->make(QueryTimer::class)->observeEnd(self::class);
}
private function waitForBV($key)
{
$bvData = null;
$wait_time_inline_verificytion_ms = 2000;
$wait_time_js_ms = null;
do {
$bvData = Cache::get($key);
// This condition is true when at least the css file was loaded
if ($bvData !== null && sizeof($bvData) > 1) {
if (!\array_key_exists("js_loaded", $bvData) && \array_key_exists("css_loaded", $bvData)) {
// CSS File was loaded but Javascript wasn't
if ($wait_time_js_ms === null) {
// Calculate a more acurate wait to since we do know how long it took the browser to load the css file
// we can estimate a more reasonable wait time to check if js is enabled
$load_time_css_ms = $bvData["start"]->diffInMilliseconds($bvData["css_loaded"]);
$wait_time_js_ms = $load_time_css_ms * 3;
$wait_time_inline_verificytion_ms = max($wait_time_js_ms + 500, $wait_time_inline_verificytion_ms);
}
if (now()->diffInMilliseconds($bvData["start"]) <= $wait_time_js_ms) {
usleep(10 * 1000);
continue;
}
}
$search_settings = \app()->make(SearchSettings::class);
if (\array_key_exists("js_loaded", $bvData)) {
$search_settings->bv_key = $key;
$search_settings->javascript_enabled = true;
if (\array_key_exists("js_picasso", $bvData)) {
$search_settings->javascript_picasso = $bvData["js_picasso"];
}
}
return true;
}
usleep(10 * 1000);
} while ($bvData === null || now()->diffInMilliseconds($bvData["start"]) < $wait_time_inline_verificytion_ms);
return false;
}
public static function logBrowserverification(Request $request)
{
$fail2banEnabled = config("metager.metager.fail2ban.enabled");
......
......@@ -68,6 +68,10 @@ class HumanVerification
/** @var ModelsHumanVerification */
$user = App::make(ModelsHumanVerification::class);
$search_settings = \app()->make(SearchSettings::class);
if (!empty($search_settings->javascript_picasso)) {
$user->__construct($search_settings->javascript_picasso);
}
/**
* Directly lock any user when there are many not whitelisted accounts on this IP
......@@ -82,7 +86,9 @@ class HumanVerification
if ($user->isLocked()) {
\App\Http\Controllers\HumanVerification::logCaptcha($request);
\app()->make(QueryTimer::class)->observeEnd(self::class);
return redirect()->route('captcha_show', ["url" => URL::full()]);
$this->logCaptcha($request); // TODO remove
return $next($request); // TODO remove
// return redirect()->route('captcha_show', ["url" => URL::full(), "bv_key" => $search_settings->bv_key]); // TODO uncomment
}
$user->addQuery();
......@@ -91,4 +97,24 @@ class HumanVerification
\app()->make(QueryTimer::class)->observeEnd(self::class);
return $next($request);
}
// TODO remove function
private function logCaptcha(\Illuminate\Http\Request $request)
{
$log = [
now()->format("Y-m-d H:i:s"),
$request->input("eingabe"),
"js=" . \app()->make(SearchSettings::class)->javascript_enabled,
"picasso=" . \app()->make(SearchSettings::class)->javascript_picasso,
];
$file_path = \storage_path("logs/metager/captcha.csv");
$fh = fopen($file_path, "a");
try {
\fputcsv($fh, $log);
} finally {
fclose($fh);
}
// Temporary Log to test new functionality. Will be removed again soon
}
}
......@@ -309,14 +309,6 @@ class MetaGer
$this->adgoalLoaded = false;
}
$search_settings = \app()->make(SearchSettings::class);
if (!empty($search_settings->jskey)) {
$bvData = Cache::get($search_settings->jskey);
if (\array_key_exists("js_loaded", $bvData) && $bvData["js_loaded"] === true) {
$search_settings->javascript_enabled = true;
}
}
# Human Verification
$this->humanVerification($this->results);
$this->humanVerification($this->ads);
......@@ -473,15 +465,28 @@ class MetaGer
{
# Let's check if we need to implement a redirect for human verification
$human_verification = \app()->make(HumanVerification::class);
$search_settings = \app()->make(SearchSettings::class);
if ($human_verification->getVerificationCount() > 10) {
foreach ($results as $result) {
$link = $result->link;
$day = Carbon::now()->day;
$verification_id = $human_verification->uid;
$pw = md5($verification_id . $day . $link . config("metager.metager.proxy.password"));
$url = route('humanverification', ['mm' => $verification_id, 'pw' => $pw, "url" => \bin2hex($link)]);
$params = [
'mm' => $verification_id,
'pw' => $pw,
"url" => \bin2hex($link)
];
if (!empty($search_settings->javascript_picasso)) {
$params["bv_key"] = $search_settings->bv_key;
}
$url = route('humanverification', $params);
$proxyPw = md5($verification_id . $day . $result->proxyLink . config("metager.metager.proxy.password"));
$proxyUrl = route('humanverification', ['mm' => $verification_id, 'pw' => $proxyPw, "url" => \bin2hex($result->proxyLink)]);
$params["pw"] = $proxyPw;
$params["url"] = \bin2hex($result->proxyLink);
$proxyUrl = route('humanverification', $params);
$result->link = $url;
$result->proxyLink = $proxyUrl;
}
......
......@@ -2,6 +2,7 @@
namespace App\Models;
use App\SearchSettings;
use Cache;
use URL;
......@@ -12,27 +13,32 @@ class HumanVerification
private $users = [];
private $user = [];
public readonly ?string $id;
public readonly ?string $uid;
public readonly ?bool $alone;
public readonly ?int $whitelisted_accounts;
public readonly ?int $not_whitelisted_accounts;
public string $id;
public string $uid;
public bool $alone;
public int $whitelisted_accounts;
public int $not_whitelisted_accounts;
public bool $picasso_enabled;
public int $request_count_all_users = 0;
public function __construct()
public function __construct(string $picasso_hash = null)
{
$request = \request();
$ip = $request->ip();
$id = hash("sha1", $ip);
$agent = $_SERVER["AGENT"];
$uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid");
$this->id = $id;
$this->uid = $uid;
// Check if picasso challenge was solved
if ($picasso_hash === null) {
$this->id = hash("sha1", $ip);
$this->uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid");
$this->picasso_enabled = false;
} else {
$this->id = hash("sha1", $picasso_hash);
$this->uid = hash("sha1", $picasso_hash . $ip . "uid");
$this->picasso_enabled = true;
}
# Get all Users of this IP
$this->users = Cache::get(self::CACHE_PREFIX . "." . $id, []);
$this->users = Cache::get(self::CACHE_PREFIX . "." . $this->id, []);
if ($this->users === null) {
$this->users = [];
}
......@@ -45,12 +51,12 @@ class HumanVerification
'unusedResultPages' => 0,
'whitelist' => false,
'locked' => false,
"lockedKey" => "",
"picasso_enabled" => $this->picasso_enabled,
"expiration" => now()->addWeeks(2),
];
$this->users[$this->uid] = $this->user;
} else {
$this->user = $this->users[$uid];
$this->user = $this->users[$this->uid];
}
# Lock out everyone in a Bot network
......@@ -64,7 +70,7 @@ class HumanVerification
if (!$userTmp["whitelist"]) {
$not_whitelisted_accounts++;
$sum += $userTmp["unusedResultPages"];
if ($userTmp["uid"] !== $uid) {
if ($userTmp["uid"] !== $this->uid) {
$alone = false;
}
} else {
......@@ -229,6 +235,7 @@ class HumanVerification
if ($changed) {
Cache::put(self::CACHE_PREFIX . "." . $user["id"], $newUserlist, now()->addWeeks(2));
$this->users = $newUserlist;
}
$this->users = $newUserlist;
......
......@@ -5,8 +5,9 @@ namespace App;
class SearchSettings
{
public $jskey = null;
public $bv_key = null; // Cache Key where data of BV is temporarily stored
public $javascript_enabled = false;
public $javascript_picasso = null;
public $header_printed = false;
public function __construct()
......
require('es6-promise').polyfill();
require('fetch-ie8');
import picassoCanvas from '../picasso';
checkPicasso();
document.querySelectorAll("div.user input, div.user select").forEach(element => {
element.addEventListener("change", event => {
element.form.submit();
});
});
\ No newline at end of file
});
function checkPicasso() {
let pcso = document.getElementById("current-user").dataset.pcso;
if (!pcso) {
let pcso = picassoCanvas();
let new_url = new URL(document.location);
new_url.searchParams.append("pcso", pcso);
document.location = new_url;
console.log(new_url);
}
}
\ No newline at end of file
const params = {
area: {
width: 300,
height: 300,
},
offsetParameter: 2001000001,
fontSizeFactor: 1.5,
multiplier: 15000,
maxShadowBlur: 50,
};
// Number of shapes to draw. The higher the more costly it is.
// Can be used as a way to adjust the aggressiveness of the proof of work (POW)
const numShapes = 5;
const initialSeed = 53;
function x64Add(m, n) {
m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
var o = [0, 0, 0, 0];
o[3] += m[3] + n[3];
o[2] += o[3] >>> 16;
o[3] &= 0xffff;
o[2] += m[2] + n[2];
o[1] += o[2] >>> 16;
o[2] &= 0xffff;
o[1] += m[1] + n[1];
o[0] += o[1] >>> 16;
o[1] &= 0xffff;
o[0] += m[0] + n[0];
o[0] &= 0xffff;
return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
}
function x64Multiply(m, n) {
m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff];
n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff];
var o = [0, 0, 0, 0];
o[3] += m[3] * n[3];
o[2] += o[3] >>> 16;
o[3] &= 0xffff;
o[2] += m[2] * n[3];
o[1] += o[2] >>> 16;
o[2] &= 0xffff;
o[2] += m[3] * n[2];
o[1] += o[2] >>> 16;
o[2] &= 0xffff;
o[1] += m[1] * n[3];
o[0] += o[1] >>> 16;
o[1] &= 0xffff;
o[1] += m[2] * n[2];
o[0] += o[1] >>> 16;
o[1] &= 0xffff;
o[1] += m[3] * n[1];
o[0] += o[1] >>> 16;
o[1] &= 0xffff;
o[0] += (m[0] * n[3]) + (m[1] * n[2]) + (m[2] * n[1]) + (m[3] * n[0]);
o[0] &= 0xffff;
return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]];
}
function x64Rotl(m, n) {
n %= 64;
if (n === 32) {
return [m[1], m[0]]
} else if (n < 32) {
return [(m[0] << n) | (m[1] >>> (32 - n)), (m[1] << n) | (m[0] >>> (32 - n))]
} else {
n -= 32;
return [(m[1] << n) | (m[0] >>> (32 - n)), (m[0] << n) | (m[1] >>> (32 - n))]
}
}