Twig: Allow HTML, but escape script

3.8k views Asked by At

I am investigating a possible XSS attack vector for my application.

What I have:

  • FormType with a single textarea field. Normally this field can contain html tags.
  • Twig template that renders the data inserted.

I use that form to insert the following content:

<b>Some valid HTML text</b>
<script type="text/javascript">alert("XSS")</script>

Viewing that data would require escaping. I am familiar with few strategies when it comes to escaping the data.

1) raw filter: Completely disables escaping -> introduces possible XSS

2) e filter:

  • html flavor outputs: <b>Some valid HTML text</b> <script type="text/javascript">alert("XSS")</script>
  • js flavor outputs: \x3Cb\x3ESome\x20valid\x20HTML\x20text\x3C\x2Fb\x3E\x0D\x0A\x3Cscript\x20type\x3D\x22text\x2Fjavascript\x22\x3Ealert\x28\x22XSS\x22\x29\x3C\x2Fscript\x3E

3) {{ var|striptags('<br>')|raw }}, outputs: Some valid HTML text alert("XSS")

This one works, but somehow I don't like it. I am rather looking for a black-list solution, not white-list.

Now the question:

Is there any other escaping strategy that allows html tags but escapes <script> tag like e("js") filter does?

Should I "kill" the script during the form submission or during the Twig rendering?

1

There are 1 answers

6
pcm On BEST ANSWER

I would suggest adding a new Twig filter that fits your needs.

It should look something like

{{var | filter_black_listed() }}

and in the filter logic you add something like

class FilterBlackListedExtension extends \Twig_Extension
{
    private $blacklistedTags = ['script', 'p'];

    public function getFilters()
    {
        return array(
            new \Twig_SimpleFilter('filter_black_listed', array($this, 'htmlFilter')),
        );
    }

    public function htmlFilter($html)
    {
        foreach ($this->blacklistedTags as $tag) {
            preg_replace('/(<' . $tag . '>)(.*)(<\/' . $tag . '>)/g', '', $html);
        }

        return $html; // maybe even apply the raw filter also afterwards.
    }

    public function getName()
    {
        return 'filter_black_listed_extension';
    }
}

let me know if you don't manage to make this work :)