Repository navigation
feat: carry render context data to the template filters it reaches - #174
Merged
Merged
Conversation
A context value carrying attacker input reaches the filter a template applies when the context is a dict literal with constant keys that the render call alone uses: through for, with, the templates it includes or extends, earlier filter arguments and filter blocks, and through a project function. Anything else leaves the filter reachable.
A filter in a block the extending template overrides, or outside the blocks of an extending template, does not render, so the context reaching it does not make it exploitable.
A render call whose context is a dict literal with constant keys, used by that call alone, makes the filter calls the rendered template applies, each fed from the context entries reaching its value and its argument. The templates are read for their scopes: for, with and the as of other tags bind names until their block ends, constant includes map names through their bindings or pass none with only, and a template extending another renders only its blocks, which replace the blocks of that name. A name found in several files gives no filter calls. The taint engine reports flows into those filters at the render call, naming the template, and function summaries record them, so a project function rendering what it receives carries it to the filters. The template calls are provided to every module, including those analysed in other processes, and are part of the cache key.
With django.template.context_processors.request certainly enabled in every Django engine the settings configure, a render passing the request with a certain context gives the template the request, whose user-controlled text attributes then reach its filters. Anything uncertain leaves the filter reachable.
When every Django template engine the settings assign to TEMPLATES, as literals no other code names, lists django.template.context_processors.request, a render call passing the request with a certain context gives the template the request. Template variables now keep their first attribute, so a filter reading request.GET, directly or through for, with and includes, is fed from the text attributes the request object model lists; user, session and anything shadowed by a context entry, a tag binding or include only are not. Through a project function, only the context entries are followed.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #146. This covers the second item: attacker input in the render context reaching a filter makes the entry point exploitable, not only reachable. The first and third items came with #173.
Summary
Render context. A filter is exploitable at the render call only where the engine is certain of the value that reaches it. The context must be a dict literal with constant keys that the render call alone uses, whether written in the call or passed through a variable. A mutated dict, one passed elsewhere,
**extra,dict(...), or any other context leaves the filter reachable.Template scopes. A filter's value is followed from a context variable directly, or through
{% for %},{% with %}, theasof other tags, constant{% include %}(with itswithbindings) and{% extends %}, the arguments of earlier filters, and the output of{% filter %}blocks. A name a tag binds shadows the context.{% include ... only %}passes none of it.Blocks. A template extending another renders only its blocks, which replace the blocks of that name in the parent. Overridden parent blocks and content outside the child's blocks are therefore not counted.
Duplicate names. A template name found in several files gives no flows, since the loaders decide which one renders.
Filter arguments. A filter's value is its first argument and the filter's own argument (
cut:bio) its second.attacker_argumentsrestricted to the value is therefore not exploitable through the argument.Project functions. A function rendering the values it receives carries them to the filters: function summaries record the filter calls.
Request context processor.
{{ request.GET.q|striptags }}counts only when all of these hold:DjangoTemplatesengine ofTEMPLATESlistsdjango.template.context_processors.requestin literals, checked on the HIR, and no other code namesTEMPLATES;render,TemplateResponse,render_to_stringgivenrequest);request;GET,POST,COOKIES,headers…), notuserorsession.Through a project function, only the context entries are followed.
Engine wiring.
TemplateCalls(interprocedural layer) is provided to every module, including those analysed in other processes, and is part of the cache key.TemplateRendergainscontextandrequestpositions.|name.Limits
{{ block.super }}is not followed.Verification
test_template_context_flows.py: 22, then 3 failures.test_template_request_processor.py: 12 failures. Each failed for the expected reason: no exploitable finding.ruff checkandmypypass.**, duplicate files,only, scope ends, block overrides, argument position, summaries, cache key, parallel batch,TEMPLATESproof, processor flag, user text, attribute through bindings, absent context) are all killed. Two surviving mutants showed redundant guards, which were removed.