Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d1c31db
JS: Reset implicit variable scope when leaving template expr
asgerf Jul 22, 2021
a7cdf53
JS: Parse mustache-style tags as expressions
asgerf Jul 17, 2021
e678c16
JS: Parse EJS-style template tags
asgerf Jul 17, 2021
96a2c3f
JS: Extract .hbs and .ejs as HTML
asgerf Jul 14, 2021
b1ce3d1
JS: Do not extract binary HTML
asgerf Jul 14, 2021
8666bc1
JS: Extract placeholders in HTML
asgerf Jul 20, 2021
66cec65
JS: Format HTMLExtractor
asgerf Jul 20, 2021
f26e94c
JS: Rename to Angular-style template
asgerf Jul 20, 2021
8fe2d84
JS: Move template-related classes to Templating file
asgerf Jul 20, 2021
1474c07
JS: Introduce TemplateInstantiation
asgerf Jul 15, 2021
5659a8a
JS: Add template resolution logic
asgerf Jul 15, 2021
f1c663b
JS: Add steps from instantiation site to placeholder expr
asgerf Jul 20, 2021
f3b97f0
JS: Add steps to/from placeholder tags
asgerf Jul 20, 2021
23eeb49
JS: Detect relevant templating syntax, and add sinks
asgerf Jul 20, 2021
2412f53
JS: Add steps and sinks for pipes
asgerf Jul 20, 2021
745f9b3
JS: Exclude non-code script tags
asgerf Jul 20, 2021
d6dbabf
JS: Ignore empty char sequences
asgerf Jul 20, 2021
0f27bff
JS: Add sinks for server-template tags in AngularJS templates
asgerf Jul 20, 2021
ee33c59
JS: Autoformat
asgerf Jul 20, 2021
623557b
JS: "this" in a template is not the global object
asgerf Jul 21, 2021
8a50d99
JS: Treat GeneratedCodeExpr as DirectEval in UnusedVariable.ql
asgerf Jul 21, 2021
248715c
JS: Restrict FileAccessToHttp a bit
asgerf Jul 21, 2021
6954a9a
JS: Treat EJS-include calls as template instantiations
asgerf Jul 21, 2021
bb80fdd
JS: Handle leading ../ in template resolution
asgerf Jul 21, 2021
bc73d9f
JS: Support templates importing each other
asgerf Jul 21, 2021
266c104
JS: More aggressive TemplateFileReference.getValue
asgerf Jul 22, 2021
425bd7a
JS: Model template instantiation from Fastify, Hapi, and Koa
asgerf Jul 26, 2021
14bada4
JS: Model consolidate and factor in template syntax from call site
asgerf Jul 26, 2021
0a14de1
JS: Also extract .njk files
asgerf Jul 26, 2021
13aa511
JS: Support TemplatePlaceholderTag.getEnclosingExpr
asgerf Jul 27, 2021
e19b6c2
JS: Update taint step
asgerf Jul 27, 2021
f563a01
JS: Recognize .njk extension in QL
asgerf Jul 27, 2021
b36e9e0
JS: Filter out common string literal sinks
asgerf Jul 27, 2021
e3e24f9
JS: Use separate ScopeManager for template exprs
asgerf Jul 28, 2021
ec5e028
JS: Bump extractor version string
asgerf Jul 28, 2021
7045fb4
JS: Expand on test
asgerf Jul 28, 2021
1444ec5
JS: Add similar test for hbs
asgerf Jul 29, 2021
28fe8da
JS: Add similar test for .njk file
asgerf Jul 29, 2021
e8d10b9
JS: Tests for template file resolution
asgerf Jul 29, 2021
308461a
JS: Pass around base folder in file resolution
asgerf Jul 29, 2021
e61d534
JS: Add ambiguity test for template file resolution
asgerf Jul 29, 2021
b1cadc8
JS: Add test for AngularJS sinks
asgerf Jul 29, 2021
b733934
JS: Add tests for EJS includes
asgerf Jul 29, 2021
4f4f524
JS: Add test for upward traversal
asgerf Jul 29, 2021
65b4424
JS: Autoformat
asgerf Aug 2, 2021
349a851
JS: Add change note
asgerf Aug 2, 2021
31d93bb
JS: Add upgrade script
asgerf Aug 2, 2021
5d2bc5e
JS: Update stats file
asgerf Aug 3, 2021
7450554
JS: Remove unused getTemplateContentNode
asgerf Aug 3, 2021
b9b10af
JS: Tolerate parse errors in test due to speculative parsing
asgerf Aug 5, 2021
13fa49a
JS: Update TRAP output
asgerf Aug 10, 2021
2da40b8
JS: Fix some performance issues
asgerf Aug 11, 2021
4923bda
JS: Autoformat
asgerf Aug 16, 2021
bef222d
JS: Add placeholder in attribute to trap test
asgerf Aug 19, 2021
a1819a5
JS: Remove unused isInPlainCodeContext
asgerf Aug 19, 2021
bac212c
JS: Fix typo: instantiaton -> instantiation
asgerf Aug 19, 2021
2553338
JS: Autoformat {AST,HTML}Extractor.java
asgerf Aug 19, 2021
8f8a468
Update javascript/ql/src/semmle/javascript/frameworks/Templating.qll
asgerf Aug 24, 2021
8a564cc
JS: Fix qldoc
asgerf Aug 24, 2021
87843a3
JS: Autoformatttt
asgerf Aug 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions javascript/change-notes/2021-08-02-handlebars-extraction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
lgtm,codescanning
* Added support for more templating languages.
- EJS, Mustache, Handlebars, Nunjucks, Hogan, and Swig are now supported.
- Template tags from the above dialects are now recognized as sinks
when not escaped safely for the context, leading to additional results for `js/xss` and `js/code-injection`.
- Files with the extension `.ejs`, `.hbs`, or `.njk` are now extracted and analyzed.
8 changes: 7 additions & 1 deletion javascript/extractor/src/com/semmle/jcorn/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void call(
Position endLoc);
}

