зеркало из https://github.com/github/codeql.git
Python: New query to check for use of jinja2 templates without auto-escaping.
This commit is contained in:
Родитель
e66691a90c
Коммит
243280dc00
|
@ -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
|
||||
|
Загрузка…
Ссылка в новой задаче