Skip to content

Commit f831931

Browse files
author
Dale Sikkema
committed
MAGETWO-16192: Security: Clickjacking solution - introduce X-Frame-Options
- use plugin to send xFrameOptions header when response is sent - get inject header value into plugin via DI argument injection - setup a config field in env.php to contain non-backend header values
1 parent 86646fc commit f831931

File tree

10 files changed

+136
-9
lines changed

10 files changed

+136
-9
lines changed

.htaccess

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,6 @@
6565
SecFilterScanPOST Off
6666
</IfModule>
6767

68-
<IfModule mod_headers.c>
69-
############################################
70-
## prevent clickjacking
71-
72-
Header set X-Frame-Options SAMEORIGIN
73-
</IfModule>
74-
7568
<IfModule mod_deflate.c>
7669

7770
############################################
@@ -187,4 +180,4 @@
187180
## If running in cluster environment, uncomment this
188181
## http://developer.yahoo.com/performance/rules.html#etags
189182

190-
#FileETag none
183+
#FileETag none

app/code/Magento/Backend/etc/adminhtml/di.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,9 @@
122122
</argument>
123123
</arguments>
124124
</type>
125+
<type name="Magento\Framework\App\Response\XFrameOptPlugin">
126+
<arguments>
127+
<argument name="xFrameOpt" xsi:type="const">Magento\Framework\App\Response\XFrameOptPlugin::BACKEND_X_FRAME_OPT</argument>
128+
</arguments>
129+
</type>
125130
</config>

app/code/Magento/Store/etc/di.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,8 @@
296296
</argument>
297297
</arguments>
298298
</type>
299+
<type name="Magento\Framework\App\Response\Http">
300+
<plugin name="xFrameOptionsHeader" type="Magento\Framework\App\Response\XFrameOptPlugin"/>
301+
</type>
302+
299303
</config>

app/etc/di.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@
134134
<preference for="Magento\Framework\Api\ImageContentValidatorInterface" type="Magento\Framework\Api\ImageContentValidator" />
135135
<preference for="Magento\Framework\Api\ImageProcessorInterface" type="Magento\Framework\Api\ImageProcessor" />
136136
<preference for="Magento\Framework\Code\Reader\ClassReaderInterface" type="Magento\Framework\Code\Reader\ClassReader" />
137+
<type name="Magento\Framework\App\Response\XFrameOptPlugin">
138+
<arguments>
139+
<argument name="xFrameOpt" xsi:type="init_parameter">Magento\Framework\App\Response\XFrameOptPlugin::DEPLOYMENT_CONFIG_X_FRAME_OPT</argument>
140+
</arguments>
141+
</type>
137142
<type name="Magento\Framework\Model\Resource\Db\TransactionManager" shared="false" />
138143
<type name="Magento\Framework\Logger\Handler\Base">
139144
<arguments>

lib/internal/Magento/Framework/App/Response/Http.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response
2121
/** Format for expiration timestamp headers */
2222
const EXPIRATION_TIMESTAMP_FORMAT = 'D, d M Y H:i:s T';
2323

24+
/** X-FRAME-OPTIONS Header name */
25+
const HEADER_X_FRAME_OPT = 'X-Frame-Options';
26+
2427
/** @var \Magento\Framework\Stdlib\CookieManagerInterface */
2528
protected $cookieManager;
2629

@@ -51,6 +54,18 @@ public function __construct(
5154
$this->dateTime = $dateTime;
5255
}
5356

