-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[javascript] CodeQL query to detect missing origin validation in cross-origin communication via postMessage #3646
Conversation
Thank you, we will get back to you next week. In the meantime, I'll direct your attention to our default sanitizer for origin validation, which seems very related:
Could that inspire some changes in your end? |
predicate isOriginChecked() { | ||
exists(IfStmt ifStmt | | ||
ifStmt = this.getAPropertyRead("origin").getAMethodCall*().asExpr().getEnclosingStmt() or | ||
ifStmt = this.getAPropertyRead("origin").asExpr().getEnclosingStmt() | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any way to ensure that the dev hasn't done something goofy like:
if (event.origin.contains('www.example.com')) {
}
Or, one that I've seen before when I reported CVE-2019-10779:
var origin = event.origin;
var host = window.location.host;
// Stop this script being called from other domains.
if (origin.indexOf(host) === -1)
return;
This was the one I used when I found a vulnerability in a GCHQ project:
GHSA-6p78-9hh5-384m
https://gist.github.com/JLLeitschuh/a6d6eb3886170dafa7c0cbe1d63edefa
Legit vulnerability query. I've found vulns from this mistake before. I appreciate this is being added. |
Hi @esbena, @JLLeitschuh thanks for your feedbacks, I really appreciated. @esbena I looked at your suggestion and I used it to check if a read access to the property @JLLeitschuh I added other checks in order to detect insufficient and insecure validation of the I am learning this amazing tool and I would like to contribute, so any comment/feedback is more than welcomed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea in the query. It may be a bit too noisy to enable by default since there are legitimate uses of cross-origin communication that do not require origin checking, but if you are willing to look at those false positives, then the query is good.
See my inline comments about the QL content.
For the record, we already treat cross-origin messages without an origin check as taint sources for our security queries.
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.ql
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.ql
Outdated
Show resolved
Hide resolved
exists(InsufficientOriginChecks insufficientChecks | | ||
this.getAPropertyRead("origin").getAMethodCall*() = insufficientChecks | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exists(InsufficientOriginChecks insufficientChecks | | |
this.getAPropertyRead("origin").getAMethodCall*() = insufficientChecks | |
) | |
this.getAPropertyRead("origin").getAMethodCall() instanceof InsufficientOriginChecks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the all the feedback. I really appreciated.
I have just a comment about this suggestion. Should it be
this.getAPropertyRead("origin").getAMethodCall*() instanceof InsufficientOriginChecks
?
Without the *
I don't catch for example the following case:
let origin = event.origin.toLowerCase();
let host = window.location.host;
// BAD
if (origin.startsWith(host) === -1)
return;
This is because in the if
statement the resolved predicate is event.origin.toLowerCase().startsWith()
(please do not hesitate to correct me if I’m wrong).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, that is a good point. Lets add the *
then.
(If you are interested, then the way we would check for such extended flows in a non-experimental query, would be using "type tracking". Type tracking would also allow us to identify bad checks in other functions for instance. But that is an advanced feature that can be added later)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, so I leave the *
.
/** | ||
* A method call for the insecure functions used to verify the `MessageEvent.origin`. | ||
*/ | ||
class InsufficientOriginChecks extends DataFlow::MethodCallNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a few API methods for calls like this already. Do they suit your needs?
See this for instance:
codeql/javascript/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
Lines 16 to 32 in d80a033
/** | |
* A check on a string for whether it contains a given substring, possibly with restrictions on the location of the substring. | |
*/ | |
class SomeSubstringCheck extends DataFlow::Node { | |
DataFlow::Node substring; | |
SomeSubstringCheck() { | |
this.(StringOps::StartsWith).getSubstring() = substring or | |
this.(StringOps::Includes).getSubstring() = substring or | |
this.(StringOps::EndsWith).getSubstring() = substring | |
} | |
/** | |
* Gets the substring. | |
*/ | |
DataFlow::Node getSubstring() { result = substring } | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out. I was already aware of these APIs, indeed I was inspired by a similar query:
codeql/javascript/ql/src/Security/CWE-020/IncorrectSuffixCheck.ql#L18-L23
Since I am pretty new to this framework, what are the benefits of using the suggested API methods?
I just want to detect if there is a weak function call (like indexOf
) to validate the origin.
It seems to me that these API methods are preferred if you what to reason about the object passed to these methods.
In any case, if you think that the actual implementation can be improved, let me know. I will try to combine the already existing API methods and what I have done.
Again, thanks for your feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a minor suggestion, feel free to ignore it.
Since I am pretty new to this framework, what are the benefits of using the suggested API methods?
The benefit is that StringOps::...
is like to cover more cases, and that it is likely to be extended in the future. See this case for instance:
codeql/javascript/ql/src/semmle/javascript/InclusionTests.qll
Lines 110 to 122 in 03c6d7a
/** | |
* A call to `_.includes` or similar, assumed to operate on strings. | |
*/ | |
private class Includes_Library extends Range, DataFlow::CallNode { | |
Includes_Library() { | |
exists(string name | | |
this = LodashUnderscore::member(name).getACall() and | |
(name = "includes" or name = "include" or name = "contains") | |
or | |
this = Closure::moduleImport("goog.string." + name).getACall() and | |
(name = "contains" or name = "caseInsensitiveContains") | |
) | |
} |
Of course, if you spot a false positive from your query due to the use of StringOps::...
, then the right choice is the simpler list of method names that you already have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for explaining this :)
Glad to know the difference. Since StringOps
covers more cases of functions that can be used to validate the origin (improperly), I changed the code in favour of StringOps
.
Thanks for the feedback.
…riginCheck.ql Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
…riginCheck.ql Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
…riginCheck.ql Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to adjust the QL more if my replies have inspired you, but otherwise I think we are good once the documentation has been fixed.
I will start a large evaluation once you are satisfied.
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.qhelp
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.qhelp
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.qhelp
Outdated
Show resolved
Hide resolved
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.ql
Outdated
Show resolved
Hide resolved
…riginCheck.qhelp Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
…riginCheck.qhelp Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
…riginCheck.qhelp Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
…riginCheck.ql Co-authored-by: Esben Sparre Andreasen <esbena@github.com>
@esbena I have made the changes. Let me know if now it’s ok and if there is something else I can do. Again, thanks for all the feedback. |
javascript/ql/src/experimental/Security/CWE-020/PostMessageNoOriginCheck.qhelp
Outdated
Show resolved
Hide resolved
I am merging this now. Thank you for the contribution @dellalibera. |
@esbena it was really a pleasure. I am really happy that this PR was merged. @JLLeitschuh thanks for your precious suggestions. I really appreciated. |
When using cross-domain communication via the
window.postMessage()
APIWindow.postMessage()
This query addresses the scenario where the
MessageEvent.origin
property is not checked(either read or call) in the
postMessage
event handler.