Skip to content

Commit

Permalink
SONARHTML-183 Rule S5247: Disabling auto-escaping in template engines…
Browse files Browse the repository at this point in the history
… is security-sensitive (#257)
  • Loading branch information
guillaume-dequenne-sonarsource committed Nov 24, 2023
1 parent aee1b2c commit 825531d
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 0 deletions.
24 changes: 24 additions & 0 deletions its/ruling/src/test/resources/expected/Web-S5247.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"project:external_webkit-jb-mr1/Tools/QueueStatusServer/templates/dashboard.html": [
28,
31
],
"project:external_webkit-jb-mr1/Tools/QueueStatusServer/templates/includes/singlequeuestatus.html": [
6,
8
],
"project:external_webkit-jb-mr1/Tools/QueueStatusServer/templates/patch.html": [
9,
9,
15,
16
],
"project:external_webkit-jb-mr1/Tools/QueueStatusServer/templates/queuestatus.html": [
35,
36,
66
],
"project:external_webkit-jb-mr1/Tools/QueueStatusServer/templates/recentstatus.html": [
46
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ protected final void createViolation(Node node, String message) {
node.getEndColumnPosition()));
}

protected final void createViolation(int startLine, int startColumn, int endLine, int endColumn , String message) {
getHtmlSourceCode().addIssue(new PreciseHtmlIssue(ruleKey, startLine, message, startColumn, endLine, endColumn));
}

