Merge branch 'release/1.6.29'

This commit is contained in:
Andy Miller
2020-12-03 14:22:52 -07:00
27 changed files with 352 additions and 343 deletions

View File

@@ -27,6 +27,9 @@ RewriteEngine On
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to use twig tags in URL.
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.

View File

@@ -1,3 +1,26 @@
# v1.6.29
## 12/02/2020
1. [](#new)
* Added basic support for `user/config/versions.yaml`
1. [](#improved)
* Updated bundled JQuery to latest version `3.5.1`
* Forward a `sid` to GPM when downloading a premium package via CLI
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
* Set `grav_cli` as referrer when using `Response` from CLI
* Add option for timeout in `self-upgrade` command [#3013](https://github.com/getgrav/grav/pull/3013)
* Allow to set SameSite from system.yaml [#3063](https://github.com/getgrav/grav/pull/3063)
* Update media.yaml with some MS Office mimetypes [#3070](https://github.com/getgrav/grav/pull/3070)
1. [](#bugfix)
* Fixed hardcoded system folder in blueprints, config and language streams
* Added `.htaccess` rule to block attempts to use Twig in the request URL
* Fix compatibility with Symfony 4.2 and up. [#3048](https://github.com/getgrav/grav/pull/3048)
* Fix failing example custom shceduled job. [#3050](https://github.com/getgrav/grav/pull/3050)
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
* Fix uploads_dangerous_extensions checking [#3060](https://github.com/getgrav/grav/pull/3060)
* Remove redundant prefixing of `.` to extension [#3060](https://github.com/getgrav/grav/pull/3060)
* Check exact extension in checkFilename utility [#3061](https://github.com/getgrav/grav/pull/3061)
# v1.6.28
## 10/07/2020

15
SECURITY.md Normal file
View File

@@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
We are focusing our security updates on the following versions
| Version | Supported |
| ------- | ------------------ |
| 1.7.x | :white_check_mark: |
| 1.6.x | :white_check_mark: |
| < 1.6 | :x: |
## Reporting a Vulnerability
Please contact contact@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.

View File

@@ -50,7 +50,7 @@
"composer/ca-bundle": "^1.0",
"dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "2.x-dev"
"willdurand/negotiation": "^3.0"
},
"require-dev": {
"codeception/codeception": "^2.4",

460
composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -39,12 +39,13 @@ form:
.command:
type: text
label: PLUGIN_ADMIN.COMMAND
placeholder: 'cd ~;ls -lah;'
placeholder: 'ls'
validate:
required: true
.args:
type: text
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
placeholder: '-lah'
.at:
type: cron
label: PLUGIN_ADMIN.SCHEDULER_RUNAT

View File

@@ -1223,6 +1223,12 @@ form:
label: PLUGIN_ADMIN.SESSION_PATH
help: PLUGIN_ADMIN.SESSION_PATH_HELP
session.samesite:
type: text
size: small
label: PLUGIN_ADMIN.SESSION_SAMESITE
help: PLUGIN_ADMIN.SESSION_SAMESITE_HELP
session.split:
type: toggle
label: PLUGIN_ADMIN.SESSION_SPLIT

View File

@@ -103,7 +103,7 @@ types:
docx:
type: file
thumb: media/thumb-docx.png
mime: application/msword
mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document
xls:
type: file
thumb: media/thumb-xls.png
@@ -111,7 +111,7 @@ types:
xlsx:
type: file
thumb: media/thumb-xlsx.png
mime: application/vnd.ms-excel
mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ppt:
type: file
thumb: media/thumb-ppt.png
@@ -119,7 +119,7 @@ types:
pptx:
type: file
thumb: media/thumb-pptx.png
mime: application/vnd.ms-powerpoint
mime: application/vnd.openxmlformats-officedocument.presentationml.presentation
pps:
type: file
thumb: media/thumb-pps.png

View File

@@ -161,6 +161,7 @@ session:
uniqueness: path # Should sessions be `path` based or `security.salt` based
secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS
httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed.
samesite: # Set session SameSite. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
split: true # Sessions should be independent between site and plugins (such as admin)
path:

View File

@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.6.28');
define('GRAV_VERSION', '1.6.29');
define('GRAV_TESTING', false);
define('DS', '/');

View File

@@ -70,7 +70,7 @@ class Backups
public function getBackupDownloadUrl($backup, $base_url)
{
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
$download = urlencode(base64_encode($backup));
$download = urlencode(base64_encode(basename($backup)));
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');

View File

@@ -59,13 +59,13 @@ class Setup extends Data
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
'' => ['environment://blueprints', 'user://blueprints', 'system://blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://config', 'user://config', 'system/config'],
'' => ['environment://config', 'user://config', 'system://config'],
]
],
'plugins' => [
@@ -89,7 +89,7 @@ class Setup extends Data
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://languages', 'user://languages', 'system/languages'],
'' => ['environment://languages', 'user://languages', 'system://languages'],
]
],
'cache' => [

View File

@@ -233,6 +233,11 @@ class GPM extends Iterator
public function getUpdatablePlugins()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['plugins'];
// local cache to speed things up
@@ -312,6 +317,11 @@ class GPM extends Iterator
public function getUpdatableThemes()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['themes'];
// local cache to speed things up
@@ -359,6 +369,10 @@ class GPM extends Iterator
*/
public function getReleaseType($package_name)
{
if (!$this->repository) {
return null;
}
$repository = $this->repository['plugins'];
if (isset($repository[$package_name])) {
return $repository[$package_name]->release_type;
@@ -407,7 +421,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugin($slug)
{
return @$this->repository['plugins'][$slug];
return $this->repository['plugins'][$slug] ?? null;
}
/**
@@ -416,7 +430,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugins()
{
return $this->repository['plugins'];
return $this->repository['plugins'] ?? null;
}
/**
@@ -426,7 +440,7 @@ class GPM extends Iterator
*/
public function getRepositoryTheme($slug)
{
return @$this->repository['themes'][$slug];
return $this->repository['themes'][$slug] ?? null;
}
/**
@@ -435,7 +449,7 @@ class GPM extends Iterator
*/
public function getRepositoryThemes()
{
return $this->repository['themes'];
return $this->repository['themes'] ?? null;
}
/**

View File

@@ -284,7 +284,8 @@ class Response
$options['fopen']['notification'] = ['self', 'progress'];
}
$options['fopen']['header'] = 'Referer: ' . Grav::instance()['uri']->rootUrl(true);
$referer = \defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
$options['fopen']['header'] = 'Referer: ' . $referer;
if (isset($options['fopen']['ssl'])) {
$ssl = $options['fopen']['ssl'];
unset($options['fopen']['ssl']);
@@ -367,7 +368,8 @@ class Response
*/
private static function curlExecFollow($ch, $options, $callback)
{
curl_setopt_array($ch, [ CURLOPT_REFERER => Grav::instance()['uri']->rootUrl(true) ]);
$referer = \defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
curl_setopt_array($ch, [ CURLOPT_REFERER => $referer ]);
if ($callback) {
curl_setopt_array(

View File

@@ -9,6 +9,8 @@
namespace Grav\Common\Processors;
use Grav\Framework\File\Formatter\YamlFormatter;
use Grav\Framework\File\YamlFile;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@@ -21,8 +23,27 @@ class ConfigurationProcessor extends ProcessorBase
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$this->startTimer();
$this->container['config']->init();
$config = $this->container['config'];
$config->init();
$this->container['plugins']->setup();
if ($config->get('versions') === null) {
$filename = GRAV_ROOT . '/user/config/versions.yaml';
if (!is_file($filename)) {
$versions = [
'core' => [
'grav' => [
'version' => GRAV_VERSION,
'history' => ['version' => GRAV_VERSION, 'date' => gmdate('Y-m-d H:i:s')]
]
]
];
$config->set('versions', $versions);
$file = new YamlFile($filename, new YamlFormatter(['inline' => 4]));
$file->save($versions);
}
}
$this->stopTimer();
return $handler->handle($request);

View File

@@ -298,8 +298,8 @@ class Job
if (is_callable($this->command)) {
$this->output = $this->exec();
} else {
$args = \is_string($this->args) ? $this->args : implode(' ', $this->args);
$command = $this->command . ' ' . $args;
$args = \is_string($this->args) ? explode(' ', $this->args) : $this->args;
$command = array_merge([$this->command], $args);
$process = new Process($command);
$this->process = $process;

View File

@@ -246,7 +246,7 @@ class Scheduler
*/
public function isCrontabSetup()
{
$process = new Process('crontab -l');
$process = new Process(['crontab', '-l']);
$process->run();
if ($process->isSuccessful()) {

View File

@@ -133,7 +133,7 @@ class Security
// Set the patterns we'll test against
$patterns = [
// Match any attribute starting with "on" or xmlns
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])([\s\/]on|\sxmlns)[a-z].*=>?#iUu',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
'invalid_protocols' => '#(' . implode('|', $invalid_protocols) . '):.*?#iUu',

View File

@@ -36,6 +36,7 @@ class SessionServiceProvider implements ServiceProviderInterface
$cookie_httponly = (bool)$config->get('system.session.httponly', true);
$cookie_lifetime = (int)$config->get('system.session.timeout', 1800);
$cookie_path = $config->get('system.session.path');
$cookie_samesite = $config->get('system.session.samesite');
if (null === $cookie_path) {
$cookie_path = '/' . trim(Uri::filterPath($uri->rootUrl(false)), '/');
}
@@ -87,8 +88,14 @@ class SessionServiceProvider implements ServiceProviderInterface
'cookie_path' => $cookie_path,
'cookie_domain' => $cookie_domain,
'cookie_secure' => $cookie_secure,
'cookie_httponly' => $cookie_httponly
] + (array) $config->get('system.session.options');
'cookie_httponly' => $cookie_httponly,
];
if ($cookie_samesite) {
$options['cookie_samesite'] = $cookie_samesite;
}
$options += (array) $config->get('system.session.options');
$session = new Session($options);
$session->setAutoStart($enabled);

View File

@@ -859,11 +859,7 @@ abstract class Utils
public static function checkFilename($filename)
{
$dangerous_extensions = Grav::instance()['config']->get('security.uploads_dangerous_extensions', []);
array_walk($dangerous_extensions, function(&$val) {
$val = '.' . $val;
});
$extension = '.' . pathinfo($filename, PATHINFO_EXTENSION);
$extension = pathinfo($filename, PATHINFO_EXTENSION);
return !(
// Empty filenames are not allowed.
@@ -872,8 +868,8 @@ abstract class Utils
|| strtr($filename, "\t\v\n\r\0\\/", '_______') !== $filename
// Filename should not start or end with dot or space.
|| trim($filename, '. ') !== $filename
// Filename should not contain .php in it.
|| static::contains($extension, $dangerous_extensions)
// File extension should not be part of configured dangerous extensions
|| in_array($extension, $dangerous_extensions)
);
}

View File

@@ -445,7 +445,7 @@ class InstallCommand extends ConsoleCommand
} else {
$repo_dir = $matches[2];
}
$paths = (array) $paths;
foreach ($paths as $repo) {
$path = rtrim($repo, '/') . '/' . $repo_dir;
@@ -570,7 +570,8 @@ class InstallCommand extends ConsoleCommand
[
'slug' => $package->slug,
'filename' => $package->premium['filename'],
'license_key' => $license
'license_key' => $license,
'sid' => md5(GRAV_ROOT)
]
));

View File

@@ -46,6 +46,9 @@ class SelfupgradeCommand extends ConsoleCommand
protected $overwrite;
/** @var int */
protected $timeout;
protected function configure()
{
$this
@@ -69,6 +72,13 @@ class SelfupgradeCommand extends ConsoleCommand
InputOption::VALUE_NONE,
'Option to overwrite packages if they already exist'
)
->addOption(
'timeout',
't',
InputOption::VALUE_OPTIONAL,
'Option to set the timeout in seconds when downloading the update (0 for no timeout)',
30
)
->setDescription('Detects and performs an update of Grav itself when available')
->setHelp('The <info>update</info> command updates Grav itself when a new version is available');
}
@@ -78,6 +88,7 @@ class SelfupgradeCommand extends ConsoleCommand
$this->upgrader = new Upgrader($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->overwrite = $this->input->getOption('overwrite');
$this->timeout = (int) $this->input->getOption('timeout');
$this->displayGPMRelease();
@@ -116,7 +127,6 @@ class SelfupgradeCommand extends ConsoleCommand
$questionHelper = $this->getHelper('question');
$this->output->writeln("Grav v<cyan>{$remote}</cyan> is now available [release date: {$release}].");
$this->output->writeln('You are currently using v<cyan>' . GRAV_VERSION . '</cyan>.');
@@ -185,7 +195,16 @@ class SelfupgradeCommand extends ConsoleCommand
{
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid('', false);
$output = Response::get($package['download'], [], [$this, 'progress']);
$options = [
'curl' => [
CURLOPT_TIMEOUT => $this->timeout,
],
'fopen' => [
'timeout' => $this->timeout,
],
];
$output = Response::get($package['download'], $options, [$this, 'progress']);
Folder::create($this->tmp);

View File

@@ -63,9 +63,10 @@ trait ObjectCollectionTrait
/**
* @param string $property Object property to be fetched.
* @param mixed $default Default value if not set.
* @param bool $doCreate Not being used.
* @return mixed[] Key/Value pairs of the properties.
*/
public function doGetProperty($property, $default = null)
public function &doGetProperty($property, $default = null, $doCreate = false)
{
$list = [];

View File

@@ -135,6 +135,7 @@ class Session implements SessionInterface
'use_strict_mode' => true,
'use_cookies' => true,
'use_only_cookies' => true,
'cookie_samesite' => true,
'referer_check' => true,
'cache_limiter' => true,
'cache_expire' => true,
@@ -211,14 +212,19 @@ class Session implements SessionInterface
if ($sessionExists) {
$params = session_get_cookie_params();
$cookie_options = array (
'expires' => time() + $params['lifetime'],
'path' => $params['path'],
'domain' => $params['domain'],
'secure' => $params['secure'],
'httponly' => $params['httponly'],
'samesite' => $params['samesite']
);
setcookie(
$sessionName,
session_id(),
time() + $params['lifetime'],
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
$cookie_options
);
}
@@ -231,14 +237,20 @@ class Session implements SessionInterface
public function invalidate()
{
$params = session_get_cookie_params();
$cookie_options = array (
'expires' => time() - 42000,
'path' => $params['path'],
'domain' => $params['domain'],
'secure' => $params['secure'],
'httponly' => $params['httponly'],
'samesite' => $params['samesite']
);
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
$cookie_options
);
if ($this->isSessionStarted()) {

View File

@@ -523,4 +523,20 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertSame('//foo.com', Utils::url('//foo.com'));
$this->assertSame('//foo.com?param=x', Utils::url('//foo.com?param=x'));
}
public function testCheckFilename()
{
// configure extension for consistent results
/** @var \Grav\Common\Config\Config $config */
$config = $this->grav['config'];
$config->set('security.uploads_dangerous_extensions', ['php', 'html', 'htm', 'exe', 'js']);
$this->assertFalse(Utils::checkFilename('foo.php'));
$this->assertFalse(Utils::checkFilename('bar.js'));
$this->assertTrue(Utils::checkFilename('foo.json'));
$this->assertTrue(Utils::checkFilename('foo.xml'));
$this->assertTrue(Utils::checkFilename('foo.yaml'));
$this->assertTrue(Utils::checkFilename('foo.yml'));
}
}

View File

@@ -27,6 +27,9 @@ RewriteEngine On
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to use twig tags in URL.
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.