In Twig, it's possible to do (and core does this in a few places) code like this, from breadcrumb.html.twig, that directly prints variables inside of an HTML attribute (esp. URLs in <a href> and <img src>):

<a href="{{ item.url }}">{{ item.text }}</a>

As long as the preprocess function (like template_preprocess_breadcrumb() is calling the proper URL sanitization functions (in this case $link->getUrl()->toString()), that {{ item.url }} is fine. However, there may be an expectation that Twig does this URL sanitization for you, since it does do this for HTML text, and therefore people might accidentally introduce XSS vectors in their code.

This is not a new problem. In Drupal 7, this was also your responsibility. And even in Twig, all Twig authors have to deal with this, either by piping to the url_encode or |e('html_attr'), or using the attribute object, or dealing with it in the prepping code.

What's new to Drupal 8 is that preprocess functions no longer need to make variables used within HTML text or fragments safe, as that gets autoescaped, so there is a bit of an inconsistency currently that preprocess functions in HEAD explicitly sanitize URLs, but not anything else, and a corollary inconsistency in which variables Twig templates print via the attributes object and which ones it prints directly into attribute values, such as <a href="{{ item.url }}">.

Possible things we could do:

- Nothing. Trust people new to D8 will read the Twig docs about this issue.
- Document cases like the above (there are lots of them) in the template and explain why they are safe.
- Either override or submit upstream an override for html_attr that does protocol filtering as well so it can be used for either URLs or strings
- Other?

CommentFileSizeAuthor
#7 unescaped-span.png42.82 KBeffulgentsia

Comments

webchick created an issue. See original summary.

effulgentsia’s picture

This is not a new problem; all Twig authors have to deal with this, either by ... or dealing with it in the prepping code. It is, however, new to Drupal 8.

Note that it is also not entirely new to Drupal 8 relative to Drupal 7. In Drupal 7, preprocess functions are expected to make all .tpl.php variables safe. But, what's new to Drupal 8 is that preprocess functions no longer need to make variables used within HTML text or fragments safe, as that gets autoescaped, so there is a bit of an inconsistency currently that preprocess functions in HEAD explicitly sanitize URLs, but not anything else, and a corollary inconsistency in which variables Twig templates print via the attributes object and which ones it prints directly into attribute values, such as <a href="{{ item.url }}">.

webchick’s picture

Title: Attribute filtering in Twig » Figure out what to do about attribute filtering in Twig
Category: Bug report » Task
Issue summary: View changes

Clarified that. Also, this issue title was missing a verb, which assaulted my inner grammar nerd, so fixed that too.

effulgentsia’s picture

Issue summary: View changes

Restored <code> tags in the IS.

catch’s picture

Just a note this isn't just about URLs, it's about any HTML attribute in a template - given that Twig won't auto-escape variables marked as safe that were filtered as opposed to escaped.

effulgentsia’s picture

StatusFileSize
new42.82 KB

Right. So for example, suppose you were to change bartik's node.html.twig to print the node title into an attribute value, like this:

<a href="{{ url }}" rel="bookmark" title="{{ label }}">{{ label }}</a>

Because {{ label }} contains a <span> element (for QuickEdit), you would get this:

Even if this example isn't an XSS vulnerability, it still leads to invalid HTML and renders to the browser in a way that isn't desired.

fabianx’s picture

Even if we tried hard to avoid using '|escape' in templates, I think this is one where it is beneficial for themers to learn it.

So we would teach to use:

<a href="{{ url | e('html_attr') }}" rel="bookmark" title="{{ label | e('html_attr') }}">{{ label }}</a>

with docs that the Attribute object takes care of that automatically.

In case url is a SafeStringAttribute() or a SafeString with strategy = 'html_attr' then twig would not do double-escaping.

Overall that kind of support would be pretty simple to add to drupalEscapeFilter() in TwigExtension.

One way to also do this is to use a link, though I would myself have to lookup how to add the label in (will edit later)

{{ link('text', url) }}

Of course writing direct HTML is nicer, but maybe we should allow access to create attribute objects on the fly in twig. Then you could choose:

a)

{% set attributes = safe_attributes({'label': label, 'href': url}); %}
<a{{- attributes }}>{{ label }}</a>

b)

<a href="{{ url | e('html_attr') }}" rel="bookmark" title="{{ label | e('html_attr') }}">{{ label }}</a>

In one case the Attribute object would take over the escaping, in the other you are yourself responsible.

In any case extending SafeString to either take a strategy parameter or have a class in descending order of safeness for each escaping strategy sounds a good idea to me and makes both of this possible.

fabianx’s picture

Actually a better example should be:

<a href="{{ url | e('url') }}" rel="bookmark" title="{{ label | e('html_attr') }}" style="{{ some_css|e('css') }}">{{ label }}</a>

and shows that maybe having magic attributes as a 90% API is better - if I get that wrong on first try ;).

Overall the following escaping strategies are supported in Twig:

  • - html
  • - js
  • - css
  • - url
  • - html_attr

(or a PHP callback that takes the template "filename" and must return the escaping strategy to use)

I would say that Urls itself should have the SafeUrlStringInterface implemented or something like that ...

joelpittet’s picture

Thanks @Fabianx. I agree, I do think we need to let Twig's security rule in the twig file.

It could prove tricky to change how those escape strategies work without going upstream pull requests, or a request to make them easier to change. So crossing my fingers a bit on them being acceptable as-is.

#9 this is a good example to work from and add to our documentation:
<a href="{{ url|e('url') }}" rel="bookmark" title="{{ label|e('html_attr') }}" style="{{ some_css|e('css') }}">{{ label }}</a>

And agree with creating a create_attribute({'id': var}) would be a great addition because right now we can only create new Attribute objects through PHP.

star-szr’s picture

Based on the Twig docs the 'url' strategy is not workable for full URLs. html_attr is probably fine for non-URL attributes.

Unless this part changes I think we should instead add our own strategies.

Built-in escapers cannot be overridden mainly they should be considered as the final implementation and also for better performance.

create_attribute() is interesting, probably not a bad idea.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Version: 9.4.x-dev » 10.1.x-dev
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs issue summary update

Since it's been 7 years without movement

1. Wonder if this is still a valid task
2. If so what needs to be done for D10

Worth noting #2544110: XSS attribute filtering is inconsistent and strips valid attributes is being worked on and not sure how it plays here.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)