-
Notifications
You must be signed in to change notification settings - Fork 1.8k
JS: Add support for EJS and Mustache-style templating dialects #6403
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
Conversation
2d9cf44
to
42fe70d
Compare
42fe70d
to
6e8a47a
Compare
6e8a47a
to
2da40b8
Compare
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.
Wow, that's a mouthful.
Here is a partial review from trying to go through commit-by-commit.
javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java
Outdated
Show resolved
Hide resolved
/** The `include` function, seen as an API node, so we can treat it as a template instantiation. */ | ||
private class IncludeFunctionAsEntryPoint extends API::EntryPoint { | ||
IncludeFunctionAsEntryPoint() { this = "IncludeFunctionAsEntryPoint" } | ||
|
||
override DataFlow::SourceNode getAUse() { | ||
result = any(TemplatePlaceholderTag tag).getInnerTopLevel().getAVariableUse("include") | ||
} | ||
|
||
override DataFlow::Node getARhs() { none() } | ||
} |
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 can see that this class is needed, because the tests fail if I remove it.
But I'm not sure where it's needed.
I can't get it to fit with the API::Node
usage in getTemplateInput
, but maybe that's me missing something.
Could you elaborate in which class/predicate this API::EntryPoint
is needed?
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.
It's needed for EjsIncludeCallInTemplate
. By ensuring that the callee has an API node, the arguments to call have API nodes as well, which is needed in getTemplateParamsNode
, which is needed in getTemplateInput
.
Using EjsIncludeCallInTemplate
directly in the definition of the entry point leads to negative recursion, unfortunately.
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.
Great, could you add a reference to EjsIncludeCallInTemplate
in the qldoc?
Co-authored-by: Erik Krogh Kristensen <erik-krogh@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.
👍
Impressive work!
Thanks for powering through another mega PR @erik-krogh! ✨ |
Adds support template files in the following dialects: EJS, Mustache, Handlebars, Nunjucks, Hogan, and Swig.
I've tried to keep the commits self-contained, but it's a large PR so it would help to have a high-level overview before diving in.
New sinks recognized
For an EJS snippet like,
we now recognize
title
as an XSS sink, since<%- ... %>
performs raw interpolation in EJS. For example, it would be vulnerable if instantiated like:Even when a "safe" interpolation tag is used (one that escapes the output), there are a few cases where we recognize it as a code injection sink:
Extracting placeholder tags
To support placeholders with non-trivial expressions, like
<%- foo.bar %>
, we extract the contents of such placeholders as JS with the same dialect we use for Angular2 expressions. (This dialect has internally been renamed to "angular-like template").The regular JS parser has also been updated to recognize placeholder tags occuring in plain JS context, so we can parse script tags that contain such placeholders. For example, we now get flow from
userdata
to theinnerHTML
assignment here:Wiring up template instantiations and template files
To see what file is rendered by
res.render('foo/mytemplate', { ... })
, we have to match up thefoo/mytemplate
string with a file.Here we use a heuristic algorithm which:
foo/mytemplate.html
,foo/mytemplate.ejs
, orfoo/mytemplate/index.html
.For example, given these files,
src/lib/app.js
containing a callres.render('foo/mytemplate')
src/lib/views/foo/mytemplate.html
src/lib/mytemplate.html
src/docs/foo/mytemplate.html
The
src/lib/mytemplate.html
file fails the initial suffix test (foo/mytemplate
).The remaining template files have the following lowest common ancestor with the referencing file (
src/lib/app.js
):src/lib/views/mytemplate.html
->src/lib/
src/docs/mytemplate.html
->src/
Since the first one has the lowest common ancestor (or equivalently, the longest common prefix) that one is picked as the target.
Evaluations