Jump to content

adrian

PW-Moderators
  • Posts

    11,147
  • Joined

  • Last visited

  • Days Won

    368

adrian last won the day on February 18

adrian had the most liked content!

Profile Information

  • Gender
    Not Telling
  • Location
    Canada

Recent Profile Visitors

49,902 profile views

adrian's Achievements

Hero Member

Hero Member (6/6)

13.5k

Reputation

226

Community Answers

  1. Interestingly if I try to open the frontend in Chrome, Firefox, or Zen now, it's still blocked, but it opens in Safari.
  2. Thanks for the suggestion, but after deleting the module files and reinstalling them, I have access again for now. But still having issues with the ban duration. I set it to 2 minutes but I am still blocked out (this time just on the frontend). Note that I am logged in, but my admin is not at /processwire or /admin (in case that has any impact on the previous issue where I was actually locked out of the backend).
  3. Even after 60 mins I still can't get in and now even my backend admin is blocked. I ended up having to remove the module folder to get access again. What am I doing wrong?
  4. Big thanks for this @maximus A couple of feature suggestions if I may :) Could you change the "Return 404 silently (stealth mode)" option to really be a stealth 404 error because at the moment it still returns the styled black Wirewall page with all its branding - it's just a change to the wording. Any chance of an option to disable the AJAX protection completely? And a confusion - I am logged into my admin, but in the same browser window I have still managed to trigger the rate limit (intentionally), but your docs state "First, all logged-in ProcessWire users are automatically whitelisted." but I am blocked and actually don't seem to be able to remove the block even after deleting the files in /assets/cache/WireWall - what am I missing?
  5. @ryan I am wondering if this should be the approach in wire/config.php if (preg_match('#^Content-Security-Policy(?:-Report-Only)?:.*\s(?:script-src|script-src-elem)\s+(?:[^;]+\s)?\'nonce-([\w+/]+=*)\'#mi', implode("\n", headers_list()), $m)) { $config->cspNonce = $m[1]; } else { $config->cspNonce = base64_encode(random_bytes(16)); } This way if we have already set one in htaccess or another included script has set one via a PHP header, then PW would use that instead of defining a new one.
  6. Ah yes, of course - that makes sense and a very different use case to what I was thinking about. I suppose if an attacker knew your system they could add that to an injected script, but hopefully that's unlikely.
  7. I suppose it would, but wouldn't the process would still invalidate the protection, right? Or am I missing something?
  8. @elabx - I don't really know how those apache modules work exactly, but wouldn't they have the same issue as I realized when I thought - why not just hook into Page::render and do a string replace on all <script> tags to add in the nonce at runtime - the problem being of course that this would also add the nonce to any maliciously injected <script> tags, thereby effectively removing all protection?
  9. Thanks @elabx - that's a cool idea having apache generate the nonce so you can populate the CSP in htaccess. Can you explain how this works with ProCache though - wouldn't the <script> calls in the cached html files have the version of the nonce from when they were created which would differ from the current one when the page is loaded. I am guessing there is something to this that I am missing - note that I don't really use ProCache. I have been setting my CSP headers in init.php because I want to change them depending on whether I am in the admin or not. And, yes, GTM is an absolute PITA with all their country TLDs. At the moment I have that part of the policy in report-only mode and I am adding domains as they are reported. $isAdmin = isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], $this->wire('config')->urls->admin) === 0; // add to list as we observe more violations and determine which sources we need to allow $googleDomains = [ "https://www.google.ca", "https://www.google.co.in", "https://www.google.co.nz", "https://www.google.co.uk", "https://www.google.com", "https://www.google.com.au", "https://www.google.com.br", "https://www.google.com.my", "https://www.google.com.sg", "https://www.google.de", "https://www.google.fr", "https://www.google.lu", "https://www.google.md", "https://www.google.nl", "https://www.google.se", "https://*.googletagmanager.com", "https://*.google-analytics.com", "https://*.googleadservices.com", "https://*.googlesyndication.com", "https://*.doubleclick.net" ]; $imgSources = array_merge( [ "'self'", "data:", "https://cdn.jsdelivr.net", "https://i.vimeocdn.com", "https://www.facebook.com", "https://connect.facebook.net", "https://www.gravatar.com" ], $googleDomains ); $connectSources = array_merge( [ "'self'", "https://svc.webspellchecker.net", "https://www.facebook.com", "https://connect.facebook.net", "https://*.facebook.com", "https://*.facebook.net" ], $googleDomains ); $scriptSourcesElem = [ "'self'", "'unsafe-eval'", // Required for Vue 2 template compilation ]; // Admin needs unsafe-inline, frontend uses nonces if ($isAdmin) { $scriptSourcesElem[] = "'unsafe-inline'"; } else { $scriptSourcesElem[] = "'nonce-" . $config->cspNonce . "'"; } $scriptSourcesElem = array_merge($scriptSourcesElem, [ "https://cdn.jsdelivr.net", "https://cdn.paddle.com", "https://www.googletagmanager.com", "https://www.google-analytics.com", "https://googleads.g.doubleclick.net", "https://connect.facebook.net", "https://svc.webspellchecker.net" ]); // For inline event handlers (onclick, onload, etc.) - allow them $scriptSourcesAttr = [ "'unsafe-inline'" // Allow onclick/onload/etc handlers ]; $styleSources = [ "'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net", "https://fonts.googleapis.com", "https://svc.webspellchecker.net" ]; $fontSources = [ "'self'", "data:", "https://fonts.gstatic.com", "https://svc.webspellchecker.net" ]; // ENFORCED CSP (blocks violations) $cspEnforce = [ "object-src 'none'", "base-uri 'none'", "form-action 'self' https://www.facebook.com", "frame-ancestors 'self'", "report-uri https://mysite.com/csp-report/", "report-to csp-endpoint" ]; if($config->https) { $cspEnforce[] = "upgrade-insecure-requests"; } // REPORT-ONLY CSP (just reports, doesn't block) $cspReportOnly = [ "script-src-elem " . implode(' ', $scriptSourcesElem), // Controls <script> tags "script-src-attr " . implode(' ', $scriptSourcesAttr), // Controls onclick/onload/etc "style-src " . implode(' ', $styleSources), "font-src " . implode(' ', $fontSources), "img-src " . implode(' ', $imgSources), "connect-src " . implode(' ', $connectSources), "report-uri https://mysite.com/csp-report/", "report-to csp-endpoint" ]; header("Reporting-Endpoints: csp-endpoint=\"https://mysite.com/csp-report/\""); header("Content-Security-Policy: " . implode('; ', $cspEnforce)); header("Content-Security-Policy-Report-Only: " . implode('; ', $cspReportOnly)); And in case anyone is interested, I have this script for the csp-report template file: <?php namespace ProcessWire; $data = file_get_contents('php://input'); if($data) { $report = json_decode($data, true); // Browser extension indicators - these are SAFE to ignore $extensionIndicators = [ 'chrome-extension://', 'moz-extension://', 'safari-extension://', 'webkit-masked-url://', 'ms-browser-extension://' ]; // Known bad actor domains to filter $knownExtensionDomains = [ 'cdn.honey.io', 'honey.io', 'extensions.grammarly.com', 'grammarly.com', 'lastpass.com', 'dashlane.com', 'bitwarden.com', '1password.com' ]; $reports = []; if (isset($report[0]) && is_array($report[0])) { $reports = $report; } else { $reports = [$report]; } $shouldLog = false; foreach ($reports as $singleReport) { $blockedURL = $singleReport['body']['blockedURL'] ?? ''; $sourceFile = $singleReport['body']['sourceFile'] ?? ''; $isExtension = false; // Only ignore if it's DEFINITELY from a browser extension (protocol check) foreach ($extensionIndicators as $indicator) { if (stripos($sourceFile, $indicator) !== false) { $isExtension = true; break; } } // Or from a known safe extension domain if (!$isExtension) { foreach ($knownExtensionDomains as $domain) { if (stripos($blockedURL, $domain) !== false || stripos($sourceFile, $domain) !== false) { $isExtension = true; break; } } } if (!$isExtension) { $shouldLog = true; break; } } if ($shouldLog) { $log->save('csp-report', $data); } } http_response_code(204); exit; I am not sure if I am overly complicating things. It honestly seems pretty hard to get all this working when you're relying on different third party scripts. I see that even google.com has a very limited policy and it's in report-only mode, so maybe everyone has given up on this anyway 😁
  10. Hi @ryan - I mentioned this here: https://github.com/processwire/processwire-issues/issues/2184#issuecomment-3901936805 but I want to make sure it isn't lost. I really think PW needs a $config->cspNonce that gets injected into all core scripts and can be used by all module developers as well. I am doing this in my config.php $config->cspNonce = base64_encode(random_bytes(16)); but if it was in /wire/config.php we could all use it - in modules and template files. Another possibility is maybe have a core method like: function getNonce(): ?string { return preg_match('#^Content-Security-Policy(?:-Report-Only)?:.*\s(?:script-src|script-src-elem)\s+(?:[^;]+\s)?\'nonce-([\w+/]+=*)\'#mi', implode("\n", headers_list()), $m) ? $m[1] : null; } that finds an existing nonce set for either script-src or script-src-elem and use the result of that to add to core scripts (src file and inline). But I don't think PW should set the CSP itself - I think that should be up to us. Please let me know if you have any thoughts or questions - I've been trying to up my game in the CSP area and this would make life a lot easier because at the moment I can't lock down the PW admin without it so I need separate CSP's for frontend and admin.
  11. Hi @maximus - this is intriguing - I use Twilio a lot, but would love a free alternative. You mention that Telegram is "already installed on most devices" but I can't find any evidence online that this is the case. Can you please cite your source for this. Thanks.
  12. Thanks for this @maximus - timezone DST changes are definitely an annoyance in development. I am curious though why you have a limited set of timezones available, rather than the complete set? I feel like it might be particularly problematic if you rely on a service like https://ipapi.co/ to determine the user's timezone from their IP address. Many of the timezones returned from that are not likely to match your list. I can see that the way you have organized them makes it much simpler for user selection, and maybe that is your primary goal but I still wonder about how it might impact its functionality.
  13. What do you mean by "not as reliably"? Does it fail sometimes? I use this in so many places and I don't think I've ever had an issue so perhaps there is something else at play here.
  14. <?php namespace ProcessWire; if($config->ajax) { bd('AJAX request detected'); echo "Replaced page content"; exit(); } ?> <script> function loadlist(segment='') { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("main").innerHTML = this.responseText; } }; xhttp.open("GET", "<?=$page->url?>"+segment, true); xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhttp.send(); } </script> <div id="content"> <div id="main">Basic page content</div> <p><button onclick="loadlist('')">Load List</button></p> </div> Note the replacement of the text in "main" and that Tracy detects the AJAX request.
×
×
  • Create New...