Skip to content

Commit 32f57bf

Browse files
committed
[TASK] Sanitize CSV and XLS export against Excel hacks
as described in https://typo3.org/security/advisory/typo3-psa-2021-002
1 parent 0cc833b commit 32f57bf

File tree

5 files changed

+150
-85
lines changed

5 files changed

+150
-85
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare(strict_types = 1);
3+
namespace In2code\Powermail\Exception;
4+
5+
/**
6+
* OutdatedTypo3Exception
7+
*/
8+
class OutdatedTypo3Exception extends \Exception
9+
{
10+
}

Classes/Utility/CsvUtility.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
declare(strict_types = 1);
3+
namespace In2code\Powermail\Utility;
4+
5+
use In2code\Powermail\Exception\OutdatedTypo3Exception;
6+
use TYPO3\CMS\Core\Utility\CsvUtility as CsvUtilityCore;
7+
8+
/**
9+
* CsvUtility
10+
*/
11+
class CsvUtility extends CsvUtilityCore
12+
{
13+
/**
14+
* See See https://typo3.org/security/advisory/typo3-psa-2021-002 for details
15+
*
16+
* @param string $value
17+
* @return string
18+
* @throws OutdatedTypo3Exception
19+
*/
20+
public static function sanitizeCell(string $value): string
21+
{
22+
if (method_exists(__CLASS__, 'prefixControlLiterals')) {
23+
return self::prefixControlLiterals($value);
24+
}
25+
throw new OutdatedTypo3Exception(
26+
'Function prefixControlLiterals() does not exists in your TYPO3 instance. ' .
27+
'Please update to the latest version.',
28+
1628632343
29+
);
30+
}
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
declare(strict_types = 1);
3+
namespace In2code\Powermail\ViewHelpers\String;
4+
5+
use In2code\Powermail\Utility\CsvUtility;
6+
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
7+
8+
/**
9+
* SanitizeCsvCellViewHelper
10+
* Because of a hijacking possibilit of Excel, we should clean the column value
11+
* See https://typo3.org/security/advisory/typo3-psa-2021-002 and https://owasp.org/www-community/attacks/CSV_Injection
12+
* for details
13+
*/
14+
class SanitizeCsvCellViewHelper extends AbstractViewHelper
15+
{
16+
/**
17+
* @return string
18+
*/
19+
public function render(): string
20+
{
21+
return CsvUtility::sanitizeCell($this->renderChildren());
22+
}
23+
}
Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,94 @@
11
{namespace vh=In2code\Powermail\ViewHelpers}
22
<f:layout name="Export" />
33

4-
Render Powermail CSV Export
5-
{mails} All Mails for exporting
6-
{fieldsUids} Fields to export (drag'n drop settings in module)
4+
Render Powermail CSV Export
5+
{mails} All Mails for exporting
6+
{fieldsUids} Fields to export (drag'n drop settings in module)
77

88
<f:section name="main"><vh:string.utf8ToUtf16><vh:string.trim>
99

10-
<f:comment>
11-
Headline
12-
</f:comment>
13-
<f:for each="{fieldUids}" as="fieldUid">
14-
<f:if condition="{vh:condition.isNumber(val:fieldUid)}">
15-
<f:then>
16-
"<vh:string.removeQuote><vh:getter.getFieldLabelFromUid uid="{fieldUid}" /></vh:string.removeQuote>";
17-
</f:then>
18-
<f:else>
19-
"<vh:string.removeQuote><f:translate key="\In2code\Powermail\Domain\Model\Mail.{vh:String.UnderscoredToLowerCamelCase(val:fieldUid)}" /></vh:string.removeQuote>";
20-
</f:else>
21-
</f:if>
22-
</f:for>
23-
<br />
10+
<f:comment>
11+
Headline
12+
</f:comment>
13+
<f:for each="{fieldUids}" as="fieldUid">
14+
<f:if condition="{vh:condition.isNumber(val:fieldUid)}">
15+
<f:then>
16+
"<vh:string.removeQuote><vh:getter.getFieldLabelFromUid uid="{fieldUid}" /></vh:string.removeQuote>";
17+
</f:then>
18+
<f:else>
19+
"<vh:string.removeQuote><f:translate key="\In2code\Powermail\Domain\Model\Mail.{vh:String.UnderscoredToLowerCamelCase(val:fieldUid)}" /></vh:string.removeQuote>";
20+
</f:else>
21+
</f:if>
22+
</f:for>
23+
<br />
2424

2525

26-
<f:comment>
27-
Content line
28-
</f:comment>
29-
<f:for each="{mails}" as="mail" iteration="index">
30-
<f:for each="{fieldUids}" as="fieldUid">
31-
<f:if condition="{vh:condition.isNumber(val:fieldUid)}">
32-
<f:then>
33-
<f:for each="{mail.answers}" as="answer">
34-
<f:if condition="{fieldUid} == {answer.field.uid}">
35-
<f:if condition="{vh:condition.isArray(val:answer.value)}">
36-
<f:then>
37-
"<vh:string.removeQuote>
38-
<f:for each="{answer.value}" as="singleValue" iteration="arrayIndex">
39-
<f:if condition="{singleValue}">
40-
{singleValue}<f:if condition="{arrayIndex.isLast}"><f:else>,</f:else></f:if>
41-
</f:if>
42-
</f:for>
43-
</vh:string.removeQuote>";
44-
</f:then>
45-
<f:else>
46-
"<vh:string.removeQuote>{answer.value}</vh:string.removeQuote>";
47-
</f:else>
48-
</f:if>
49-
</f:if>
50-
</f:for>
51-
</f:then>
52-
<f:else>
53-
<f:if condition="{vh:condition.isDateTimeVariableInVariable(obj:mail, prop:fieldUid)}">
54-
<f:then>
55-
<f:if condition="{0: fieldUid} == {0: 'crdate'}">
56-
<f:then>
57-
"<vh:string.removeQuote><f:format.date format="d.m.Y H:i:s"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date>
58-
<f:translate key="Clock" /></vh:string.removeQuote>";
59-
</f:then>
60-
<f:else>
26+
<f:comment>
27+
Content line
28+
</f:comment>
29+
<f:for each="{mails}" as="mail" iteration="index">
30+
<f:for each="{fieldUids}" as="fieldUid">
31+
<f:if condition="{vh:condition.isNumber(val:fieldUid)}">
32+
<f:then>
33+
<f:for each="{mail.answers}" as="answer">
34+
<f:if condition="{fieldUid} == {answer.field.uid}">
35+
<f:if condition="{vh:condition.isArray(val:answer.value)}">
36+
<f:then>
37+
"<vh:string.removeQuote>
38+
<vh:string.sanitizeCsvCell>
39+
<f:for each="{answer.value}" as="singleValue" iteration="arrayIndex">
40+
<f:if condition="{singleValue}">
41+
{singleValue}<f:if condition="{arrayIndex.isLast}"><f:else>,</f:else></f:if>
42+
</f:if>
43+
</f:for>
44+
</vh:string.sanitizeCsvCell>
45+
</vh:string.removeQuote>";
46+
</f:then>
47+
<f:else>
48+
"<vh:string.removeQuote><vh:string.sanitizeCsvCell>{answer.value}</vh:string.sanitizeCsvCell></vh:string.removeQuote>";
49+
</f:else>
50+
</f:if>
51+
</f:if>
52+
</f:for>
53+
</f:then>
54+
<f:else>
55+
<f:if condition="{vh:condition.isDateTimeVariableInVariable(obj:mail, prop:fieldUid)}">
56+
<f:then>
57+
<f:if condition="{0: fieldUid} == {0: 'crdate'}">
58+
<f:then>
59+
"<vh:string.removeQuote><f:format.date format="d.m.Y H:i:s"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date>
60+
<f:translate key="Clock" /></vh:string.removeQuote>";
61+
</f:then>
62+
<f:else>
63+
<f:if condition="{0: fieldUid} == {0: 'time'}">
64+
<f:then>
65+
"<vh:string.removeQuote><f:format.date format="%M:%S"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date></vh:string.removeQuote>";
66+
</f:then>
67+
<f:else>
68+
"<vh:string.removeQuote><f:format.date format="H:i:s"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date></vh:string.removeQuote>";
69+
</f:else>
70+
</f:if>
71+
</f:else>
72+
</f:if>
73+
</f:then>
74+
<f:else>
75+
<f:if condition="{0: fieldUid} == {0: 'marketing_page_funnel'}">
76+
<f:then>
77+
<f:if condition="{vh:condition.isArray(val: '{vh:misc.variableInVariable(obj:mail, prop:fieldUid)}')}">
78+
"<f:for each="{vh:misc.variableInVariable(obj:mail, prop:fieldUid)}" as="pid" iteration="pageIndex"><vh:getter.getPageNameFromUid uid="{pid}" /><f:if condition="{pageIndex.isLast}"><f:else> > </f:else></f:if></f:for>"
79+
</f:if>
80+
</f:then>
81+
<f:else>
82+
"<vh:string.removeQuote><vh:string.sanitizeCsvCell><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></vh:string.sanitizeCsvCell></vh:string.removeQuote>";
83+
</f:else>
84+
</f:if>
85+
</f:else>
86+
</f:if>
87+
</f:else>
88+
</f:if>
89+
</f:for>
6190

62-
<f:if condition="{0: fieldUid} == {0: 'time'}">
63-
<f:then>
64-
"<vh:string.removeQuote><f:format.date format="%M:%S"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date></vh:string.removeQuote>";
65-
</f:then>
66-
<f:else>
67-
"<vh:string.removeQuote><f:format.date format="H:i:s"><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></f:format.date></vh:string.removeQuote>";
68-
</f:else>
69-
</f:if>
70-
</f:else>
71-
</f:if>
72-
</f:then>
73-
<f:else>
74-
<f:if condition="{0: fieldUid} == {0: 'marketing_page_funnel'}">
75-
<f:then>
76-
<f:if condition="{vh:condition.isArray(val: '{vh:misc.variableInVariable(obj:mail, prop:fieldUid)}')}">
77-
"<f:for each="{vh:misc.variableInVariable(obj:mail, prop:fieldUid)}" as="pid" iteration="pageIndex"><vh:getter.getPageNameFromUid uid="{pid}" /><f:if condition="{pageIndex.isLast}"><f:else> > </f:else></f:if></f:for>"
78-
</f:if>
79-
</f:then>
80-
<f:else>
81-
"<vh:string.removeQuote><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></vh:string.removeQuote>";
82-
</f:else>
83-
</f:if>
84-
</f:else>
85-
</f:if>
86-
</f:else>
87-
</f:if>
88-
</f:for>
89-
90-
<br />
91-
</f:for>
91+
<br />
92+
</f:for>
9293

9394
</vh:string.trim></vh:string.utf8ToUtf16></f:section>

Resources/Private/Templates/Module/ExportXls.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@
4040
<f:then>
4141
<f:for each="{answer.value}" as="singleValue" iteration="arrayIndex">
4242
<f:if condition="{singleValue}">
43-
{singleValue}<f:if condition="{arrayIndex.isLast}"><f:else>,</f:else></f:if>
43+
<vh:string.sanitizeCsvCell>{singleValue}</vh:string.sanitizeCsvCell><f:if condition="{arrayIndex.isLast}"><f:else>,</f:else></f:if>
4444
</f:if>
4545
</f:for>
4646
</f:then>
4747
<f:else>
48-
{answer.value}
48+
<vh:string.sanitizeCsvCell>{answer.value}</vh:string.sanitizeCsvCell>
4949
</f:else>
5050
</f:if>
5151
</f:if>
@@ -81,7 +81,7 @@
8181
</f:if>
8282
</f:then>
8383
<f:else>
84-
<vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" />
84+
<vh:string.sanitizeCsvCell><vh:misc.variableInVariable obj="{mail}" prop="{fieldUid}" /></vh:string.sanitizeCsvCell>
8585
</f:else>
8686
</f:if>
8787
</f:else>

0 commit comments

Comments
 (0)