always require integrity checking for certain CDNs

This commit is contained in:
Stephan Brandauer
2022-02-15 13:10:14 +01:00
parent 83764df4f5
commit 780fa97869
2 changed files with 29 additions and 7 deletions

View File

@@ -22,15 +22,27 @@ predicate isLocalhostPrefix(string host) {
])
}
bindingset[path]
predicate isUntrustedSourcePath(string path) {
path.substring(0, 2) = "//"
/** A path that is vulnerable to a MITM attack. */
bindingset[url]
predicate isUntrustedSourceUrl(string url) {
url.substring(0, 2) = "//"
or
exists(string hostPath | hostPath = path.regexpCapture("http://(.*)", 1) |
exists(string hostPath | hostPath = url.regexpCapture("http://(.*)", 1) |
not isLocalhostPrefix(hostPath)
)
}
/** A path that needs an integrity check — even with https. */
bindingset[url]
predicate isCdnUrlWithCheckingRequired(string url) {
// Some CDN URLs are required to have an integrity attribute. We only add CDNs to that list
// that recommend integrity-checking.
url.regexpMatch([
"^https?://code\\.jquery\\.com/.*\\.js$", "^https?://cdnjs\\.cloudflare\\.com/.*\\.js$",
"^https?://cdnjs\\.com/.*\\.js$"
])
}
abstract class IncludesUntrustedContent extends HTML::Element {
/** Gets an explanation why this source is untrusted. */
abstract string getProblem();
@@ -39,8 +51,12 @@ abstract class IncludesUntrustedContent extends HTML::Element {
/** A script element that refers to untrusted content. */
class ScriptElementWithUntrustedContent extends IncludesUntrustedContent, HTML::ScriptElement {
ScriptElementWithUntrustedContent() {
isUntrustedSourcePath(this.getSourcePath()) and
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest)
not exists(string digest | not digest = "" | this.getIntegrityDigest() = digest) and
(
isUntrustedSourceUrl(this.getSourcePath())
or
isCdnUrlWithCheckingRequired(this.getSourcePath())
)
}
override string getProblem() {
@@ -50,7 +66,7 @@ class ScriptElementWithUntrustedContent extends IncludesUntrustedContent, HTML::
/** An iframe element that includes untrusted content. */
class IframeElementWithUntrustedContent extends HTML::IframeElement, IncludesUntrustedContent {
IframeElementWithUntrustedContent() { isUntrustedSourcePath(this.getSourcePath()) }
IframeElementWithUntrustedContent() { isUntrustedSourceUrl(this.getSourcePath()) }
override string getProblem() { result = "iframe elements should use an HTTPS url" }
}

View File

@@ -12,5 +12,11 @@
<iframe src="http://::1/foo.html"></iframe> <!-- OK (localhost) -->
<iframe src="http://[::1]:80/foo.html"></iframe> <!-- OK (localhost) -->
<iframe src="http://127.0.0.1:444/foo.html"></iframe> <!-- OK (localhost) -->
<!-- Some CDNs recommend using the integrity attribute — for those, we demand it even with https links -->
<!-- OK (digest present) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js" integrity="sha512-7oYXeK0OxTFxndh0erL8FsjGvrl2VMDor6fVqzlLGfwOQQqTbYsGPv4ZZ15QHfSk80doyaM0ZJdvkyDcVO7KFA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- NOT OK (digest missing) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</body>
</html>