Security in TypoScript - Applying stdWrap functions like htmlSpecialChars to data in dataWrap
A frequently used feature of TypoScript is stdWrap. It provides many functions and wrappers to parse your data. It serves as a multipurpose parsing suite with helpers of any kind. dataWrap is one of the most powerful among them, but it's also one of the most tricky ones. Read on for a little demonstration on how to add security to your TypoScript by applying the stdWrap function "htmlSpecialChars" to data in dataWrap...
Introduction
Last night I was having a security session on a community website. Some content was controlled by TypoScript and I did some penetration tests on Cross-Site Scripting (XSS).
The website allows anyone to register and enter data to be shown on the website. The threat was that a possible attacker could create an account and enter malicious data.
There were two possibilities to protect the website from being XSSed:
- 1. validate and filter the user data before it gets saved,
- 2. escape and filter the user data before it gets displayed on the website.
Adding XSS protection in TypoScript
Let's have a look at the second step where TypoScript comes into the match. For testing purpose I put the following malicious data into the DB fields:
<script>alert('fieldname')</script>
Whenever the data is not escaped before being displayed, an alert window pops up reporting the fieldname.
The website makes heavy use of dataWrap to show the data in the frontend. The cool thing with dataWrap is, that it allows to shrink your TypoScript code by putting multiple getText requests into a one liner. Have a look at the first example:
Example 1
page.10 = TEXT
page.10 {
dataWrap = Hello {field:title} {field:firstname} {field:lastname}
}
The output of a regular dataset here would be: Hello Dr. Franken Stein. But when testing a malicious dataset with <script>alert('fieldname')</script> I ran into XSS with popups all around.
So the next step was to remove all bad code from the result set. I decided to use htmlSpecialChars:
Example 2
page.10 = TEXT
page.10 {
dataWrap = Hello {field:title} {field:firstname} {field:lastname}
htmlSpecialChars = 1
}
My expectation was that the complete string would be parsed through htmlspecialchars(). If there was malicious data in the name fields, it would get escaped. But testing the stuff revealed the same behaviour as in example 1: The field data was not escaped with htmlspecialchars and I ended up with javascript alerts popping around on my screen. Next, I tried a different approach:
Example 3
page.10 = TEXT
page.10.value = Hello
page.20 = TEXT
page.20 {
field = title
htmlSpecialChars = 1
}
page.30 {
field = firstname
htmlSpecialChars = 1
}
page.40 {
field = lastname
htmlSpecialChars = 1
}
This time, testing was successful: All HTML data was escaped and no popups: The result was again Hello Dr. Franken Stein and Hello <script>alert('title')</script> <script>alert('firstname')</script> <script>alert('lastname')</script> as plain text with escaped characters.
The drawback was that the code was bloated. I tried to shrink it by switching back from field to dataWrap. And this time I tried a different approach to apply htmlSpecialChars:
Example 4
page.10 = COA
page.10.stdWrap.htmlSpecialChars = 1
page.10.10 = TEXT
page.10.10.dataWrap = Hello {field:title} {field:firstname} {field:lastname}
... and Bingo: No popups, all fields were correctly parsed through htmlspecialchar().
Summary
Although example 2 appeared logical to me, it did not work. The reason was that the getText sections in dataWrap were not being touched by surrounding stdWrap functions. As example 4 showed, the solution was to use COA as a container for the data. That made it possible to wrap the complete string with htmlSpecialChars.
What's the lesson learned of this case?
Never trust your intuitive expectation of how code works when it comes to security issues. Better test twice than overlook a leak. Test driven development is the way to go.
Comments
page.10.stdWrap.dataWrap = Hello {field:title} {field:firstname} {field:lastname}
thats the solution for example2!
because htmlSpecialChars is executed before dataWrap, but stdWrap is executed before htmlSpecialChars:)
IMHO it is important to understand, that the order or the properties are called in that order. So in many cases you can change the order with calling stdWrap.yourProperty.
But thanks for this summary anyway:)
Really good work, thx a lot.
I will directly use it. :)