57+
/**
58+
* Sends the X-FRAME-OPTIONS header to protect against click-jacking
59+
*
60+
* @param string $value
61+
* @return void
62+
* @codeCoverageIgnore
63+
*/
64+
public function setXFrameOptions($value)
65+
{
66+
$this->setHeader(self::HEADER_X_FRAME_OPT, $value);
67+
}
68+
5469
/**
5570
* Send Vary cookie
5671
*
@@ -109,6 +124,7 @@ public function setPrivateHeaders($ttl)
109124
* Set headers for no-cache responses
110125
*
111126
* @return void
127+
* @codeCoverageIgnore
112128
*/
113129
public function setNoCacheHeaders()
114130
{
@@ -122,6 +138,7 @@ public function setNoCacheHeaders()
122138
*
123139
* @param string $content String in JSON format
124140
* @return \Magento\Framework\App\Response\Http
141+
* @codeCoverageIgnore
125142
*/
126143
public function representJson($content)
127144
{
@@ -131,6 +148,7 @@ public function representJson($content)
131148

132149
/**
133150
* @return string[]
151+
* @codeCoverageIgnore
134152
*/
135153
public function __sleep()
136154
{
@@ -141,6 +159,7 @@ public function __sleep()
141159
* Need to reconstruct dependencies when being de-serialized.
142160
*
143161
* @return void
162+
* @codeCoverageIgnore
144163
*/
145164
public function __wakeup()
146165
{
@@ -154,6 +173,7 @@ public function __wakeup()
154173
*
155174
* @param string $time
156175
* @return string
176+
* @codeCoverageIgnore
157177
*/
158178
protected function getExpirationHeader($time)
159179
{
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
/***
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Framework\App\Response;
8+
9+
/**
10+
* Adds an X-FRAME-OPTIONS header to HTTP responses to safeguard against click-jacking.
11+
* @codeCoverageIgnore
12+
*/
13+
class XFrameOptPlugin
14+
{
15+
/** Deployment config key for frontend x-frame-options header value */
16+
const DEPLOYMENT_CONFIG_X_FRAME_OPT = 'x-frame-options';
17+
18+
/** Always send DENY in backend x-frame-options header */
19+
const BACKEND_X_FRAME_OPT = 'DENY';
20+
21+
/**
22+
*The header value
23+
* @var string
24+
*/
25+
private $xFrameOpt;
26+
27+
/**
28+
* @param string $xFrameOpt
29+
*/
30+
public function __construct($xFrameOpt)
31+
{
32+
$this->xFrameOpt = $xFrameOpt;
33+
}
34+
35+
/**
36+
* @param \Magento\Framework\App\Response\Http $subject
37+
* @return void
38+
*/
39+
public function beforeSendResponse(\Magento\Framework\App\Response\Http $subject)
40+
{
41+
$subject->setXFrameOptions($this->xFrameOpt);
42+
}
43+
}

lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ConfigOptionsListConstants
2222
const CONFIG_PATH_DB_CONNECTION_DEFAULT = 'db/connection/default';
2323
const CONFIG_PATH_DB_CONNECTIONS = 'db/connection';
2424
const CONFIG_PATH_DB_PREFIX = 'db/table_prefix';
25+
const CONFIG_PATH_X_FRAME_OPT = 'x-frame-options';
2526
/**#@-*/
2627

2728
/**#@+
@@ -67,7 +68,7 @@ class ConfigOptionsListConstants
6768
const KEY_INIT_STATEMENTS = 'initStatements';
6869
const KEY_ACTIVE = 'active';
6970
/**#@-*/
70-
71+
7172
/**
7273
* Db config key
7374
*/

setup/src/Magento/Setup/Model/ConfigGenerator.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,18 @@ public function createResourceConfig()
210210

211211
return $configData;
212212
}
213+
214+
/**
215+
* Creates x-frame-options header config data
216+
*
217+
* @return ConfigData
218+
*/
219+
public function createXFrameConfig()
220+
{
221+
$configData = new ConfigData(ConfigFilePool::APP_ENV);
222+
if ($this->deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT) === null) {
223+
$configData->set(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT, 'DENY');
224+
}
225+
return $configData;
226+
}
213227
}

setup/src/Magento/Setup/Model/ConfigOptionsList.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public function createConfig(array $data, DeploymentConfig $deploymentConfig)
158158
}
159159
$configData[] = $this->configGenerator->createDbConfig($data);
160160
$configData[] = $this->configGenerator->createResourceConfig();
161+
$configData[] = $this->configGenerator->createXFrameConfig();
161162
return $configData;
162163
}
163164

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/***
3+
* Copyright © 2015 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Setup\Test\Unit\Model;
8+
9+
10+
use Magento\Framework\Config\ConfigOptionsListConstants;
11+
12+
class ConfigGeneratorTest extends \PHPUnit_Framework_TestCase
13+
{
14+
/** @var \Magento\Framework\App\DeploymentConfig | \PHPUnit_Framework_MockObject_MockObject */
15+
private $deploymentConfigMock;
16+
/** @var \Magento\Setup\Model\ConfigGenerator | \PHPUnit_Framework_MockObject_MockObject */
17+
private $model;
18+
19+
public function setUp()
20+
{
21+
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
22+
23+
$this->deploymentConfigMock = $this->getMockBuilder('Magento\Framework\App\DeploymentConfig')
24+
->disableOriginalConstructor()
25+
->getMock();
26+
$this->model = $objectManager->getObject(
27+
'Magento\Setup\Model\ConfigGenerator',
28+
['deploymentConfig' => $this->deploymentConfigMock]
29+
);
30+
}
31+
32+
public function testCreateXFrameConfig()
33+
{
34+
$this->deploymentConfigMock->expects($this->atLeastOnce())
35+
->method('get')
36+
->with(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT)
37+
->willReturn(null);
38+
$configData = $this->model->createXFrameConfig();
39+
$this->assertSame('DENY', $configData->getData()[ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT]);
40+
}
41+
}

0 commit comments

Comments
 (0)