private boolean allowHashBang, allowReturnOutsideFunction, allowImportExportEverywhere;
private boolean allowHashBang, allowReturnOutsideFunction, allowImportExportEverywhere, allowGeneratedCodeExprs;
private boolean preserveParens, mozExtensions, jscript, esnext, v8Extensions, e4x;
private int ecmaVersion;
private AllowReserved allowReserved;
Expand All @@ -58,6 +58,7 @@ public Options() {
this.allowReserved = AllowReserved.YES;
this.allowReturnOutsideFunction = false;
this.allowImportExportEverywhere = false;
this.allowGeneratedCodeExprs = true;
this.allowHashBang = false;
this.onToken = null;
this.onComment = null;
Expand All @@ -75,6 +76,7 @@ public Options(Options that) {
this.allowHashBang = that.allowHashBang;
this.allowReturnOutsideFunction = that.allowReturnOutsideFunction;
this.allowImportExportEverywhere = that.allowImportExportEverywhere;
this.allowGeneratedCodeExprs = that.allowGeneratedCodeExprs;
this.preserveParens = that.preserveParens;
this.mozExtensions = that.mozExtensions;
this.jscript = that.jscript;
Expand Down Expand Up @@ -104,6 +106,10 @@ public boolean allowImportExportEverywhere() {
return allowImportExportEverywhere;
}

public boolean allowGeneratedCodeExprs() {
return allowGeneratedCodeExprs;
}

public boolean preserveParens() {
return preserveParens;
}
Expand Down
59 changes: 57 additions & 2 deletions javascript/extractor/src/com/semmle/jcorn/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import com.semmle.js.ast.ForStatement;
import com.semmle.js.ast.FunctionDeclaration;
import com.semmle.js.ast.FunctionExpression;
import com.semmle.js.ast.GeneratedCodeExpr;
import com.semmle.js.ast.IFunction;
import com.semmle.js.ast.INode;
import com.semmle.js.ast.IPattern;
Expand Down Expand Up @@ -537,7 +538,7 @@ private Token readToken_question() { // '?'
}
return this.finishOp(TokenType.questionquestion, 2);
}

}
return this.finishOp(TokenType.question, 1);
}
Expand Down Expand Up @@ -617,6 +618,15 @@ private Token readToken_lt_gt(int code) { // '<>'
this.skipSpace();
return this.nextToken();
}
if (next == '%' && code == '<' && this.options.allowGeneratedCodeExprs()) {
// `<%`, the beginning of an EJS-style template tag
size = 2;
int nextNext = charAt(this.pos + 2);
if (nextNext == '=' || nextNext == '-') {
++size;
}
return this.finishOp(TokenType.generatedCodeDelimiterEJS, size);
}
if (next == 61) size = 2;
return this.finishOp(TokenType.relational, size);
}
Expand Down Expand Up @@ -1688,6 +1698,9 @@ protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) {
return this.parseNew();
} else if (this.type == TokenType.backQuote) {
return this.parseTemplate(false);
} else if (this.type == TokenType.generatedCodeDelimiterEJS) {
String openingDelimiter = (String) this.value;
return this.parseGeneratedCodeExpr(this.startLoc, openingDelimiter, "%>");
} else {
this.unexpected();
return null;
Expand Down Expand Up @@ -1929,10 +1942,16 @@ MethodDefinition.Kind getMethodKind() {
// Parse an object literal or binding pattern.
protected Expression parseObj(boolean isPattern, DestructuringErrors refDestructuringErrors) {
Position startLoc = this.startLoc;
if (!isPattern && options.allowGeneratedCodeExprs() && charAt(pos) == '{') {
// Parse mustache-style placeholder expression: {{ ... }} or {{{ ... }}}
return charAt(pos + 1) == '{'
? parseGeneratedCodeExpr(startLoc, "{{{", "}}}")
: parseGeneratedCodeExpr(startLoc, "{{", "}}");
}
boolean first = true;
Map<String, PropInfo> propHash = new LinkedHashMap<>();
List<Property> properties = new ArrayList<Property>();
this.next();
this.next(); // skip '{'
while (!this.eat(TokenType.braceR)) {
if (!first) {
this.expect(TokenType.comma);
Expand All @@ -1949,6 +1968,42 @@ protected Expression parseObj(boolean isPattern, DestructuringErrors refDestruct
return this.finishNode(node);
}

/** Emit a token ranging from the current position until <code>endOfToken</code>. */
private Token generateTokenEndingAt(int endOfToken, TokenType tokenType) {
this.lastTokEnd = this.end;
this.lastTokStart = this.start;
this.lastTokEndLoc = this.endLoc;
this.lastTokStartLoc = this.startLoc;
this.start = this.pos;
this.startLoc = this.curPosition();
this.pos = endOfToken;
return finishToken(tokenType);
}

/** Parse a generated expression. The current token refers to the opening delimiter. */
protected Expression parseGeneratedCodeExpr(Position startLoc, String openingDelimiter, String closingDelimiter) {
// Emit a token for what's left of the opening delimiter, if there are any remaining characters
int startOfBody = startLoc.getOffset() + openingDelimiter.length();
if (this.pos != startOfBody) {
this.generateTokenEndingAt(startOfBody, TokenType.generatedCodeDelimiter);
}

// Emit a token for the generated code body
int endOfBody = this.input.indexOf(closingDelimiter, startOfBody);
if (endOfBody == -1) {
this.unexpected(startLoc);
}
Token bodyToken = this.generateTokenEndingAt(endOfBody, TokenType.generatedCodeExpr);

// Emit a token for the closing delimiter
this.generateTokenEndingAt(endOfBody + closingDelimiter.length(), TokenType.generatedCodeDelimiter);

this.next(); // produce lookahead token

return finishNode(new GeneratedCodeExpr(new SourceLocation(startLoc), openingDelimiter, closingDelimiter,
bodyToken.getValue()));
}

protected Property parseProperty(
boolean isPattern,
DestructuringErrors refDestructuringErrors,
Expand Down
6 changes: 5 additions & 1 deletion javascript/extractor/src/com/semmle/jcorn/TokenType.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.semmle.jcorn;

import com.semmle.jcorn.Parser.TokContext;
import java.util.LinkedHashMap;
import java.util.Map;

import com.semmle.jcorn.Parser.TokContext;

/// tokentype.js

// ## Token types
Expand Down Expand Up @@ -89,6 +90,9 @@ public void updateContext(Parser parser, TokenType prevType) {
arrow = new TokenType(new Properties("=>").beforeExpr()),
template = new TokenType(new Properties("template")),
invalidTemplate = new TokenType(new Properties("invalidTemplate")),
generatedCodeExpr = new TokenType(new Properties("generatedCodeExpr")),
generatedCodeDelimiter = new TokenType(new Properties("generatedCodeDelimiter")),
generatedCodeDelimiterEJS = new TokenType(new Properties("<%/%>")),
ellipsis = new TokenType(new Properties("...").beforeExpr()),
backQuote =
new TokenType(new Properties("`").startsExpr()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ protected Token readToken(int code) {
&& code == 60
&& this.exprAllowed
&&
// avoid getting confused on HTML comments
this.charAt(this.pos + 1) != '!') {
// avoid getting confused on HTML comments or EJS-style template tags
this.charAt(this.pos + 1) != '!' &&
this.charAt(this.pos + 1) != '%') {
++this.pos;
return this.finishToken(jsxTagStart);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,4 +777,9 @@ public R visit(XMLQualifiedIdentifier nd, C c) {
public R visit(XMLDotDotExpression nd, C c) {
return visit((Expression) nd, c);
}

@Override
public R visit(GeneratedCodeExpr nd, C c) {
return visit((Expression) nd, c);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.semmle.js.ast;

/**
* A placeholder for generated code, speculatively parsed as a primary expression.
*
* <p>For example, in this snippet,
*
* <pre>
* let data = {{user_data}};
* </pre>
*
* the expression <code>{{user_data}}</code> is assumed to be filled in by a templating engine so
* that it can be parsed as an expression, and a <code>GeneratedCodeExpr</code> is thus created to
* represent it.
*/
public class GeneratedCodeExpr extends Expression {
private String openingDelimiter;
private String closingDelimiter;
private String body;

public GeneratedCodeExpr(
SourceLocation loc, String openingDelimiter, String closingDelimiter, String body) {
super("GeneratedCodeExpr", loc);
this.openingDelimiter = openingDelimiter;
this.closingDelimiter = closingDelimiter;
this.body = body;
}

public String getOpeningDelimiter() {
return openingDelimiter;
}

public String getClosingDelimiter() {
return closingDelimiter;
}

public String getBody() {
return body;
}

@Override
public <C, R> R accept(Visitor<C, R> v, C c) {
return v.visit(this, c);
}
}
5 changes: 5 additions & 0 deletions javascript/extractor/src/com/semmle/js/ast/NodeCopier.java
Original file line number Diff line number Diff line change
Expand Up @@ -894,4 +894,9 @@ public INode visit(XMLQualifiedIdentifier nd, Void c) {
public INode visit(XMLDotDotExpression nd, Void c) {
return new XMLDotDotExpression(visit(nd.getLoc()), copy(nd.getLeft()), copy(nd.getRight()));
}

@Override
public INode visit(GeneratedCodeExpr nd, Void c) {
return new GeneratedCodeExpr(visit(nd.getLoc()), nd.getOpeningDelimiter(), nd.getClosingDelimiter(), nd.getBody());
}
}
2 changes: 2 additions & 0 deletions javascript/extractor/src/com/semmle/js/ast/Visitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,6 @@ public interface Visitor<C, R> {
public R visit(XMLQualifiedIdentifier nd, C c);

public R visit(XMLDotDotExpression nd, C c);

public R visit(GeneratedCodeExpr generatedCodeExpr, C c);
}
Loading