Blocktranslate asvar escapes without marking safe (Re: Ticket 33631)

(Follow up to ticket 33631, where I was asked to discuss this here)

Short summary: The documentation gives this example usage of blocktranslate asvar:

{% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}

And then uses {{ the_title }} in the rest of the template. This example is broken regarding HTML character escaping, because the_title is a str instead of a SafeString. Thus, any further use of {{ the_title }} will lead to double-escaping as long as {{ title }} produced any characters that would be changed by HTML escaping.

In the ticket, we considered whether blocktranslate asvar should return a SafeString instead, which I think it should.

However the ticket was closed as wontfix,

  1. claiming that it was not clear whether the output assigned to the_title could reliably be marked safe and
  2. asking me to rather use {{ the_title|safe }} in any following usage.

I think both these points are wrong or misleading:

  1. If I removed the asvar part, the output would be rendered without additional escaping, so the blocktranslate result is already implicitly marked safe. Only when I add the asvar argument, because the result is then stored in a str instead of being rendered, this information gets lost. Consider this code:

    from django.template import Template, Context
    template_content = "{% blocktranslate %}Title: &{{ title }}{% endblocktranslate %}"
    rendered = Template(template_content).render(Context({"title": "amp; & Title"}))
    assert "Title: & & Title" in rendered  # assertion successful
    

    Notice that the inner & is escaped exactly once, and the html-escape character & that is formed accidentally, is kept exactly as is, without the additional escaping.

    I argue that these two templates should produce exactly the same output (which is also what the example in the documentation implies), but they don’t:

    {% blocktranslate asvar the_title %}Title: &{{ title }}{% endblocktranslate %}{{ the_title }}
    
    {% blocktranslate %}Title: &{{ title }}{% endblocktranslate %}
    
  2. Obviously, the example from the documentation is a toy-example. In a real-world scenario, you’d have usages much later, or even in different (included) files. For XSS safety, strings should be marked safe at the source whenever possible, so code auditors can directly reason about correctness and safety. Storing the string marked as unsafe and expecting all later uses to use the |safe template tag is a XSS vulnerability waiting to happen.

Note that any workaround we ask users to apply here (such as manually adding |safe on later uses, or computing the title in the view, or something else) basically means that we disagree with the (very sane) assumptions that the template in the documentation makes.

1 Like

Thanks for the follow-up @He3lixxx! :+1:

It’s this bit I’m not quite following:

… output would be rendered without additional escaping

Whether we use asvar or not, IFACS only the variable parts of the constructed string are run through conditional_escape(), via render_value_in_context() (Relevant block)

Given that the rest of the template in the blocktranslate could be anything, and once assigned to a variable used anywhere, it’s that which I was a bit :grimacing: about being _"… a XSS vulnerability waiting to happen".

I couldn’t see how we can safely (in the general case) mark that as a safe string? (Can we? :thinking:)

Hence asking you to follow-up here so we could get more eyes on it. :eyes:

Hi @carltongibson, thanks for the fast reply.

only the variable parts of the constructed string are run through conditional_escape()

Yes.

Given that the rest of the template in the blocktranslate could be anything

This does not match what I think django’s policy on template inputs for translate and blocktranslate is. As discussed here, the template is considered safe by django for usability reasons. This is why blocktranslate can and will directly print the contents without any further escaping when used without asvar:

{% blocktranslate %} This is considered safe, even with <b>HTML</b> {% endblocktranslate %}

renders to (without any escaping happening)

This is considered safe, even with <b>HTML</b>

With that in mind, I don’t understand why the asvar argument would change anything about this. If we would normally put the translation result in the rendered template without escaping, why would we not mark it safe when storing it in a variable?


Edit: I think we can simplify the case to avoid confusion:

{% blocktranslate %}Some <b>HTML</b>{% endblocktranslate %}

assumes that the template is safe, and renders it directly, resulting in

Some <b>HTML</b>

where

{% blocktranslate asvar some_var %}Some <b>HTML</b>{% endblocktranslate %}
{{ some_var }}

will not assume that the template is safe, so some_var is a str, so this renders as

Some &gt;b&lt;HTML&gt;/b&lt;
1 Like

Hey @He3lixxx

Yes, this may be telling, and the discussion on the older ticket too.

I was hoping for some guidance from other voices so let’s give this over the weekend to see if there are any, and I’ll take another look next week. Thanks!

I think that He3lixxx’s arguments make sense. We should at least explore a patch and the outcome for tests.

1 Like

Super. Thanks both! I’ve reopened and marked as Accepted.