protected final void createViolation(int line, String message) {
getHtmlSourceCode().addIssue(
new HtmlIssue(ruleKey, line == 0 ? null : line, message)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SonarSource HTML analyzer :: Sonar Plugin
* Copyright (c) 2010-2023 SonarSource SA and Matthijs Galesloot
* sonarqube@googlegroups.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonar.plugins.html.checks.security;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.sonar.check.Rule;
import org.sonar.plugins.html.checks.AbstractPageCheck;
import org.sonar.plugins.html.node.TextNode;

/*
Note: While this rule covers Jinja2/Django templates, the HTML analyzer is not meant to explicitly support these templating engine and will only cover basic cases.
Any more elaborate support of these engines should be done in a dedicated sensor with a dedicated parser.
*/
@Rule(key = "S5247")
public class DisabledAutoescapeCheck extends AbstractPageCheck {

private static final String MESSAGE = "Make sure disabling auto-escaping feature is safe here.";
private static final Pattern pattern = Pattern.compile("\\{%\\s*autoescape\\s+(false|off)\\s*%\\}|\\|safe\\s*\\}\\}");

@Override
public void characters(TextNode textNode) {
Matcher matcher = pattern.matcher(textNode.getCode());
if (!matcher.find()) {
return;
}
var lines = textNode.getCode().lines()
.collect(Collectors.toList());
boolean raisedWithPreciseLocation = false;
for (int i=0; i<lines.size(); i++) {
Matcher lineMatcher = pattern.matcher(lines.get(i));
while (lineMatcher.find()) {
int issueLine = i + textNode.getStartLinePosition();
int startColumn = i != 0 ? lineMatcher.start() : (textNode.getStartColumnPosition() + lineMatcher.start());
int endColumn = i != 0 ? lineMatcher.end() : (textNode.getStartColumnPosition() + lineMatcher.end());
createViolation(issueLine, startColumn, issueLine, endColumn, MESSAGE);
raisedWithPreciseLocation = true;
}
}
if (!raisedWithPreciseLocation) {
// If no precise location can be found for the issue (the node only contains violations spanning multiple lines), the issue is raised on the whole text node
createViolation(textNode, MESSAGE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.sonar.plugins.html.checks.dependencies.LibraryDependencyCheck;
import org.sonar.plugins.html.checks.header.HeaderCheck;
import org.sonar.plugins.html.checks.header.MultiplePageDirectivesCheck;
import org.sonar.plugins.html.checks.security.DisabledAutoescapeCheck;
import org.sonar.plugins.html.checks.scripting.JspScriptletCheck;
import org.sonar.plugins.html.checks.scripting.LongJavaScriptCheck;
import org.sonar.plugins.html.checks.scripting.NestedJavaScriptCheck;
Expand Down Expand Up @@ -139,6 +140,7 @@ public final class CheckClasses {
DoctypePresenceCheck.class,
TableHeaderHasIdOrScopeCheck.class,
InputWithoutLabelCheck.class,
DisabledAutoescapeCheck.class,
ImgWithoutWidthOrHeightCheck.class,
PageWithoutFaviconCheck.class,
TodoCommentCheck.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<p>To reduce the risk of cross-site scripting attacks, templating systems, such as <code>Twig</code>, <code>Django</code>, <code>Smarty</code>,
<code>Groovy's template engine</code>, allow configuration of automatic variable escaping before rendering templates. When escape occurs, characters
that make sense to the browser (eg: &lt;a&gt;) will be transformed/replaced with escaped/sanitized values (eg: &amp; lt;a&amp; gt; ).</p>
<p>Auto-escaping is not a magic feature to annihilate all cross-site scripting attacks, it depends on <a
href="https://twig.symfony.com/doc/3.x/filters/escape.html">the strategy applied</a> and the context, for example a "html auto-escaping" strategy
(which only transforms html characters into <a href="https://developer.mozilla.org/en-US/docs/Glossary/Entity">html entities</a>) will not be relevant
when variables are used in a <a href="https://en.wikipedia.org/wiki/HTML_attribute">html attribute</a> because '<code>:</code>' character is not
escaped and thus an attack as below is possible:</p>
<pre>
&lt;a href="{{ myLink }}"&gt;link&lt;/a&gt; // myLink = javascript:alert(document.cookie)
&lt;a href="javascript:alert(document.cookie)"&gt;link&lt;/a&gt; // JS injection (XSS attack)
</pre>
<h2>Ask Yourself Whether</h2>
<ul>
<li> Templates are used to render web content and
<ul>
<li> dynamic variables in templates come from untrusted locations or are user-controlled inputs </li>
<li> there is no local mechanism in place to sanitize or validate the inputs. </li>
</ul> </li>
</ul>
<p>There is a risk if you answered yes to any of those questions.</p>
<h2>Recommended Secure Coding Practices</h2>
<p>Enable auto-escaping by default and continue to review the use of inputs in order to be sure that the chosen auto-escaping strategy is the right
one.</p>
<h2>Sensitive Code Example</h2>
<pre>
&lt;!-- Django templates --&gt;
&lt;p&gt;{{ variable|safe }}&lt;/p&gt;&lt;!-- Sensitive --&gt;
{% autoescape off %}&lt;!-- Sensitive --&gt;

&lt;!-- Jinja2 templates --&gt;
&lt;p&gt;{{ variable|safe }}&lt;/p&gt;&lt;!-- Sensitive --&gt;
{% autoescape false %}&lt;!-- Sensitive --&gt;
</pre>
<h2>See</h2>
<ul>
<li> <a href="https://owasp.org/Top10/A03_2021-Injection/">OWASP Top 10 2021 Category A3</a> - Injection </li>
<li> <a href="https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md">OWASP Cheat
Sheet</a> - XSS Prevention Cheat Sheet </li>
<li> <a href="https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)">OWASP Top 10 2017 Category A7</a> - Cross-Site
Scripting (XSS) </li>
<li> <a href="https://cwe.mitre.org/data/definitions/79">MITRE, CWE-79</a> - Improper Neutralization of Input During Web Page Generation
('Cross-site Scripting') </li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"title": "Disabling auto-escaping in template engines is security-sensitive",
"type": "SECURITY_HOTSPOT",
"code": {
"impacts": {
"SECURITY": "MEDIUM"
},
"attribute": "COMPLETE"
},
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"cwe"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-5247",
"sqKey": "S5247",
"scope": "Main",
"securityStandards": {
"CWE": [
79
],
"OWASP": [
"A7"
],
"OWASP Top 10 2021": [
"A3"
],
"PCI DSS 3.2": [
"6.5.7"
],
"PCI DSS 4.0": [
"6.2.4"
],
"ASVS 4.0": [
"5.3.3"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"S4084",
"S4645",
"S5148",
"S5247",
"S5254",
"S5255",
"S5256",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SonarSource HTML analyzer :: Sonar Plugin
* Copyright (c) 2010-2023 SonarSource SA and Matthijs Galesloot
* sonarqube@googlegroups.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonar.plugins.html.checks.security;

import java.io.File;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.sonar.plugins.html.checks.CheckMessagesVerifierRule;
import org.sonar.plugins.html.checks.TestHelper;
import org.sonar.plugins.html.visitor.HtmlSourceCode;

class DisabledAutoescapeCheckTest {
@RegisterExtension
public CheckMessagesVerifierRule checkMessagesVerifier = new CheckMessagesVerifierRule();

@Test
void test_jinja() {
HtmlSourceCode sourceCode = TestHelper.scan(new File("src/test/resources/checks/jinjaAutoescape.html"), new DisabledAutoescapeCheck());

checkMessagesVerifier.verify(sourceCode.getIssues())
.next().atLocation(6, 35, 6, 57).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(7, 35, 7, 55).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(8, 35, 8, 55).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(9, 34, 11, 40).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(12, 42, 12, 49).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(13, 43, 13, 51).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(16, 12, 16, 32).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(19, 12, 19, 32).withMessage("Make sure disabling auto-escaping feature is safe here.")
.next().atLocation(19, 60, 19, 80).withMessage("Make sure disabling auto-escaping feature is safe here.");
}
}
24 changes: 24 additions & 0 deletions sonar-html-plugin/src/test/resources/checks/jinjaAutoescape.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<main class="container">
<center>
<p style="font-size:2em;"> {% autoescape false %}{{xss}}{% endautoescape %} </p>
<p style="font-size:2em;"> {% autoescape off %}{{xss2}}{% endautoescape %} </p>
<p style="font-size:2em;"> {%autoescape false%}{{xss3}}{% endautoescape %} </p>
<p style="font-size:2em;"> {%autoescape
false%}
{{xss4}}{% endautoescape %} </p>
<p style="font-size:2em;"> {{hello|safe}}</p>
<p style="font-size:2em;"> {{hello2|safe }}</p>
<p style="font-size:2em;"> {{thisissafe}}</p>
<p style="font-size:2em;"> bla
{%autoescape false%}{{xss4}}{% endautoescape %}
bla </p>
<p style="font-size:2em;"> bla
{%autoescape false%}{{xss5}}{% endautoescape %} {%autoescape false%}{{xss6}}{% endautoescape %}
bla </p>
</center>
</main>
</body>
</html>

0 comments on commit 825531d

Please sign in to comment.