Python: New query to check for use of jinja2 templates without auto-escaping.

This commit is contained in:
Mark Shannon
2018-11-19 11:50:10 +00:00
committed by Mark Shannon
parent e66691a90c
commit 243280dc00
7 changed files with 183 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Cross-site scripting attacks can occur if untrusted input is not escaped. This applies to templates as well as code.
The <code>jinja2</code> templates may be vulnerable to XSS if the environment has <code>autoescape</code> set to <code>False</code>.
Unfortunately, <code>jinja2</code> sets <code>autoescape</code> to <code>False</code> by default.
Explicitly setting <code>autoescape</code> to <code>True</code> when creating an <code>Environment</code> object will prevent this.
</p>
</overview>
<recommendation>
<p>
Avoid setting jinja2 autoescape to False.
Jinja2 provides the function <code>select_autoescape</code> to make sure that the correct auto-escaping is chosen.
For example, it can be used when creating an environment <code>Environment(autoescape=select_autoescape(['html', 'xml'])</code>
</p>
</recommendation>
<example>
<p>
The following example is a minimal flask app which shows a safe and unsafe way to render the given name back to the page.
The first view is unsafe as <code>first_name</code> is not escaped, leaving the page vulnerable to cross-site scripting attacks.
The second view is safe as <code>first_name</code> is escaped, so it is not vulnerable to cross-site scripting attacks.
</p>
<sample src="examples/jinja2.py" />
</example>
<references>
<li>
http://jinja.pocoo.org/docs/2.10/api/
Jinja2: <a href="http://jinja.pocoo.org/docs/2.10/api/">API</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,56 @@
/**
* @name Jinja2 templating with autoescape=False
* @description Using jinja2 templates with autoescape=False can
* cause a cross-site scripting vulnerability.
* @kind problem
* @problem.severity error
* @precision medium
* @id py/jinja2/autoescape-false
* @tags security
* external/cwe/cwe-079
*/
import python
predicate jinja2Environment(Object callable, int autoescape) {
exists(ModuleObject jinja2 |
jinja2.getName() = "jinja2" |
jinja2.getAttribute("Environment") = callable and
callable.(ClassObject).getPyClass().getInitMethod().getArg(autoescape+1).asName().getId() = "autoescape"
or
exists(ModuleObject environment |
environment.getAttribute("Template") = callable and
callable.(ClassObject).lookupAttribute("__new__").(FunctionObject).getFunction().getArg(autoescape+1).asName().getId() = "autoescape"
)
)
}
ControlFlowNode getAutoEscapeParameter(CallNode call) {
exists(Object callable |
call.getFunction().refersTo(callable) |
jinja2Environment(callable, _) and
result = call.getArgByName("autoescape")
or
exists(int arg |
jinja2Environment(callable, arg) and
result = call.getArg(arg)
)
)
}
from CallNode call
where
not exists(call.getNode().getStarargs()) and
not exists(call.getNode().getKwargs()) and
(
not exists(getAutoEscapeParameter(call)) and
exists(Object env |
call.getFunction().refersTo(env) and
jinja2Environment(env, _)
)
or
exists(Object isFalse |
getAutoEscapeParameter(call).refersTo(isFalse) and isFalse.booleanValue() = false
)
)
select call, "Using jinja2 templates with autoescape=False can potentially allow XSS attacks."

View File

@@ -0,0 +1,27 @@
from flask import Flask, request, make_response, escape
from jinja2 import Environment, select_autoescape, FileSystemLoader
app = Flask(__name__)
loader = FileSystemLoader( searchpath="templates/" )
unsafe_env = Environment(loader=loader)
safe1_env = Environment(loader=loader, autoescape=True)
safe2_env = Environment(loader=loader, autoescape=select_autoescape())
def render_response_from_env(env):
name = request.args.get('name', '')
template = env.get_template('template.html')
return make_response(template.render(name=name))
@app.route('/unsafe')
def unsafe():
return render_response_from_env(unsafe_env)
@app.route('/safe1')
def safe1():
return render_response_from_env(safe1_env)
@app.route('/safe2')
def safe2():
return render_response_from_env(safe2_env)