Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Promote Template Injection query from experimental #17922

Merged

Conversation

joefarebrother
Copy link
Contributor

@joefarebrother joefarebrother commented Nov 6, 2024

Promotes the Template Injection query, originally submitted to the experimental query pack here.

Fixes a modeled Jinja2 sink to properly refer to Environment().from_string(); and promotes sinks from the other template frameworks included in the experimental version.
Excludes the Cheetah package as its documentation links are dead and its last update was 15 years ago.

@github-actions github-actions bot added the Python label Nov 6, 2024
@joefarebrother joefarebrother force-pushed the python-promote-template-injection branch from b487f4c to 2d04af8 Compare November 20, 2024 12:00
@joefarebrother joefarebrother force-pushed the python-promote-template-injection branch 3 times, most recently from 9377747 to 66e173c Compare November 21, 2024 17:16
Copy link
Contributor

github-actions bot commented Nov 21, 2024

QHelp previews:

python/ql/src/Security/CWE-074/TemplateInjection.qhelp

Server Side Template Injection

A template from a server templating engine such as Jinja constructed from user input can allow the user to execute arbitrary code using certain template features. It can also allow for cross-site scripting.

Recommendation

Ensure that an untrusted value is not used to directly construct a template. Jinja also provides SandboxedEnvironment that prohibits access to unsafe methods and attributes. This can be used if constructing a template from user input is absolutely necessary.

Example

In the following case, template is used to generate a Jinja2 template string. This can lead to remote code execution.

from django.urls import path
from django.http import HttpResponse
from jinja2 import Template, escape


def a(request):
    template = request.GET['template']

    # BAD: Template is constructed from user input. 
    t = Template(template)

    name = request.GET['name']
    html = t.render(name=escape(name))
    return HttpResponse(html)


urlpatterns = [
    path('a', a),
]

The following is an example of a string that could be used to cause remote code execution when interpreted as a template:

{% for s in ().__class__.__base__.__subclasses__() %}{% if "warning" in s.__name__ %}{{s()._module.__builtins__['__import__']('os').system('cat /etc/passwd') }}{% endif %}{% endfor %}

In the following case, user input is not used to construct the template. Instead, it is only used as the parameters to render the template, which is safe.

from django.urls import path
from django.http import HttpResponse
from jinja2 import Template, escape


def a(request):
    # GOOD: Template is a constant, not constructed from user input
    t = Template("Hello, {{name}}!")

    name = request.GET['name']
    html = t.render(name=escape(name))
    return HttpResponse(html)


urlpatterns = [
    path('a', a),
]

In the following case, a SandboxedEnvironment is used, preventing remote code execution.

from django.urls import path
from django.http import HttpResponse
from jinja2 import escape
from jinja2.sandbox import SandboxedEnvironment


def a(request):
    env = SandboxedEnvironment()
    template = request.GET['template']

    # GOOD: A sandboxed environment is used to construct the template. 
    t = env.from_string(template)

    name = request.GET['name']
    html = t.render(name=escape(name))
    return HttpResponse(html)


urlpatterns = [
    path('a', a),
]

References

@joefarebrother joefarebrother marked this pull request as ready for review November 21, 2024 17:26
@joefarebrother joefarebrother requested a review from a team as a code owner November 21, 2024 17:26
@joefarebrother joefarebrother force-pushed the python-promote-template-injection branch from f6ef223 to 190afe6 Compare November 21, 2024 17:44
Copy link
Contributor

@tausbn tausbn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments and suggestions here and there, but otherwise this looks good to me! 👍

Comment on lines 36 to 45
/** Gets a reference to an instance of `jinja2.Environment`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result = EnvironmentClass::classRef().getACall()
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}

/** Gets a reference to an instance of `jinja2.Environment`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why we need to use type tracking here. It looks to me like classRef().getAnInstance() would do the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.
In what sort of situations do we want to use type tracking as opposed to API graphs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I would suggest using API graphs wherever possible, only using type tracking when API graphs are not sufficient. One example of where we do this is in the modelling of pathlib, where we want to observe that the / operator returns a Path object. Since the API graph (at present) doesn't have any way to reference the step from a to a / b (say) because the latter doesn't have a node in the API graph, we have to use type tracking instead.

In the glorious future when we get API graphs à la Ruby, we should be able to do this without using type trackers. (Well, apart from the two very general type(back)trackers that the API graph calculation is built upon.)

private class Jinja2FromStringConstruction extends TemplateConstruction::Range,
DataFlow::MethodCallNode
{
Jinja2FromStringConstruction() { this.calls(EnvironmentClass::instance(), "from_string") }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also seems like something we could push into the API graph layer, to make it slightly more general.

python/ql/src/Security/CWE-074/TemplateInjection.ql Outdated Show resolved Hide resolved
python/ql/src/Security/CWE-074/TemplateInjection.qhelp Outdated Show resolved Hide resolved
python/ql/src/Security/CWE-074/TemplateInjection.qhelp Outdated Show resolved Hide resolved
@tausbn tausbn self-requested a review December 3, 2024 13:44
tausbn
tausbn previously approved these changes Dec 3, 2024
Copy link
Contributor

@tausbn tausbn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes. This looks good to me! 👍

@joefarebrother
Copy link
Contributor Author

Do we need a separate docs review from the docs team, or ok to merge as is?

@joefarebrother joefarebrother added the ready-for-doc-review This PR requires and is ready for review from the GitHub docs team. label Dec 4, 2024
@myarb
Copy link

myarb commented Dec 4, 2024

👋🏼 @joefarebrother I'm not entirely sure if this requires a separate Docs review, so I've asked in #code-security-docs in Slack. cc @felicitymay or @saritai

@myarb
Copy link

myarb commented Dec 5, 2024

👋🏼 @joefarebrother I've confirmed with Felicity that this needs a docs review. For the reasoning, I'll share her Slack message:

The user-facing content in an experimental query is not reviewed by the docs team before it's merged, so when an experimental query is promoted into the standard suite it always needs a docs review.

I've added this to the Docs Content review board for a writer to review.

subatoi
subatoi previously approved these changes Dec 6, 2024
Copy link
Contributor

@subatoi subatoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Just some very minor suggestions—thank you

python/ql/src/Security/CWE-074/TemplateInjection.qhelp Outdated Show resolved Hide resolved
python/ql/src/Security/CWE-074/TemplateInjection.qhelp Outdated Show resolved Hide resolved
@joefarebrother joefarebrother dismissed stale reviews from subatoi and tausbn via 8a5e55b December 9, 2024 10:22
subatoi
subatoi previously approved these changes Dec 9, 2024
Copy link
Contributor

@subatoi subatoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reapproving in case it saves you time—thank you!

@joefarebrother
Copy link
Contributor Author

Updated test outputs - just needs re-approval if tests now pass

@joefarebrother joefarebrother merged commit c41c2ad into github:main Dec 10, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Python ready-for-doc-review This PR requires and is ready for review from the GitHub docs team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants