| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e66691a commit 243280d
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,42 @@ | |||
| 1 | + <!DOCTYPE qhelp PUBLIC | ||
| 2 | + "-//Semmle//qhelp//EN" | ||
| 3 | + "qhelp.dtd"> | ||
| 4 | + <qhelp> | ||
| 5 | + | ||
| 6 | + <overview> | ||
| 7 | + <p> | ||
| 8 | + | ||
| 9 | + Cross-site scripting attacks can occur if untrusted input is not escaped. This applies to templates as well as code. | ||
| 10 | + The <code>jinja2</code> templates may be vulnerable to XSS if the environment has <code>autoescape</code> set to <code>False</code>. | ||
| 11 | + Unfortunately, <code>jinja2</code> sets <code>autoescape</code> to <code>False</code> by default. | ||
| 12 | + Explicitly setting <code>autoescape</code> to <code>True</code> when creating an <code>Environment</code> object will prevent this. | ||
| 13 | + </p> | ||
| 14 | + </overview> | ||
| 15 | + | ||
| 16 | + <recommendation> | ||
| 17 | + <p> | ||
| 18 | + Avoid setting jinja2 autoescape to False. | ||
| 19 | + Jinja2 provides the function <code>select_autoescape</code> to make sure that the correct auto-escaping is chosen. | ||
| 20 | + For example, it can be used when creating an environment <code>Environment(autoescape=select_autoescape(['html', 'xml'])</code> | ||
| 21 | + </p> | ||
| 22 | + </recommendation> | ||
| 23 | + | ||
| 24 | + <example> | ||
| 25 | + <p> | ||
| 26 | + The following example is a minimal flask app which shows a safe and unsafe way to render the given name back to the page. | ||
| 27 | + The first view is unsafe as <code>first_name</code> is not escaped, leaving the page vulnerable to cross-site scripting attacks. | ||
| 28 | + The second view is safe as <code>first_name</code> is escaped, so it is not vulnerable to cross-site scripting attacks. | ||
| 29 | + </p> | ||
| 30 | + <sample src="examples/jinja2.py" /> | ||
| 31 | + </example> | ||
| 32 | + | ||
| 33 | + <references> | ||
| 34 | + <li> | ||
| 35 | + http://jinja.pocoo.org/docs/2.10/api/ | ||
| 36 | + Jinja2: <a href="http://jinja.pocoo.org/docs/2.10/api/">API</a>. | ||
| 37 | + </li> | ||
| 38 | + <li> | ||
| 39 | + Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>. | ||
| 40 | + </li> | ||
| 41 | + </references> | ||
| 42 | + </qhelp> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,56 @@ | |||
| 1 | + /** | ||
| 2 | + * @name Jinja2 templating with autoescape=False | ||
| 3 | + * @description Using jinja2 templates with autoescape=False can | ||
| 4 | + * cause a cross-site scripting vulnerability. | ||
| 5 | + * @kind problem | ||
| 6 | + * @problem.severity error | ||
| 7 | + * @precision medium | ||
| 8 | + * @id py/jinja2/autoescape-false | ||
| 9 | + * @tags security | ||
| 10 | + * external/cwe/cwe-079 | ||
| 11 | + */ | ||
| 12 | + | ||
| 13 | + import python | ||
| 14 | + | ||
| 15 | + predicate jinja2Environment(Object callable, int autoescape) { | ||
| 16 | + exists(ModuleObject jinja2 | | ||
| 17 | + jinja2.getName() = "jinja2" | | ||
| 18 | + jinja2.getAttribute("Environment") = callable and | ||
| 19 | + callable.(ClassObject).getPyClass().getInitMethod().getArg(autoescape+1).asName().getId() = "autoescape" | ||
| 20 | + or | ||
| 21 | + exists(ModuleObject environment | | ||
| 22 | + environment.getAttribute("Template") = callable and | ||
| 23 | + callable.(ClassObject).lookupAttribute("__new__").(FunctionObject).getFunction().getArg(autoescape+1).asName().getId() = "autoescape" | ||
| 24 | + ) | ||
| 25 | + ) | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + ControlFlowNode getAutoEscapeParameter(CallNode call) { | ||
| 29 | + exists(Object callable | | ||
| 30 | + call.getFunction().refersTo(callable) | | ||
| 31 | + jinja2Environment(callable, _) and | ||
| 32 | + result = call.getArgByName("autoescape") | ||
| 33 | + or | ||
| 34 | + exists(int arg | | ||
| 35 | + jinja2Environment(callable, arg) and | ||
| 36 | + result = call.getArg(arg) | ||
| 37 | + ) | ||
| 38 | + ) | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + from CallNode call | ||
| 42 | + where | ||
| 43 | + not exists(call.getNode().getStarargs()) and | ||
| 44 | + not exists(call.getNode().getKwargs()) and | ||
| 45 | + ( | ||
| 46 | + not exists(getAutoEscapeParameter(call)) and | ||
| 47 | + exists(Object env | | ||
| 48 | + call.getFunction().refersTo(env) and | ||
| 49 | + jinja2Environment(env, _) | ||
| 50 | + ) | ||
| 51 | + or | ||
| 52 | + exists(Object isFalse | | ||
| 53 | + getAutoEscapeParameter(call).refersTo(isFalse) and isFalse.booleanValue() = false | ||
| 54 | + ) | ||
| 55 | + ) | ||
| 56 | + select call, "Using jinja2 templates with autoescape=False can potentially allow XSS attacks." | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,27 @@ | |||
| 1 | + from flask import Flask, request, make_response, escape | ||
| 2 | + from jinja2 import Environment, select_autoescape, FileSystemLoader | ||
| 3 | + | ||
| 4 | + app = Flask(__name__) | ||
| 5 | + loader = FileSystemLoader( searchpath="templates/" ) | ||
| 6 | + | ||
| 7 | + unsafe_env = Environment(loader=loader) | ||
| 8 | + safe1_env = Environment(loader=loader, autoescape=True) | ||
| 9 | + safe2_env = Environment(loader=loader, autoescape=select_autoescape()) | ||
| 10 | + | ||
| 11 | + def render_response_from_env(env): | ||
| 12 | + name = request.args.get('name', '') | ||
| 13 | + template = env.get_template('template.html') | ||
| 14 | + return make_response(template.render(name=name)) | ||
| 15 | + | ||
| 16 | + @app.route('/unsafe') | ||
| 17 | + def unsafe(): | ||
| 18 | + return render_response_from_env(unsafe_env) | ||
| 19 | + | ||
| 20 | + @app.route('/safe1') | ||
| 21 | + def safe1(): | ||
| 22 | + return render_response_from_env(safe1_env) | ||
| 23 | + | ||
| 24 | + @app.route('/safe2') | ||
| 25 | + def safe2(): | ||
| 26 | + return render_response_from_env(safe2_env) | ||
| 27 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + | jinja2_escaping.py:9:14:9:39 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. | | ||
| 2 | + | jinja2_escaping.py:41:5:41:29 | ControlFlowNode for Environment() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. | | ||
| 3 | + | jinja2_escaping.py:43:1:43:3 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. | | ||
| 4 | + | jinja2_escaping.py:44:1:44:15 | ControlFlowNode for E() | Using jinja2 templates with autoescape=False can potentially allow XSS attacks. | | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Security/CWE-079/Jinja2WithoutEscaping.ql | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,8 @@ | |||
| 1 | 1 | edges | |
| 2 | 2 | | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | | |
| 3 | 3 | | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | | |
| 4 | + | jinja2_escaping.py:14:12:14:23 | dict of externally controlled string | jinja2_escaping.py:14:12:14:39 | externally controlled string | | ||
| 5 | + | jinja2_escaping.py:14:12:14:39 | externally controlled string | jinja2_escaping.py:16:47:16:50 | externally controlled string | | ||
| 4 | 6 | | reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string | | |
| 5 | 7 | | reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string | | |
| 6 | 8 | | reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,51 @@ | |||
| 1 | + | ||
| 2 | + Environment(loader=templateLoader, autoescape=fake_func()) | ||
| 3 | + from flask import Flask, request, make_response, escape | ||
| 4 | + from jinja2 import Environment, select_autoescape, FileSystemLoader | ||
| 5 | + | ||
| 6 | + app = Flask(__name__) | ||
| 7 | + loader = FileSystemLoader( searchpath="templates/" ) | ||
| 8 | + | ||
| 9 | + unsafe_env = Environment(loader=loader) | ||
| 10 | + safe1_env = Environment(loader=loader, autoescape=True) | ||
| 11 | + safe2_env = Environment(loader=loader, autoescape=select_autoescape()) | ||
| 12 | + | ||
| 13 | + def render_response_from_env(env): | ||
| 14 | + name = request.args.get('name', '') | ||
| 15 | + template = env.get_template('template.html') | ||
| 16 | + return make_response(template.render(name=name)) | ||
| 17 | + | ||
| 18 | + @app.route('/unsafe') | ||
| 19 | + def unsafe(): | ||
| 20 | + return render_response_from_env(unsafe_env) | ||
| 21 | + | ||
| 22 | + @app.route('/safe1') | ||
| 23 | + def safe1(): | ||
| 24 | + return render_response_from_env(safe1_env) | ||
| 25 | + | ||
| 26 | + @app.route('/safe2') | ||
| 27 | + def safe2(): | ||
| 28 | + return render_response_from_env(safe2_env) | ||
| 29 | + | ||
| 30 | + # Explicit autoescape | ||
| 31 | + | ||
| 32 | + e = Environment( | ||
| 33 | + loader=loader, | ||
| 34 | + autoescape=select_autoescape(['html', 'htm', 'xml']) | ||
| 35 | + ) # GOOD | ||
| 36 | + | ||
| 37 | + # Additional checks with flow. | ||
| 38 | + auto = select_autoescape | ||
| 39 | + e = Environment(autoescape=auto) # GOOD | ||
| 40 | + z = 0 | ||
| 41 | + e = Environment(autoescape=z) # BAD | ||
| 42 | + E = Environment | ||
| 43 | + E() # BAD | ||
| 44 | + E(autoescape=z) # BAD | ||
| 45 | + E(autoescape=auto) # GOOD | ||
| 46 | + E(autoescape=0+1) # GOOD | ||
| 47 | + | ||
| 48 | + def checked(cond=False): | ||
| 49 | + if cond: | ||
| 50 | + e = Environment(autoescape=cond) # GOOD | ||
| 51 | + | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments