فهرست منبع

Put CSP everywhere (#7810)

* Puts CSP everywhere in `p/api`
   * including the HTML query page ❗
   * Also in `p/ext.php`
* Puts `X-Content-Type-Options: nosniff` everywhere
* Fixes custom icon configuration not showing `blob:` icon in statsController (idle feeds)
   * Also removes `style-src 'unsafe-inline'` since it doesn't seem to be needed
* Improves CSP of `p/f.php`

* Add `sandbox` directive
Inverle 7 ماه پیش
والد
کامیت
7df6c201f2
9فایلهای تغییر یافته به همراه22 افزوده شده و 4 حذف شده
  1. 1 2
      app/Controllers/statsController.php
  2. 1 1
      app/FreshRSS.php
  3. 2 0
      p/api/fever.php
  4. 3 0
      p/api/greader.php
  5. 2 0
      p/api/index.php
  6. 1 0
      p/api/pshb.php
  7. 7 0
      p/api/query.php
  8. 2 0
      p/ext.php
  9. 3 1
      p/f.php

+ 1 - 2
app/Controllers/statsController.php

@@ -30,8 +30,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
 		$this->_csp([
 			'default-src' => "'self'",
 			'frame-ancestors' => "'none'",
-			'img-src' => '* data:',
-			'style-src' => "'self' 'unsafe-inline'",
+			'img-src' => '* data: blob:',
 		]);
 
 		$catDAO = FreshRSS_Factory::createCategoryDao();

+ 1 - 1
app/FreshRSS.php

@@ -149,7 +149,7 @@ class FreshRSS extends Minz_FrontController {
 	}
 
 	public static function preLayout(): void {
-		header("X-Content-Type-Options: nosniff");
+		header('X-Content-Type-Options: nosniff');
 
 		FreshRSS_Share::load(join_path(APP_PATH, 'shares.php'));
 		self::loadStylesAndScripts();

+ 2 - 0
p/api/fever.php

@@ -1,5 +1,7 @@
 <?php
 declare(strict_types=1);
+header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
+header('X-Content-Type-Options: nosniff');
 
 /**
  * Fever API for FreshRSS

+ 3 - 0
p/api/greader.php

@@ -28,6 +28,9 @@ Server-side API compatible with Google Reader API layer 2
 require(__DIR__ . '/../../constants.php');
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
+header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
+header('X-Content-Type-Options: nosniff');
+
 if (PHP_INT_SIZE < 8) {	//32-bit
 	/** @return numeric-string */
 	function hex2dec(string $hex): string {

+ 2 - 0
p/api/index.php

@@ -1,5 +1,7 @@
 <?php
 	declare(strict_types=1);
+	header("Content-Security-Policy: default-src 'self'; frame-ancestors 'none'");
+	header('X-Content-Type-Options: nosniff');
 ?>
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">

+ 1 - 0
p/api/pshb.php

@@ -6,6 +6,7 @@ require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 const MAX_PAYLOAD = 3_145_728;
 
 header('Content-Type: text/plain; charset=UTF-8');
+header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
 header('X-Content-Type-Options: nosniff');
 
 $ORIGINAL_INPUT = file_get_contents('php://input', false, null, 0, MAX_PAYLOAD) ?: '';

+ 7 - 0
p/api/query.php

@@ -1,5 +1,8 @@
 <?php
 declare(strict_types=1);
+
+header('X-Content-Type-Options: nosniff');
+
 require(__DIR__ . '/../../constants.php');
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
@@ -175,10 +178,12 @@ if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
 
 if (in_array($format, ['rss', 'atom'], true)) {
 	header('Content-Type: application/rss+xml; charset=utf-8');
+	header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
 	$view->_layout(null);
 	$view->_path('index/rss.phtml');
 } elseif (in_array($format, ['greader', 'json'], true)) {
 	header('Content-Type: application/json; charset=utf-8');
+	header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
 	$view->_layout(null);
 	$view->type = 'query/' . $token;
 	$view->list_title = $query->getName();
@@ -190,9 +195,11 @@ if (in_array($format, ['rss', 'atom'], true)) {
 		die();
 	}
 	header('Content-Type: application/xml; charset=utf-8');
+	header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
 	$view->_layout(null);
 	$view->_path('index/opml.phtml');
 } else {
+	header("Content-Security-Policy: default-src 'self'; frame-src *; img-src * data:; frame-ancestors 'none'; media-src *");
 	$view->_layout('layout');
 	$view->_path('index/html.phtml');
 }

+ 2 - 0
p/ext.php

@@ -94,6 +94,8 @@ if (!is_valid_path($absolute_filename)) {
 $content_type = FreshRSS_extension_Controller::MIME_TYPES[$file_type];
 header("Content-Type: {$content_type}");
 header("Content-Disposition: inline; filename='{$file_name}'");
+header("Content-Security-Policy: default-src 'self'; frame-ancestors 'none'");
+header('X-Content-Type-Options: nosniff');
 header('Referrer-Policy: same-origin');
 
 $mtime = @filemtime($absolute_filename);

+ 3 - 1
p/f.php

@@ -5,6 +5,9 @@ require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 require(LIB_PATH . '/favicons.php');
 require(LIB_PATH . '/http-conditional.php');
 
+header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox");
+header('X-Content-Type-Options: nosniff');
+
 function show_default_favicon(int $cacheSeconds = 3600): void {
 	$default_mtime = @filemtime(DEFAULT_FAVICON) ?: 0;
 	if (!httpConditional($default_mtime, $cacheSeconds, 2)) {
@@ -56,7 +59,6 @@ if (($ico_mtime == false || $ico_mtime < $txt_mtime || ($ico_mtime < time() - (m
 	}
 }
 
-header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; img-src 'self'; style-src 'self';");
 if (!httpConditional($ico_mtime, mt_rand(14, 21) * 86400, 2)) {
 	$ico_content_type = contentType($ico);
 	header('Content-Type: ' . $ico_content_type);