Skip to content
Snippets Groups Projects
Commit d5a44248 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

capture csp errors

parent bafe8b2c
No related branches found
No related tags found
1 merge request!1994Resolve "CSP Verification"
...@@ -16,6 +16,7 @@ server { ...@@ -16,6 +16,7 @@ server {
location / { location / {
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
add_header "Content-Security-Policy" $csp;
} }
location ~ \.php$ { location ~ \.php$ {
...@@ -27,6 +28,7 @@ server { ...@@ -27,6 +28,7 @@ server {
fastcgi_read_timeout 900; fastcgi_read_timeout 900;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; include fastcgi_params;
add_header "Content-Security-Policy" $csp;
} }
#error_page 404 /404.html; #error_page 404 /404.html;
......
...@@ -14,6 +14,7 @@ server { ...@@ -14,6 +14,7 @@ server {
location / { location / {
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
add_header "Content-Security-Policy" $csp;
} }
location ~ \.php$ { location ~ \.php$ {
...@@ -24,6 +25,7 @@ server { ...@@ -24,6 +25,7 @@ server {
fastcgi_read_timeout 900; fastcgi_read_timeout 900;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; include fastcgi_params;
add_header "Content-Security-Policy" $csp;
} }
#error_page 404 /404.html; #error_page 404 /404.html;
......
...@@ -24,7 +24,10 @@ http { ...@@ -24,7 +24,10 @@ http {
'"$http_user_agent" "$http_x_forwarded_for"'; '"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/null main; access_log /dev/null main;
add_header "Content-Security-Policy" "default-src 'self'; script-src 'self'; script-src-elem 'self'; script-src-attr 'self'; style-src 'self'; style-src-elem 'self'; style-src-attr 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; frame-ancestors 'self' https://scripts.zdv.uni-mainz.de; form-action 'self' www.paypal.com"; map $upstream_http_content_security_policy $csp {
'' "default-src 'self'; script-src 'self'; script-src-elem 'self'; script-src-attr 'self'; style-src 'self'; style-src-elem 'self'; style-src-attr 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; frame-ancestors 'self' https://scripts.zdv.uni-mainz.de; form-action 'self' www.paypal.com";
}
add_header "X-Frame-Options" "sameorigin"; add_header "X-Frame-Options" "sameorigin";
add_header "X-Content-Type-Options" "nosniff"; add_header "X-Content-Type-Options" "nosniff";
add_header "ReferrerPolicy" "origin"; add_header "ReferrerPolicy" "origin";
......
...@@ -22,6 +22,7 @@ class HumanVerification extends Controller ...@@ -22,6 +22,7 @@ class HumanVerification extends Controller
const EXPIRELONG = 60 * 60 * 24 * 14; const EXPIRELONG = 60 * 60 * 24 * 14;
const EXPIRESHORT = 60 * 60 * 72; const EXPIRESHORT = 60 * 60 * 72;
const TOKEN_PREFIX = "humanverificationtoken."; const TOKEN_PREFIX = "humanverificationtoken.";
const BV_DATA_EXPIRATION_MINUTES = 5;
public static function captchaShow(Request $request) public static function captchaShow(Request $request)
{ {
...@@ -269,14 +270,19 @@ class HumanVerification extends Controller ...@@ -269,14 +270,19 @@ class HumanVerification extends Controller
// Verify that key is a md5 checksum // Verify that key is a md5 checksum
if (preg_match("/^[a-f0-9]{32}$/", $key)) { if (preg_match("/^[a-f0-9]{32}$/", $key)) {
$bvData = Cache::get($key); Cache::lock($key . "_lock", 10)->block(5, function () use ($key) {
if ($bvData === null) { $bvData = Cache::get($key);
$bvData = []; if ($bvData === null) {
} $bvData = [];
$bvData["css_loaded"] = now(); }
Cache::put($key, $bvData, now()->addSeconds(30)); if (\array_key_exists("css", $bvData)) {
$bvData["css"] = array();
}
$bvData["css"]["loaded"] = now();
Cache::put($key, $bvData, now()->addMinutes(self::BV_DATA_EXPIRATION_MINUTES));
});
} }
return response(view('layouts.resultpage.verificationCss', ["url" => route("bv_verificationimage", ["id" => $key])]), 200)->header("Content-Type", "text/css"); return response(view('layouts.resultpage.verificationCss'), 200)->header("Content-Type", "text/css");
} }
public function verificationJsFile(Request $request) public function verificationJsFile(Request $request)
...@@ -288,38 +294,95 @@ class HumanVerification extends Controller ...@@ -288,38 +294,95 @@ class HumanVerification extends Controller
abort(404); abort(404);
} }
$bvData = Cache::get($key); // Acquire lock
if ($bvData === null) { Cache::lock($key . "_lock", 10)->block(5, function () use ($key, $request) {
$bvData = []; $bvData = Cache::get($key);
} if ($bvData === null) {
$bvData["js_loaded"] = now(); $bvData = [];
if ($request->has("sp")) { }
$bvData["csp"] = false; if (!\array_key_exists("js", $bvData)) {
} else { $bvData["js"] = array();
$bvData["csp"] = true; }
} $bvData["js"]["loaded"] = now();
if (!\array_key_exists("csp", $bvData)) {
$bvData["csp"] = array();
}
if ($request->has("sp")) {
$bvData["csp"]["honor"] = false;
} else {
$bvData["csp"]["honor"] = true;
}
if ($request->has("wd")) {
$bvData["webdriver"] = true;
} else {
$bvData["webdriver"] = false;
}
Cache::put($key, $bvData, now()->addMinutes(self::BV_DATA_EXPIRATION_MINUTES));
});
Cache::put($key, $bvData, now()->addSeconds(30));
return response()->file(\public_path("img/1px.png", ["Content-Type" => "image/png"])); return response()->file(\public_path("img/1px.png", ["Content-Type" => "image/png"]));
} }
public function verificationImage(Request $request) public function verificationCSP(Request $request, string $mgv)
{ {
$key = $request->input("id", "");
// Verify that key is a md5 checksum // Verify that key is a md5 checksum
if (!preg_match("/^[a-f0-9]{32}$/", $key)) { if (!preg_match("/^[a-f0-9]{32}$/", $mgv)) {
abort(404); abort(404);
} }
$bvData = Cache::get($key); Cache::lock($mgv . "_lock", 10)->block(5, function () use ($mgv, $request) {
if ($bvData === null) { $bvData = Cache::get($mgv);
$bvData = []; if ($bvData === null) {
} $bvData = [];
$bvData["css_image_loaded"] = now(); }
Cache::put($key, $bvData, now()->addSeconds(30));
return response()->file(\public_path("img/1px.png", ["Content-Type" => "image/png"])); $report = $request->getContent();
$report = \json_decode($report);
if (empty($report) || !\property_exists($report, "csp-report")) {
return;
} else {
$report = $report->{"csp-report"};
}
if (!\array_key_exists("csp", $bvData)) {
$bvData["csp"] = array();
}
// Check if this is our wanted CSP error
$js_url = url("/js/index.js");
if (\property_exists($report, "source-file") && stripos($report->{"source-file"}, $js_url) === 0) {
$bvData["csp"]["reporting_enabled"] = true;
} else {
$bvData["csp"]["loaded"] = now();
}
// Update CSP Error Count
if (!\array_key_exists("error_count", $bvData["csp"])) {
$bvData["csp"]["error_count"] = 1;
} else {
$bvData["csp"]["error_count"]++;
}
// Update Array of Column- and Line-Numbers of the errors
if (\property_exists($report, "line-number")) {
if (!\array_key_exists("line-numbers", $bvData["csp"])) {
$bvData["csp"]["line-numbers"] = array();
}
$bvData["csp"]["line-numbers"][] = $report->{"line-number"};
}
if (\property_exists($report, "column-number")) {
if (!\array_key_exists("column-numbers", $bvData["csp"])) {
$bvData["csp"]["column-numbers"] = array();
}
$bvData["csp"]["column-numbers"][] = $report->{"column-number"};
}
Cache::put($mgv, $bvData, now()->addMinutes(self::BV_DATA_EXPIRATION_MINUTES));
});
} }
} }
...@@ -83,64 +83,76 @@ class BrowserVerification ...@@ -83,64 +83,76 @@ class BrowserVerification
"start" => now() "start" => now()
], now()->addMinutes(30)); ], now()->addMinutes(30));
echo (view('layouts.resultpage.verificationHeader')->with('key', $key)->render()); $report_to = route("csp_verification", ["mgv" => $key]);
flush(); return response()->stream(function () use ($next, $request, $route, $key) {
echo (view('layouts.resultpage.verificationHeader')->with('key', $key)->render());
if ($this->supportsInlineVerification() && $this->waitForBV($key)) {
echo (view('layouts.resultpage.resources')->render());
flush(); flush();
\app()->make(QueryTimer::class)->observeEnd(self::class);
\app()->make(SearchSettings::class)->header_printed = true;
return $next($request);
}
$params = $request->all(); if ($this->supportsInlineVerification() && $this->waitForBV($key)) {
$params["mgv"] = $key; echo (view('layouts.resultpage.resources')->render());
$url = route($route, $params); flush();
\app()->make(QueryTimer::class)->observeEnd(self::class);
\app()->make(SearchSettings::class)->header_printed = true;
return $next($request);
}
$params = $request->all();
$params["mgv"] = $key;
$url = route($route, $params);
echo (view('layouts.resultpage.unverifiedResultPage') echo (view('layouts.resultpage.unverifiedResultPage')
->with('url', $url) ->with('url', $url)
->render()); ->render());
flush(); flush();
\app()->make(QueryTimer::class)->observeEnd(self::class); \app()->make(QueryTimer::class)->observeEnd(self::class);
}, 200, ["Content-Security-Policy" => "default-src 'self'; script-src 'self'; script-src-elem 'self'; script-src-attr 'self'; style-src 'self'; style-src-elem 'self'; style-src-attr 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; frame-ancestors 'self' https://scripts.zdv.uni-mainz.de; form-action 'self' www.paypal.com; report-uri " . $report_to . "; report_to " . $report_to]);
} }
private function waitForBV($key, $wait_time_inline_verificytion_ms = 2000) private function waitForBV($key, $wait_time_inline_verificytion_ms = 2000)
{ {
$bvData = null; $bvData = null;
$wait_time_js_ms = null; $wait_time_ms = 250;
$wait_start = now(); $wait_start = now();
$js_loaded = false;
$csp_loaded = null;
do { do {
usleep(10 * 1000);
$bvData = Cache::get($key); $bvData = Cache::get($key);
// This condition is true when at least the css file was loaded if ($bvData === null) {
if ($bvData !== null && sizeof($bvData) > 1) { continue;
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 = $wait_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($wait_start) <= $wait_time_js_ms) {
usleep(10 * 1000);
continue;
}
}
if (!$js_loaded && \array_key_exists("js", $bvData) && \array_key_exists("loaded", $bvData["js"])) {
$js_loaded = true;
$search_settings = \app()->make(SearchSettings::class); $search_settings = \app()->make(SearchSettings::class);
if (\array_key_exists("js_loaded", $bvData)) { $search_settings->bv_key = $key;
$search_settings->bv_key = $key; $search_settings->javascript_enabled = true;
$search_settings->javascript_enabled = true; }
if (\array_key_exists("csp", $bvData) && \array_key_exists("loaded", $bvData["csp"])) {
if (now()->diffInMilliseconds($bvData["csp"]["loaded"]) > $wait_time_ms) {
$csp_loaded = true;
} else {
$csp_loaded = false;
} }
if (\array_key_exists("csp", $bvData) && $bvData["csp"] === false) { }
if (
\array_key_exists("css", $bvData) && \array_key_exists("loaded", $bvData["css"]) &&
now()->diffInMilliseconds($bvData["css"]["loaded"]) > $wait_time_ms && ($csp_loaded === null ||
$csp_loaded === true
)
) {
// Css is loaded and all other checks should've been, too
if (\array_key_exists("csp", $bvData) && array_key_exists("honor", $bvData["csp"]) && $bvData["csp"]["honor"] === false) {
$this->logCSP(); $this->logCSP();
} }
return true; return true;
} }
usleep(10 * 1000);
} while ($bvData === null || now()->diffInMilliseconds($wait_start) < $wait_time_inline_verificytion_ms); } while ($bvData === null || now()->diffInMilliseconds($wait_start) < $wait_time_inline_verificytion_ms);
return false; return false;
} }
......
...@@ -3,19 +3,26 @@ require('fetch-ie8'); ...@@ -3,19 +3,26 @@ require('fetch-ie8');
// Find the key id for the browser-verification // Find the key id for the browser-verification
document.querySelectorAll("link").forEach(element => { document.querySelectorAll("link").forEach(element => {
// Should get blocked by csp
eval("window.sp = 1;");
let href = element.href; let href = element.href;
let matches = href.match(/http[s]{0,1}:\/\/[^\/]+\/index\.css\?id=(.+)/i); let matches = href.match(/http[s]{0,1}:\/\/[^\/]+\/index\.css\?id=(.+)/i);
if (!matches) { if (!matches) {
return true; return true;
} }
try {
// Should get blocked by csp
eval("window.sp = 1;");
} catch (err) { }
let key = matches[1]; let key = matches[1];
let url = "/img/logo.png?id=" + key; let url = "/img/logo.png?id=" + key;
if (window.sp == 1) { if (window.sp == 1) {
url += "&sp" url += "&sp"
} }
if (navigator.webdriver) {
url += "&wd"
}
return fetch(url); return fetch(url);
}); });
...@@ -5,7 +5,6 @@ html { ...@@ -5,7 +5,6 @@ html {
body { body {
margin: 0; margin: 0;
height: 100%; height: 100%;
background-image: url('{{ $url }}');
} }
iframe#mg-framed { iframe#mg-framed {
......
...@@ -9,4 +9,4 @@ Route::post('verify/metager', [HumanVerification::class, 'captchaSolve'])->name( ...@@ -9,4 +9,4 @@ Route::post('verify/metager', [HumanVerification::class, 'captchaSolve'])->name(
Route::get('r/metager/{hv}/{pw}/{url}', ['as' => 'humanverification', 'uses' => 'HumanVerification@removeGet']); Route::get('r/metager/{hv}/{pw}/{url}', ['as' => 'humanverification', 'uses' => 'HumanVerification@removeGet']);
Route::post('img/dog.jpg', [HumanVerification::class, 'whitelist']); Route::post('img/dog.jpg', [HumanVerification::class, 'whitelist']);
Route::get('index.css', [HumanVerification::class, 'verificationCssFile']); Route::get('index.css', [HumanVerification::class, 'verificationCssFile']);
Route::get('metager.png', [HumanVerification::class, 'verificationImage'])->name("bv_verificationimage"); Route::post('{mgv}/csp-report', [HumanVerification::class, 'verificationCSP'])->name("csp_verification");
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment