Skip to content

Commit f92a3aa

Browse files
Add S5734 Require nosniff header in web.config
1 parent 028ddc1 commit f92a3aa

File tree

17 files changed

+391
-23
lines changed

17 files changed

+391
-23
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"project:asp.net-web-application/web.config": [
3+
1
4+
]
5+
}

sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/CheckList.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.sonar.plugins.xml.checks.security.web.BasicAuthenticationCheck;
4141
import org.sonar.plugins.xml.checks.security.web.CrossOriginResourceSharingCheck;
4242
import org.sonar.plugins.xml.checks.security.web.HttpOnlyOnCookiesCheck;
43+
import org.sonar.plugins.xml.checks.security.web.MimeNosniffCheck;
4344
import org.sonar.plugins.xml.checks.security.web.ValidationFiltersCheck;
4445
import org.sonar.plugins.xml.checks.spring.DefaultMessageListenerContainerCheck;
4546
import org.sonar.plugins.xml.checks.spring.SingleConnectionFactoryCheck;
@@ -89,7 +90,8 @@ public static List<Class<?>> getCheckClasses() {
8990
FixmeCommentCheck.class,
9091
ValidationFiltersCheck.class,
9192
DisallowedDependenciesCheck.class,
92-
CommentedOutCodeCheck.class
93+
CommentedOutCodeCheck.class,
94+
MimeNosniffCheck.class
9395
);
9496
}
9597

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* SonarQube XML Plugin
3+
* Copyright (C) 2010-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.xml.checks.security.web;
18+
19+
import org.sonar.plugins.xml.Xml;
20+
import org.sonarsource.analyzer.commons.xml.XmlFile;
21+
import org.sonarsource.analyzer.commons.xml.checks.SimpleXPathBasedCheck;
22+
23+
/**
24+
* Base class for checks targeting Java's web.xml and .NET web.config files.
25+
*/
26+
public class BaseWebCheck extends SimpleXPathBasedCheck {
27+
protected static final String WEB_XML_ROOT = "web-app";
28+
29+
@Override
30+
public final void scanFile(XmlFile file) {
31+
if (isWebXmlFile(file)) {
32+
scanWebXml(file);
33+
} else if (Xml.isDotNetApplicationConfig(file.getInputFile())) {
34+
scanWebConfig(file);
35+
}
36+
}
37+
38+
/** Scan Java's web.xml. */
39+
protected void scanWebXml(XmlFile file) {
40+
// Ignored by default.
41+
}
42+
43+
/** Scan .NET's web.config. */
44+
protected void scanWebConfig(XmlFile file) {
45+
// Ignored by default.
46+
}
47+
48+
private static boolean isWebXmlFile(XmlFile file) {
49+
return "web.xml".equalsIgnoreCase(file.getInputFile().filename());
50+
}
51+
52+
/**
53+
* Builds an XPath expression that matches the deepest existing node.
54+
*
55+
* For example, for segments "a", "b", "c", the resulting expression is:
56+
* <pre>
57+
* /a/b/c | /a/b[not(c)] | /a[not(b)]
58+
* </pre>
59+
*/
60+
protected String getDeepestExistingNode(String ... segments) {
61+
StringBuilder expression = new StringBuilder();
62+
for (int len = segments.length; len > 0; len--) {
63+
for (int i = 0; i < len; i++) {
64+
expression.append("/").append(segments[i]);
65+
}
66+
if (len < segments.length) {
67+
expression.append("[not(").append(segments[len]).append(")]");
68+
}
69+
if (len > 1) {
70+
expression.append("|");
71+
}
72+
}
73+
return expression.toString();
74+
}
75+
}

sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/web/BasicAuthenticationCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.w3c.dom.Document;
2424

2525
@Rule(key = "S2647")
26-
public class BasicAuthenticationCheck extends AbstractWebXmlCheck {
26+
public class BasicAuthenticationCheck extends BaseWebCheck {
2727

2828
private XPathExpression authMethodBasicExpression = XPathBuilder
2929
.forExpression("/j:web-app/j:login-config/j:auth-method[.='BASIC']")
@@ -36,7 +36,7 @@ public class BasicAuthenticationCheck extends AbstractWebXmlCheck {
3636
.build();
3737

3838
@Override
39-
void scanWebXml(XmlFile file) {
39+
protected void scanWebXml(XmlFile file) {
4040
Document webApp = file.getDocument();
4141
if (!evaluateAsList(httpsEnabledExpression, webApp).isEmpty()) {
4242
return;

sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/web/CrossOriginResourceSharingCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.sonarsource.analyzer.commons.xml.XmlFile;
2424

2525
@Rule(key = "S5122")
26-
public class CrossOriginResourceSharingCheck extends AbstractWebXmlCheck {
26+
public class CrossOriginResourceSharingCheck extends BaseWebCheck {
2727

2828
private static final Pattern STAR_IN_COMMA_SEPARATED_LIST_REGEX = Pattern.compile("(^|,)\\*(,|$)");
2929

@@ -37,7 +37,7 @@ public class CrossOriginResourceSharingCheck extends AbstractWebXmlCheck {
3737
.build();
3838

3939
@Override
40-
void scanWebXml(XmlFile file) {
40+
protected void scanWebXml(XmlFile file) {
4141
evaluateAsList(corsAllowedOrigins, file.getDocument()).stream()
4242
.filter(node -> STAR_IN_COMMA_SEPARATED_LIST_REGEX.matcher(node.getNodeValue()).find())
4343
.forEach(node -> reportIssue(node, "Make sure this permissive CORS policy is safe here."));

sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/web/HttpOnlyOnCookiesCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.w3c.dom.Node;
2525

2626
@Rule(key = "S3330")
27-
public class HttpOnlyOnCookiesCheck extends AbstractWebXmlCheck {
27+
public class HttpOnlyOnCookiesCheck extends BaseWebCheck {
2828

2929
private XPathExpression sessionConfigCookieConfigExpression = XPathBuilder
3030
.forExpression("/n:web-app/n:session-config/n:cookie-config")
@@ -36,7 +36,7 @@ public class HttpOnlyOnCookiesCheck extends AbstractWebXmlCheck {
3636
.build();
3737

3838
@Override
39-
void scanWebXml(XmlFile file) {
39+
protected void scanWebXml(XmlFile file) {
4040
evaluateAsList(sessionConfigCookieConfigExpression, file.getDocument()).forEach(this::checkHttpOnly);
4141
}
4242

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* SonarQube XML Plugin
3+
* Copyright (C) 2010-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.xml.checks.security.web;
18+
19+
import org.sonar.check.Rule;
20+
import org.sonarsource.analyzer.commons.xml.XPathBuilder;
21+
import org.sonarsource.analyzer.commons.xml.XmlFile;
22+
import org.w3c.dom.Document;
23+
import org.w3c.dom.NodeList;
24+
25+
import javax.xml.xpath.XPathExpression;
26+
27+
/**
28+
* Ensure that the X-Content-Type-Options header is set to "nosniff" to prevent MIME type sniffing.
29+
* The check applies to .NET web.config files.
30+
*/
31+
@Rule(key = "S5734")
32+
public class MimeNosniffCheck extends BaseWebCheck {
33+
private final XPathExpression httpCookiesExpression = XPathBuilder
34+
.forExpression(
35+
"/configuration"
36+
+ "/system.webServer"
37+
+ "/httpProtocol"
38+
+ "/customHeaders"
39+
+ "/add[@name=\"X-Content-Type-Options\" and @value=\"nosniff\"]")
40+
.build();
41+
42+
/** Attach the issue to the closest existing node. */
43+
private final XPathExpression reportNodeExpression = XPathBuilder
44+
.forExpression(getDeepestExistingNode("configuration", "system.webServer", "httpProtocol", "customHeaders"))
45+
.build();
46+
47+
@Override
48+
protected void scanWebConfig(XmlFile file) {
49+
Document document = file.getDocument();
50+
NodeList expectedNodes = evaluate(httpCookiesExpression, document);
51+
52+
// null is returned on internal errors, and we don't want to raise a false positive in that case.
53+
if (expectedNodes != null && expectedNodes.getLength() == 0) {
54+
evaluateAsList(reportNodeExpression, document)
55+
.stream()
56+
.findFirst()
57+
.ifPresent(target ->
58+
reportIssue(target, "Global <httpCookies> tag is missing or its 'httpOnlyCookies' attribute is not set to true."));
59+
}
60+
}
61+
}

sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/checks/security/web/ValidationFiltersCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
@Rule(key = "S3355")
2929
@DeprecatedRuleKey(repositoryKey = "java", ruleKey = "S3355")
30-
public class ValidationFiltersCheck extends AbstractWebXmlCheck {
30+
public class ValidationFiltersCheck extends BaseWebCheck {
3131
private XPathExpression filterNamesFromFilterExpression = getXPathExpression(WEB_XML_ROOT + "/filter/filter-name");
3232
private XPathExpression filterNamesFromFilterMappingExpression = getXPathExpression(WEB_XML_ROOT + "/filter-mapping/filter-name");
3333

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<p><a href="https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/">MIME confusion</a> attacks occur when an
2+
attacker successfully tricks a web-browser to interpret a resource as a different type than the one expected. To correctly interpret a resource
3+
(script, image, stylesheet …​) web browsers look for the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type">Content-Type
4+
header</a> defined in the HTTP response received from the server, but often this header is not set or is set with an incorrect value. To avoid
5+
content-type mismatch and to provide the best user experience, web browsers try to deduce the right content-type, generally by inspecting the content
6+
of the resources (the first bytes). This "guess mechanism" is called <a href="https://en.wikipedia.org/wiki/Content_sniffing">MIME type
7+
sniffing</a>.</p>
8+
<p>Attackers can take advantage of this feature when a website ("example.com" here) allows to upload arbitrary files. In that case, an attacker can
9+
upload a malicious image <em>fakeimage.png</em> (containing malicious JavaScript code or <a
10+
href="https://docs.microsoft.com/fr-fr/archive/blogs/ieinternals/script-polyglots">a polyglot content</a> file) such as:</p>
11+
<pre>
12+
&lt;script&gt;alert(document.cookie)&lt;/script&gt;
13+
</pre>
14+
<p>When the victim will visit the website showing the uploaded image, the malicious script embedded into the image will be executed by web browsers
15+
performing MIME type sniffing.</p>
16+
<h2>Ask Yourself Whether</h2>
17+
<ul>
18+
<li> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type">Content-Type</a> header is not systematically set for all
19+
resources. </li>
20+
<li> Content of resources can be controlled by users. </li>
21+
</ul>
22+
<p>There is a risk if you answered yes to any of those questions.</p>
23+
<h2>Recommended Secure Coding Practices</h2>
24+
<p>Implement <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">X-Content-Type-Options</a> header with
25+
<em>nosniff</em> value (the only existing value for this header) which is supported by all modern browsers and will prevent browsers from performing
26+
MIME type sniffing, so that in case of Content-Type header mismatch, the resource is not interpreted. For example within a &lt;script&gt; object
27+
context, JavaScript MIME types are expected (like <em>application/javascript</em>) in the Content-Type header.</p>
28+
<h2>Sensitive Code Example</h2>
29+
<p>The following ASP.NET website configuration is sensitive, because it does not set the <code>X-Content-Type-Options</code> HTTP response header to
30+
<code>nosniff</code>:</p>
31+
<pre>
32+
&lt;?xml version="1.0" encoding="utf-8"?&gt;
33+
&lt;configuration&gt; &lt;!-- Sensitive --&gt;
34+
&lt;system.webServer&gt;
35+
&lt;!-- ... --&gt;
36+
&lt;/system.webServer&gt;
37+
&lt;/configuration&gt;
38+
</pre>
39+
<h2>Compliant Solution</h2>
40+
<p>To mitigate this finding, set <code>nosniff</code> globally for all pages in the application in the <code>web.config</code> file. While it is also
41+
possible to configure the header individually in the application code, or per <code>&lt;location&gt;</code> element, setting it globally is less
42+
error-prone and therefore recommended.</p>
43+
<pre>
44+
&lt;?xml version="1.0" encoding="utf-8"?&gt;
45+
&lt;configuration&gt;
46+
&lt;system.webServer&gt;
47+
&lt;httpProtocol&gt;
48+
&lt;customHeaders&gt;
49+
&lt;add name="X-Content-Type-Options" value="nosniff"/&gt;
50+
&lt;/customHeaders&gt;
51+
&lt;/httpProtocol&gt;
52+
&lt;!-- ... --&gt;
53+
&lt;/system.webServer&gt;
54+
&lt;/configuration&gt;
55+
</pre>
56+
<h2>See</h2>
57+
<ul>
58+
<li> OWASP - <a href="https://owasp.org/Top10/A05_2021-Security_Misconfiguration/">Top 10 2021 Category A5 - Security Misconfiguration</a> </li>
59+
<li> OWASP - <a href="https://owasp.org/www-project-top-ten/2017/A6_2017-Security_Misconfiguration">Top 10 2017 Category A6 - Security
60+
Misconfiguration</a> </li>
61+
<li> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">developer.mozilla.org</a> - X-Content-Type-Options
62+
</li>
63+
<li> <a href="https://blog.mozilla.org/security/2016/08/26/mitigating-mime-confusion-attacks-in-firefox/">blog.mozilla.org</a> - Mitigating MIME
64+
Confusion Attacks in Firefox </li>
65+
</ul>
66+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"title": "Allowing browsers to sniff MIME types is security-sensitive",
3+
"type": "SECURITY_HOTSPOT",
4+
"code": {
5+
"impacts": {
6+
"SECURITY": "LOW"
7+
},
8+
"attribute": "COMPLETE"
9+
},
10+
"status": "ready",
11+
"remediation": {
12+
"func": "Constant\/Issue",
13+
"constantCost": "5min"
14+
},
15+
"tags": [],
16+
"defaultSeverity": "Minor",
17+
"ruleSpecification": "RSPEC-5734",
18+
"sqKey": "S5734",
19+
"scope": "Main",
20+
"securityStandards": {
21+
"OWASP": [
22+
"A6"
23+
],
24+
"OWASP Top 10 2021": [
25+
"A5"
26+
]
27+
}
28+
}

0 commit comments

Comments
 (0)