Skip to content

Commit 8957436

Browse files
authored
Merge pull request #1871 from nextcloud/use-csp-nonces
Use CSP nonces
2 parents 27ba46c + ee8b8ad commit 8957436

19 files changed

Lines changed: 302 additions & 12 deletions

File tree

apps/theming/appinfo/app.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
'script',
4848
[
4949
'src' => $linkToJs,
50+
'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce()
5051
], ''
5152
);
5253

core/templates/layout.base.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
2020
<?php endforeach; ?>
2121
<?php foreach ($_['jsfiles'] as $jsfile): ?>
22-
<script src="<?php print_unescaped($jsfile); ?>"></script>
22+
<script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
2323
<?php endforeach; ?>
2424
<?php print_unescaped($_['headers']); ?>
2525
</head>

core/templates/layout.guest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
2121
<?php endforeach; ?>
2222
<?php foreach($_['jsfiles'] as $jsfile): ?>
23-
<script src="<?php print_unescaped($jsfile); ?>"></script>
23+
<script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
2424
<?php endforeach; ?>
2525
<?php print_unescaped($_['headers']); ?>
2626
</head>

core/templates/layout.user.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<link rel="stylesheet" href="<?php print_unescaped($cssfile); ?>" media="print">
2828
<?php endforeach; ?>
2929
<?php foreach($_['jsfiles'] as $jsfile): ?>
30-
<script src="<?php print_unescaped($jsfile); ?>"></script>
30+
<script nonce="<?php p(\OC::$server->getContentSecurityPolicyNonceManager()->getNonce()) ?>" src="<?php print_unescaped($jsfile); ?>"></script>
3131
<?php endforeach; ?>
3232
<?php print_unescaped($_['headers']); ?>
3333
</head>

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@
674674
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php',
675675
'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
676676
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
677+
'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
677678
'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
678679
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
679680
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
704704
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php',
705705
'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
706706
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
707+
'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
707708
'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
708709
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
709710
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',

lib/private/AppFramework/DependencyInjection/DIContainer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ public function __construct($appName, $urlParams = array()){
379379
$c['AppName'],
380380
$app->isLoggedIn(),
381381
$app->isAdminUser(),
382-
$app->getServer()->getContentSecurityPolicyManager()
382+
$app->getServer()->getContentSecurityPolicyManager(),
383+
$app->getServer()->getCsrfTokenManager()
383384
);
384385
});
385386

lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
3737
use OC\AppFramework\Utility\ControllerMethodReflector;
3838
use OC\Security\CSP\ContentSecurityPolicyManager;
39+
use OC\Security\CSRF\CsrfTokenManager;
3940
use OCP\AppFramework\Http\ContentSecurityPolicy;
4041
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
4142
use OCP\AppFramework\Http\RedirectResponse;
@@ -77,6 +78,8 @@ class SecurityMiddleware extends Middleware {
7778
private $isAdminUser;
7879
/** @var ContentSecurityPolicyManager */
7980
private $contentSecurityPolicyManager;
81+
/** @var CsrfTokenManager */
82+
private $csrfTokenManager;
8083

8184
/**
8285
* @param IRequest $request
@@ -88,6 +91,7 @@ class SecurityMiddleware extends Middleware {
8891
* @param bool $isLoggedIn
8992
* @param bool $isAdminUser
9093
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
94+
* @param CSRFTokenManager $csrfTokenManager
9195
*/
9296
public function __construct(IRequest $request,
9397
ControllerMethodReflector $reflector,
@@ -97,7 +101,8 @@ public function __construct(IRequest $request,
97101
$appName,
98102
$isLoggedIn,
99103
$isAdminUser,
100-
ContentSecurityPolicyManager $contentSecurityPolicyManager) {
104+
ContentSecurityPolicyManager $contentSecurityPolicyManager,
105+
CsrfTokenManager $csrfTokenManager) {
101106
$this->navigationManager = $navigationManager;
102107
$this->request = $request;
103108
$this->reflector = $reflector;
@@ -107,6 +112,7 @@ public function __construct(IRequest $request,
107112
$this->isLoggedIn = $isLoggedIn;
108113
$this->isAdminUser = $isAdminUser;
109114
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
115+
$this->csrfTokenManager = $csrfTokenManager;
110116
}
111117

112118

@@ -171,6 +177,23 @@ public function beforeController($controller, $methodName) {
171177

172178
}
173179

180+
private function browserSupportsCspV3() {
181+
$browserWhitelist = [
182+
// Chrome 40+
183+
'/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/',
184+
// Firefox 45+
185+
'/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/',
186+
// Safari 10+
187+
'/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/',
188+
];
189+
190+
if($this->request->isUserAgent($browserWhitelist)) {
191+
return true;
192+
}
193+
194+
return false;
195+
}
196+
174197
/**
175198
* Performs the default CSP modifications that may be injected by other
176199
* applications
@@ -190,6 +213,10 @@ public function afterController($controller, $methodName, Response $response) {
190213
$defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy();
191214
$defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy);
192215

216+
if($this->browserSupportsCspV3()) {
217+
$defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue());
218+
}
219+
193220
$response->setContentSecurityPolicy($defaultPolicy);
194221

195222
return $response;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
4+
*
5+
* @license GNU AGPL version 3 or any later version
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
*/
21+
22+
namespace OC\Security\CSP;
23+
24+
use OC\Security\CSRF\CsrfTokenManager;
25+
26+
/**
27+
* @package OC\Security\CSP
28+
*/
29+
class ContentSecurityPolicyNonceManager {
30+
/** @var CsrfTokenManager */
31+
private $csrfTokenManager;
32+
/** @var string */
33+
private $nonce = '';
34+
35+
/**
36+
* @param CsrfTokenManager $csrfTokenManager
37+
*/
38+
public function __construct(CsrfTokenManager $csrfTokenManager) {
39+
$this->csrfTokenManager = $csrfTokenManager;
40+
}
41+
42+
/**
43+
* Returns the current CSP nounce
44+
*
45+
* @return string
46+
*/
47+
public function getNonce() {
48+
if($this->nonce === '') {
49+
$this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue());
50+
}
51+
52+
return $this->nonce;
53+
}
54+
}

lib/private/Security/CSRF/CsrfToken.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
class CsrfToken {
3434
/** @var string */
3535
private $value;
36+
/** @var string */
37+
private $encryptedValue = '';
3638

3739
/**
3840
* @param string $value Value of the token. Can be encrypted or not encrypted.
@@ -48,8 +50,12 @@ public function __construct($value) {
4850
* @return string
4951
*/
5052
public function getEncryptedValue() {
51-
$sharedSecret = base64_encode(random_bytes(strlen($this->value)));
52-
return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret;
53+
if($this->encryptedValue === '') {
54+
$sharedSecret = base64_encode(random_bytes(strlen($this->value)));
55+
$this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret;
56+
}
57+
58+
return $this->encryptedValue;
5359
}
5460

5561
/**

0 commit comments

Comments
 (0)