Problem/Motivation
We still have ~8 theme functions in core.
Potentially modules doing a minimal port from 7.x-8.x will have them too.
At least one core contributor in a sprint discussion yesterday had either not realized or had forgotten that no autoescaping happens in theme functions.
If they can, so can other people, so we could end up with XSS as people remove early-escape from places then forget it ends up in a theme function.
Non-test remaining theme functions in core:
- theme_indentation
- theme_field_ui_table
- theme_language_negotiation_configure_browser_form_table
- theme_language_content_settings_table
- theme_system_modules_details
- theme_system_modules_uninstall
- theme_views_view_fields
- theme_views_view_field
Proposed resolution
1. Document this clearly for theme functions.
2. Add a helper function that theme functions can use to get the same escaping behavior as Twig templates
3. Review existing theme functions in core to double check things depending on the above.
Unfortunately it is not possible to auto-escape $variables for themes without making assumptions we can not make. $variables is not just a flat array for keys and scalar values. The values can be render arrays and objects. We cannot go into the render arrays and escape early as this would break rendering. And we can not change properties of objects cause that is just wrong. See #89. Therefore we need to do it the same way as Twig does it - which is wrap every output into the template with a method. Twig can do this automatically - because it has a compilation step - theme functions can not because, well, they are PHP.
Proposed resolution
Add a helper method to help people that still use theme functions, to escape there output, if its already safe.
Remaining tasks
User interface changes
API changes
Data model changes
| Comment | File | Size | Author |
|---|---|---|---|
| #106 | interdiff.txt | 3.04 KB | David_Rothstein |
| #106 | 2572929-106.patch | 9.8 KB | David_Rothstein |
| #102 | interdiff.txt | 676 bytes | dawehner |
| #102 | 2572929-102.patch | 9.77 KB | dawehner |
| #97 | increment.txt | 518 bytes | pwolanin |
Comments
Comment #2
stefan.r commentedWhat would be the drawback of automatically admin XSS filtering unsafe output? (other than the performance hit and disallowing people from intentionally putting javascript in a theme_* function)
Comment #3
chx commentedThe way to solve this issue is to list all the remaining theme_ functions and figure out what are they doing and whether they would suffer from an xss filtering. Then possibly whitelist those and throw an exception for any other to stop worrying. Possibly.
Comment #4
dawehnerWell of course we could deprecate theme functions now, but the question is, is the effort we need to make it secure by default not worth the effort of potential break already ported contrib modules? Maybe its the right thing to break them, as they maybe are unsafe.
From a release management perspective I'd suggest: Apply Xss::filterAdmin, open (probably reopen) the conversions as major and then reevaluate afterwards again if its okay to remove the functionality . On top of Xss::filterAdmin() we should ideally mark the functionality as deprecated already, if possible.
Comment #5
stefan.r commented@dawehner sorry to continue the off-topic discussion, but shouldn't we deprecate them anyway? The question is just whether for Drupal 8.0.0 or 9.0.0.
If for 8, we could just whitelist core theme functions while we try to get rid of them post-8.0.0. Which may break some existing Drupal 8 sites and some ported modules that use theme functions, and make the effort of porting D7 to D8 potentially slightly larger (though it's large anyway compared to D4->D5->D6->D7). But if we do this for 9, I do wonder what people might end up doing in contrib theme functions, and it's yet another (insecure) feature that would have to be supported all throughout the 8.x cycle, as we can't just to deprecate them for a minor release.
Do we have an existing issue about theme function deprecation/removal where we can grep through all of contrib and see how prevalent its use is anyway?
Comment #6
catchThis is where I'm leading towards, then if you really are doing something that gets broken and you actually need to do, SafeString is there.
Then marking deprecated (in another issue) will discourage people from using them at all, although we have core conversions to get through still of course.
Comment #7
star-szrI am working on #2573231: Add a helper method to help theme functions to esacpe if the output is not safe yet..
Comment #8
dawehnerLet's do that! It is secure, it encourages people to do it right. Let's make the deprecation later.
Comment #11
pwolanin commentedPer discussion with @alexpott, changing the title. We need to add something the mimics Twig autoescape for use in theme functions (or even tpl.php files):
see: \Drupal\Core\Template\TwigExtension::escapeFilter()
Comment #12
pwolanin commentedHere's a start on that as a patch.
Needs tests and the actual doc changes and possibly applying this to any theme fucntion that needs it
Comment #13
pwolanin commentedclosed #2573231: Add a helper method to help theme functions to esacpe if the output is not safe yet. as duplicate
Comment #14
dawehnerI agree, we want to make people it somehow possible write theme functions in a similar way than twig templates.
Comment #15
pwolanin commentedStarting on a test, but it's failing and need to debug.
Comment #16
dawehnerLooking at that for a while.
Comment #17
star-szrJust want to mention this is also relevant for PHPTemplate since it's still hanging out in a dark corner of core.
Comment #18
dawehnerUsually things work if they are done right.
Comment #21
lauriiiI think we could be more explicit what the theme_autoescape actually does because it autoescapes the render array and returns the rendered output.
@see Drupal\Core\Template\TwigExtension\escapeFilter . This not really DRY so it might be worth moving this into some place where it affects both of the places where this logic occurs.
Nit: special fits the previous line
Unnecessary
No empty modules property is needed
Nit: Double line change
Comment #22
pwolanin commentedSo we need to add more test cases and docs to theme()
Comment #23
stefan.r commentedComment #24
chx commentedWhat about an
assert(in_array($theme_function, $core_whitelist))won't break production but will force developers to nuke 'em.Comment #25
mikeker commentedAny reason we can't do this to address #21.2?
(Sorry, left to eat dinner and found @stefan.r had posted a patch, thus the double interdiff...)
Comment #28
catch@chx we should open a followup issue for that, but for me core needs to be converted first before we start enforcing anything, so no whitelist.
Comment #29
lauriiiTalked briefly with @dawehner about the solution taken on #25 and I think we both agreed that its not optimal but since we are anyway adding Twig autoescape functionality for legacy system it might make sense to have it the way it is done there.
What are we gonna do to support escaping in PHP templates?
Comment #30
chx commented> @chx we should open a followup issue for that, but for me core needs to be converted first before we start enforcing anything, so no whitelist.
Why. The community had almost three years to convert the massive amounts of templates and theme functions. We got this far. That's amazing. Tough luck we couldn't finish it all. We want to release ASAP not wait on these. Contrib or custom won't face anything even remotely as bad as core. Some of those theme functions have seen no change since 2004-2005. Enforcing them to convert is fine.
> What are we gonna do to support escaping in PHP templates?
May I recommend a big fat serving of nothing? Seriously: just use Twig. It's not in core, not my circus, not my monkeys.
Comment #31
chx commentedSee the top of #1825952: Turn on twig autoescape by default
> a PHPTemplate engine in contrib is going to have to add back a way to securely format variables
It was never meant to be otherwise.
Comment #32
wim leers@dawehner walked me through this, explained the problem, and asked for my opinion.
theme_escape_and_render(), nottheme_autoescape(). IMO the latter is nonsensical, because the fact that you have to call that function in the first place means it's manual, not automatic.theme_escape_and_render()should not duplicate logic, but instead call the Twig extension'sescapeFilter()method. Then we have full symmetry and no duplication: variables for templates and in theme functions then get the exact same treatment. Less to understand, less to maintain.If we choose to not call the Twig extension's
escapeFilter()method fromtheme_escape_and_render(), then I think it's absolutely required that it very explicitly, very visibly state that the code must be kept in sync withescapeFilter().Comment #33
lauriiiPhp template engine is still in Drupal core. See: https://api.drupal.org/api/drupal/core!themes!engines!phptemplate!phptem...
Comment #34
wim leersNow I read the entire issue.
#4
+1
In #11, @pwolanin changed the course of the issue significantly after a discussion with @alexpott that isn't recorded on the issue. I think because we need to provide this in the mean time, until we deprecate theme functions altogether? But then will we be able to also deprecate the function we add here? I guess?
#17:
:O Where?
#28 + #30:
I like @chx's thinking here. Why can't we do that?
Comment #35
stefan.r commented@Wim Leers why is this being realistic? They're insecure, and elsewhere in core we "use" plenty of things without "supporting" them by marking them @internal. I don't understand why can't do so for theme functions - just because we might use some legacy theme functions doesnt mean they need to be part of the API.
Can't we somehow move this handful of legacy functions into its own thing so we can add an assert() that doesn't involve whitelisting? Or cripple the
Comment #36
chx commentedURGH if phptemplate is still there then nuke it. Kill it with fire. Or move it to contrib. Whatever you do, don't ship with it!!!! URGH. Modules can't ship with phptemplate / .tpl.php files anyways! Thanks god. ThemeManager::render hardwires twig_render_template and .html.twig for modules otherwise ppl would pester contrib maintainers for templates in the engine they use. Urgh nope. I didn't add that but it's really wise. We nuked config.storage.install saying everyone will ship YAML and that be the end of it. Same sane reasoning. Anyways, phptemplate is definitely out of scope and we don't have the time to write a new layer for making phptemplate secure. If that's a doable thing. Just swipe it under the rug and forget about it.
Comment #37
lauriiiWouldn't it be enough to write docs that phptemplates don't have autoescaping and shouldn't be used for that reason? Modules can't change templating engine because otherwise themers would be very confused when they would have to create their own templates for their module.
Comment #38
dawehnerComment #39
dawehnerPeople: The title of this issue is
,
PHPTemplate is a different thing: Added a new issue: #2574717: Remove PHPTemplate, and add test coverage for multiple theme engine support
Comment #40
alexpottComment #41
wim leersIdeally, we get rid of theme functions. But that's a nice-to-have at this point in the cycle. We can't have auto-escape in theme functions. We shouldn't use theme functions. But theme functions in D7 had to take care of escaping themselves, and if they choose to upgrade to D8 and keep those instead of upgrading to Twig, then they still have to do the escaping manually. The helper function we add here ensures that it is done in a way that is consistent with how Twig does it.
@dawehner and @stefan.r firmly believe it is better to have this be entirely decoupled from Twig rather than calling Twig's escaping logic.
I think the only thing missing, is the actual documentation part. Let's add some documentation to
theme.api.php? Do we need a change record?Comment #42
wim leersComment #43
dawehnerWorking on the documentation bit
Comment #44
dawehnerComment #46
heddnNit: spelling of functionality.
Comment #47
xjmComment #48
xjmI think this can just be "Automatically escapes variables for theme functions."
Minor: spelling, line wrapping, remove the word "You" from the beginning.
So I understand the desire not to couple the theme API to Twig, but this looks an awful lot like duplicated code. How do we ensure this and the Twig escape filter don't diverge? Could they both call a method from some other independent component?
Also, I wonder if we should deprecate this function from the outset, with the goal of removing the need for this BC layer in 9.0.x? Or maybe that's part of a separate issue to discuss if we want to deprecate theme functions generally?
I think we can probably make this a little more concise e.g. removing the words "It is important to know that" or starting a new paragraph. I'll apply the patch and see if I can suggest an improvement in context; just making a note for now.
This hunk should probably go in #2574717: Remove PHPTemplate, and add test coverage for multiple theme engine support.
I think this hunk needs discussion but is in scope for #2574717: Remove PHPTemplate, and add test coverage for multiple theme engine support in any case rather than this issue.
Comment #49
dawehnerWorking on it
Comment #50
dawehner5 and 6 was accidental.
This is an incremental interdiff due to people throwing us out here.
Comment #51
pwolanin commentedlooks great
Comment #52
xjmWe are still finishing off a few things in the patch; see above reviews. :)
I created a followup that we can reference for the code duplication: #2575065: Refactor theme_escape_and_render() and TwigExtension::escapeFilter() to share reused code
I'll update the patch some more.
Comment #53
xjmAlso filed #2575081: [policy, no patch] Use E_USER_DEPRECATED in Drupal 8 minor releases.
Comment #55
xjmIs this hunk just one that we already discovered was unsafe? Do we need to check the rest in a subsequent issue? Ideally of course we would complete the children of #2348381: [META-0 theme functions left] Convert/refactor core theme functions instead.
I added back the accidentally removed docs in
theme.api.phpand tweaked them a bit. I thought it was important to mention the need for escaping both times theme functions were mentioned, but these doc updates could probably use a double-check.Comment #56
xjmTypo: "autescape"
Comment #57
lauriiiOne nit which can be fixed on commit:
s/autescape/auto-escape
Comment #58
mikeker commentedI believe we should avoid the use of "auto" anything for this function as it's not automatically doing it. I'll post a patch updating the docs shortly.
Can someone explain why the approach in #25 was dismissed? From #29
And #32.3
Then in #41
I believe a lot of these conversations happened on IRC or in-person at the Barcelona sprint. Can someone give those of us following along at home the gist of it? Thanks!
Comment #59
mikeker commentedCopy/paste leftovers?
Comment #61
mikeker commentedAddresses points raised in #56/#57 and #59 along with a few doc cleanups that jumped out at me.
Hopefully these are the final nits to be picked!
Comment #64
mikeker commentedDang it...
Comment #69
xjmComment #70
xjmAside from fixing the test, this looks good to me too.
Could be fixed on commit: function name needs parens.
Also, just so it's documented here, @mikeker and I chatted a bit about #58. I suggested it was primarily for code decoupling, and that we can improve it in #2575065: Refactor theme_escape_and_render() and TwigExtension::escapeFilter() to share reused code.
Comment #71
xjmOh, in addition to renaming the file, we should also fix this too. :)
Comment #72
xjmFixing those things.
Comment #73
xjmOh, my question in #55 is still outstanding. Do we have any potential XSS vulnerabilities in core theme functions other than that function? Doesn't need to block this issue, but it's probably important to do.
I'd even suggest documenting on every theme function in core TBH. But especially an inline comment in the ones that actually need the escaping function, since people will copy them to do stuff.
Comment #74
dawehnerI haven't checked other theme functions, but this one seemed just too obvious.
Good catch!
Comment #75
hussainwebI made this change first in #2574717: Remove PHPTemplate, and add test coverage for multiple theme engine support and later found out that this is that actual patch where the function is being introduced. I am repeating the same change here.
Comment #76
pwolanin commentedCan we have the new function in one place not both? or mark this issue duplicate and closed?
Comment #77
dawehner@peter don't make it harder than it is, seriously.
Comment #78
hussainweb@pwolanin: I guess the idea is to keep the issues of documenting the escape and adding a helper separate from removing phptemplate from core (so that there's a git history). I imagine this issue must go in first, after which the patch in the other issue would have to be rerolled.
Comment #79
dawehner... Note: This is coming directly from twig, so we should NOT change here.
Comment #80
dawehnerReverted that particular thing back, given that it should be more in parallel.
Comment #81
stefan.r commentedLooks great
Comment #82
lauriiiRTBC++
Comment #83
David_Rothstein commentedI think theme_escape_and_render() is a confusing name, because it doesn't always escape. Also, we know we want to recommend its use outside of theme functions too (see #2528284: Document that alternate Drupal 8 theme engines must implement auto-escape or they are not secure and #2574717: Remove PHPTemplate, and add test coverage for multiple theme engine support)... although I suppose "theme" in the function name can mean "theme system" as much as "theme functions"....
I suggest calling it either render_safe() or theme_render_safe().
Comment #84
David_Rothstein commentedOr render_safe_html()/theme_render_safe_html() (because someone will point out that it's not safe in all contexts)...
Comment #85
alexpottIt does not always render() either. Setting to needs work to address #83/#84.
Wrt to render context i think it was worth documenting that this is for the HTML context - not sure if it worth encoding that in the name since the 99% use-case of theme functions is to generate HTML.
Comment #86
dawehnerWe discussed this for a while here. We want to ensure that this is sort of part of the theme system (so its still usable in theme engines)
but discourage people to use it anywhere else, like in random controllers.
Comment #87
larowlanI think this is asking for trouble - can't the theme system/engine do this for us - i.e. it already knows which theme hooks are functions and which use a template - can't it call theme_escape_and_render on non templates automatically? We don't want to leave it to chance - it will be easy to forget.
Comment #88
catch@larowlan that would be similar to the earlier idea here of Xss::filterAdmin() on the results of theme functions. I still like that idea but others were less keen.
Comment #89
alexpottI looked into auto-escaping in the ThemeManager...
Unfortunately this is not possible - since we often just reach into
$variablesand get whatever we like. In @larowlan's example above, we render$variables['build']and then reach in and get$variables['build']['#title'].Comment #90
alexpottWe can make this more explicit.
The rendered string, safe for use in HTML. The string is not safe when used as an HTML attribute.I like the new name.
Comment #91
pwolanin commentedok, I'm going to update this now
Comment #92
pwolanin commenteddoc fix plus test fixes to avoid unhelpful exception on test fail plus adding descriptive keys and more test cases in the data provider.
Comment #93
dawehnerThis is lovely!
Comment #94
stefan.r commenteds/The string is not safe when used as an HTML attribute./The string is not safe when used inside an HTML attribute./
Comment #95
xjmComment #96
dawehnerDo we need a CR for this particular issue?
Comment #97
pwolanin commentedtext fix
@dawehner - do we need a CR for api additions usually?
Comment #98
wim leersIt's important that
theme_*()functions use this. So I'd say "yes" in this case.Comment #99
alexpottImproving the comment about why it is impossible to do this prior to calling the theme function.
Comment #100
xjmYep, agreed on the CR.
Comment #101
dawehner/me works on a CR
dawehner--
Comment #102
dawehnerAdded https://www.drupal.org/node/2575445, adapted https://www.drupal.org/node/2296163 a bit and realized that we can now leverage the new function
While talking with @xjm on IRC we realized that we can actually do better and not call
drupal_render()directly.Comment #103
pwolanin commentedgreat. CR looks good.
Comment #104
cilefen commentedComment #105
David_Rothstein commentedThe patch is still referencing the old "theme_escape_and_render" function name in several places.
I was thinking more that it is not safe for use in HTML attributes (which is now documented in the patch). I guess it's hard to really communicate that in a function name, but that's what I was aiming for. I'm not sure "autoescape" is the right name here, but it's definitely better than "escape" and it's better documented now also.
Comment #106
David_Rothstein commentedFixed the outdated references.
Comment #107
wim leersShip it.
Comment #108
alexpottThis is a necessary API addition to make it possible to have theme_* functions that are compatible with Twig autoescaping. Basically we have to do what Twig does manually and as show in #89 we can't do anything automatic.
Committed 03ac04f and pushed to 8.0.x. Thanks!
Comment #111
dawehnerOh testbot
Comment #112
xjmWe can address some of the related documentation work in #2575551: Document the Drupal 8 sanitization API in the API docs and on Drupal.org. I published the CR.
Did anyone file a followup for checking the other 7 core theme functions?
Comment #113
stefan.r commented