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 коммит произвёл Mark Shannon
Родитель e66691a90c
Коммит 243280dc00
7 изменённых файлов: 183 добавлений и 0 удалений

Просмотреть файл

@ -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>

Просмотреть файл

@ -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."

Просмотреть файл

@ -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)

Просмотреть файл

@ -0,0 +1,4 @@
| jinja2_escaping.py:9:14:9:39 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
| jinja2_escaping.py:41:5:41:29 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
| jinja2_escaping.py:43:1:43:3 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |
| jinja2_escaping.py:44:1:44:15 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. |

Просмотреть файл

@ -0,0 +1 @@
Security/CWE-079/Jinja2WithoutEscaping.ql

Просмотреть файл

@ -1,6 +1,8 @@
edges
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string |
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string |
| jinja2_escaping.py:14:12:14:23 | dict of externally controlled string | jinja2_escaping.py:14:12:14:39 | externally controlled string |
| jinja2_escaping.py:14:12:14:39 | externally controlled string | jinja2_escaping.py:16:47:16:50 | externally controlled string |
| reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string |
| reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string |
| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string |

Просмотреть файл

@ -0,0 +1,51 @@
Environment(loader=templateLoader, autoescape=fake_func())
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)
# Explicit autoescape
e = Environment(
loader=loader,
autoescape=select_autoescape(['html', 'htm', 'xml'])
) # GOOD
# Additional checks with flow.
auto = select_autoescape
e = Environment(autoescape=auto) # GOOD
z = 0
e = Environment(autoescape=z) # BAD
E = Environment
E() # BAD
E(autoescape=z) # BAD
E(autoescape=auto) # GOOD
E(autoescape=0+1) # GOOD
def checked(cond=False):
if cond:
e = Environment(autoescape=cond) # GOOD