Skip to content

Commit f9bb1fc

Browse files
authored
Merge pull request #102 from EC-CUBE/dev/feat_twig_sandbox3
脆弱性対応(Twig Sandbox)
2 parents b5593b0 + 658ddbd commit f9bb1fc

File tree

7 files changed

+306
-3
lines changed

7 files changed

+306
-3
lines changed

Diff for: src/Eccube/Application.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public function initConfig()
105105
->parseConfig('nav', $configAll, true)
106106
->parseConfig('doctrine_cache', $configAll)
107107
->parseConfig('http_cache', $configAll)
108-
->parseConfig('session_handler', $configAll);
108+
->parseConfig('session_handler', $configAll)
109+
->parseConfig('twig_sandbox', $configAll);
109110

110111
return $configAll;
111112
});
@@ -285,6 +286,7 @@ public function initRendering()
285286
$this->register(new \Silex\Provider\TwigServiceProvider(), array(
286287
'twig.form.templates' => array('Form/form_layout.twig'),
287288
));
289+
$this->register(new \Eccube\ServiceProvider\SandboxServiceProvider());
288290
$this['twig'] = $this->share($this->extend('twig', function (\Twig_Environment $twig, \Silex\Application $app) {
289291
$twig->addExtension(new \Eccube\Twig\Extension\EccubeExtension($app));
290292
$twig->addExtension(new \Twig_Extension_StringLoader());

Diff for: src/Eccube/Common/Constant.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Constant {
2828
/**
2929
* EC-CUBE VERSION.
3030
*/
31-
const VERSION = '3.0.18-p6';
31+
const VERSION = '3.0.18-p7';
3232

3333
/**
3434
* Enable value.

Diff for: src/Eccube/Resource/config/twig_sandbox.yml.dist

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
twig_sandbox:
2+
allowed_tags:
3+
- 'block'
4+
- 'extends'
5+
- 'for'
6+
- 'if'
7+
- 'set'
8+
- 'spaceless'
9+
- 'verbatim'
10+
- 'with'
11+
- 'form_theme'
12+
- 'stopwatch'
13+
- 'trans'
14+
- 'trans_default_domain'
15+
allowed_filters:
16+
- 'abs'
17+
- 'batch'
18+
- 'capitalize'
19+
- 'date'
20+
- 'escape'
21+
- 'default'
22+
- 'doctrine_format_sql'
23+
- 'doctrine_prettify_sql'
24+
- 'doctrine_pretty_query'
25+
- 'doctrine_replace_query_parameters'
26+
- 'first'
27+
- 'format'
28+
- 'abbr_class'
29+
- 'abbr_method'
30+
- 'file_link'
31+
- 'file_relative'
32+
- 'format_args'
33+
- 'format_args_as_text'
34+
- 'humanize'
35+
- 'json_encode'
36+
- 'keys'
37+
- 'last'
38+
- 'length'
39+
- 'lower'
40+
- 'merge'
41+
- 'replace'
42+
- 'round'
43+
- 'split'
44+
- 'striptags'
45+
- 'title'
46+
- 'trim'
47+
- 'no_image_product'
48+
- 'date_format'
49+
- 'price'
50+
- 'ellipsis'
51+
- 'time_ago'
52+
- 'upper'
53+
- 'date_modify'
54+
- 'escape'
55+
- 'nl2br'
56+
- 'number_format'
57+
- 'slice'
58+
- 'split'
59+
- 'striptags'
60+
allowed_functions:
61+
- 'cycle'
62+
- 'max'
63+
- 'min'
64+
- 'random'
65+
- 'range'
66+
- 'template_from_string'
67+
- 'absolute_url'
68+
- 'asset'
69+
- 'asset_version'
70+
- 'csrf_token'
71+
- 'form_parent'
72+
- 'fragment_uri'
73+
- 'impersonation_exit_path'
74+
- 'impersonation_exit_url'
75+
- 'is_granted'
76+
- 'logout_path'
77+
- 'logout_url'
78+
- 'path'
79+
- 'relative_path'
80+
- 't'
81+
- 'url'
82+
- 'calc_inc_tax'
83+
- 'active_menus'
84+
- 'csrf_token_for_anchor'
85+
- 'url'
86+
- 'path'
87+
- 'is_object'
88+
- 'get_product'
89+
- 'date'
90+
allowed_methods: []
91+
allowed_properties: []

Diff for: src/Eccube/Resource/template/default/Product/detail.twig

+1-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ $(function(){
278278
{% if Product.freearea %}
279279
<div id="sub_area" class="row">
280280
<div class="col-sm-10 col-sm-offset-1">
281-
<div id="detail_free_box__freearea" class="freearea">{{ include(template_from_string(Product.freearea)) }}</div>
281+
<div id="detail_free_box__freearea" class="freearea">{{ include(template_from_string(Product.freearea), sandboxed = true) }}</div>
282282
</div>
283283
</div>
284284
{% endif %}
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Eccube\ServiceProvider;
4+
5+
use Eccube\EventListener\LogListener;
6+
use Eccube\Log\Logger;
7+
use Eccube\Log\Monolog\Helper\LogHelper;
8+
use Eccube\Twig\Extension\IgnoreTwigSandboxErrorExtension;
9+
use Silex\Application;
10+
use Silex\ServiceProviderInterface;
11+
use Symfony\Bridge\Twig\Extension\DumpExtension;
12+
13+
/**
14+
* Class LogServiceProvider
15+
*
16+
* @package Eccube\ServiceProvider
17+
*/
18+
class SandboxServiceProvider implements ServiceProviderInterface
19+
{
20+
public function register(Application $app)
21+
{
22+
$app['twig'] = $app->share($app->extend('twig', function ($twig, $app) {
23+
24+
// ホワイトリストの設定
25+
$twig_sandbox_list = $app['config']['twig_sandbox'];
26+
27+
$tags = $twig_sandbox_list['allowed_tags'];
28+
$filters = $twig_sandbox_list['allowed_filters'];
29+
$methods = $twig_sandbox_list['allowed_methods'];
30+
$properties = $twig_sandbox_list['allowed_properties'];
31+
$functions = $twig_sandbox_list['allowed_functions'];
32+
33+
$policy = new \Twig\Sandbox\SecurityPolicy($tags, $filters, $methods, $properties, $functions);
34+
$sandbox = new \Twig\Extension\SandboxExtension($policy);
35+
36+
$twig->addExtension($sandbox);
37+
$twig->addExtension(new IgnoreTwigSandboxErrorExtension());
38+
39+
return $twig;
40+
}));
41+
}
42+
43+
public function boot(Application $app)
44+
{
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of EC-CUBE
5+
*
6+
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7+
*
8+
* http://www.ec-cube.co.jp/
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Eccube\Twig\Extension;
15+
16+
use Twig\Environment;
17+
use Twig\Error\LoaderError;
18+
use Twig\Extension\AbstractExtension;
19+
use Twig\Extension\SandboxExtension;
20+
use Twig\Sandbox\SecurityError;
21+
use Twig\TwigFunction;
22+
23+
/**
24+
* \vendor\twig\twig\src\Extension\CoreExtension の拡張
25+
*/
26+
class IgnoreTwigSandboxErrorExtension extends AbstractExtension
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function getFunctions()
32+
{
33+
return array(
34+
new \Twig_SimpleFunction('include', array($this, 'twig_include'), array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))),
35+
);
36+
}
37+
38+
/**
39+
* twig sandboxの例外を操作します
40+
* app_env = devの場合、エラーを表示する
41+
* app_env = prodの場合、エラーを表示しない
42+
*
43+
* @param Environment $env
44+
* @param $context
45+
* @param $template
46+
* @param $variables
47+
* @param $withContext
48+
* @param $ignoreMissing
49+
* @param $sandboxed
50+
*
51+
* @return string|void
52+
*
53+
* @throws LoaderError
54+
* @throws SecurityError
55+
*/
56+
public function twig_include(Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
57+
{
58+
try {
59+
return \twig_include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed);
60+
} catch (SecurityError $e) {
61+
62+
// devではエラー画面が表示されるようにする
63+
$app = \Eccube\Application::getInstance(array('output_config_php' => false));
64+
if ($app['debug']) {
65+
throw $e;
66+
} else {
67+
// ログ出力
68+
log_warning($e->getMessage(), array('exception' => $e));
69+
70+
// 例外がスローされた場合、sandboxが効いた状態になってしまうため追加
71+
$sandbox = $env->getExtension( '\Twig\Extension\SandboxExtension');
72+
if (!$sandbox->isSandboxedGlobally()) {
73+
$sandbox->disableSandbox();
74+
}
75+
}
76+
}
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of EC-CUBE
5+
*
6+
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7+
*
8+
* http://www.ec-cube.co.jp/
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Eccube\Tests\Twig\Extension;
15+
16+
use Eccube\Tests\Web\AbstractWebTestCase;
17+
18+
class IgnoreTwigSandboxErrorExtensionTest extends AbstractWebTestCase
19+
{
20+
protected $app;
21+
22+
23+
public function setUp()
24+
{
25+
parent::setUp();
26+
$this->app = \Eccube\Application::getInstance();
27+
$this->app['debug'] = false;
28+
}
29+
30+
31+
public function tearDown()
32+
{
33+
$this->app['debug'] = true;
34+
parent::tearDown();
35+
}
36+
37+
/**
38+
* @dataProvider twigSnippetsProvider
39+
*/
40+
public function testFreeArea($snippet, $whitelisted)
41+
{
42+
$Product = $this->createProduct();
43+
$Product->setFreeArea('__RENDERED__'.$snippet);
44+
$this->app['orm.em']->flush();
45+
46+
$crawler = $this->client->request('GET', $this->app['url_generator']->generate('product_detail', array('id' => $Product->getId())));
47+
$text = $crawler->text();
48+
49+
// $snippetがsandboxで制限された場合はフリーエリアは空で出力されるため、__RENDERED__の出力有無で結果を確認する
50+
if ($whitelisted) {
51+
self::assertContains('__RENDERED__', $text);
52+
} else {
53+
self::assertNotContains('__RENDERED__', $text);
54+
}
55+
}
56+
57+
public function twigSnippetsProvider()
58+
{
59+
// 0: twigスニペット, 1: ホワイトリスト対象かどうか
60+
return array(
61+
// タグ・フィルター・関数
62+
array('{% set foo = "bar" %}', true),
63+
array('{% with %} test {% endwith %}', true),
64+
array('{% if true %} <p>test</p> {% endif %}', true),
65+
array('{% autoescape %} test {% endautoescape %}', false),
66+
array('{% macro input(name, value, type = "text", size = 20) %}<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}"/>{% endmacro %}', false),
67+
array('{% include "index.twig" %}', false),
68+
array('{{ "-5"|abs }}', true),
69+
array('{{ "2020/02/01"|date }}', true),
70+
array('{{ [1, 2, 3, 4]|first }}', true),
71+
array('{{ [1, 2, 3]|sort }}', false),
72+
array('{{ "<p> <strong>test</strong> </p>" |raw }}', false),
73+
array('{{ url("homepage") }}', true),
74+
array('{{ random(1, 100) }}', true),
75+
array('{% for i in range(3, 0) %} {{ i }}, {% endfor %}', true),
76+
array('{{ source("index.twig") }}', false),
77+
array('{{ form_start(form) }} <div>test </div> {{ form_end(form) }}', false),
78+
array('{{ include(template_from_string("Hello")) }}', false),
79+
// 変数
80+
array('{{ Product.name }}', true),
81+
array('{{ app.session }}', false),
82+
array('{{ app.security }}', false),
83+
array('{{ app.request.cookies }}', false),
84+
);
85+
}
86+
}

0 commit comments

Comments
 (0)