Merge branch 'master'

This commit is contained in:
Denis Levin
2018-10-01 14:51:56 -07:00
383 changed files with 11060 additions and 8219 deletions

View File

@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Semmle JavaScript Default Queries
Bundle-SymbolicName: com.semmle.plugin.semmlecode.javascript.queries;singleton:=true
Bundle-Version: 1.18.0.qualifier
Bundle-Version: 1.18.1.qualifier
Bundle-Vendor: Semmle Ltd.
Bundle-ActivationPolicy: lazy
Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.0.qualifier, 1.18.0.qualifier]"
Require-Bundle: com.semmle.plugin.qdt.ui;bundle-version="[1.18.1.qualifier,1.18.1.qualifier]"

View File

@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Replacing a substring with itself has no effect and usually indicates a mistake, such as
misspelling a backslash escape.
</p>
</overview>
<recommendation>
<p>
Examine the string replacement to find and correct any typos.
</p>
</recommendation>
<example>
<p>
The following code snippet attempts to backslash-escape all double quotes in <code>raw</code>
by replacing all instances of <code>"</code> with <code>\"</code>:
</p>
<sample src="examples/IdentityReplacement.js" />
<p>
However, the replacement string <code>'\"'</code> is actually the same as <code>'"'</code>,
with <code>\"</code> interpreted as an identity escape, so the replacement does nothing.
Instead, the replacement string should be <code>'\\"'</code>:
</p>
<sample src="examples/IdentityReplacementGood.js" />
</example>
<references>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Escape_notation">String escape notation</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,68 @@
/**
* @name Replacement of a substring with itself
* @description Replacing a substring with itself has no effect and may indicate a mistake.
* @kind problem
* @problem.severity warning
* @id js/identity-replacement
* @precision very-high
* @tags correctness
* security
* external/cwe/cwe-116
*/
import javascript
/**
* Holds if `e`, when used as the first argument of `String.prototype.replace`, matches
* `s` and nothing else.
*/
predicate matchesString(Expr e, string s) {
exists (RegExpLiteral rl |
rl = e and
not rl.isIgnoreCase() and
regExpMatchesString(rl.getRoot(), s)
)
or
s = e.getStringValue()
}
/**
* Holds if `t` matches `s` and nothing else.
*/
language[monotonicAggregates]
predicate regExpMatchesString(RegExpTerm t, string s) {
// constants match themselves
s = t.(RegExpConstant).getValue()
or
// assertions match the empty string
(t instanceof RegExpCaret or
t instanceof RegExpDollar or
t instanceof RegExpWordBoundary or
t instanceof RegExpNonWordBoundary or
t instanceof RegExpLookahead or
t instanceof RegExpLookbehind) and
s = ""
or
// groups match their content
regExpMatchesString(t.(RegExpGroup).getAChild(), s)
or
// single-character classes match that character
exists (RegExpCharacterClass recc | recc = t and not recc.isInverted() |
recc.getNumChild() = 1 and
regExpMatchesString(recc.getChild(0), s)
)
or
// sequences match the concatenation of their elements
exists (RegExpSequence seq | seq = t |
s = concat(int i, RegExpTerm child | child = seq.getChild(i) |
any(string subs | regExpMatchesString(child, subs)) order by i
)
)
}
from MethodCallExpr repl, string s, string friendly
where repl.getMethodName() = "replace" and
matchesString(repl.getArgument(0), s) and
repl.getArgument(1).getStringValue() = s and
(if s = "" then friendly = "the empty string" else friendly = "'" + s + "'")
select repl.getArgument(0), "This replaces " + friendly + " with itself."

View File

@@ -0,0 +1 @@
var escaped = raw.replace(/"/g, '\"');

View File

@@ -0,0 +1 @@
var escaped = raw.replace(/"/g, '\\"');

View File

@@ -38,12 +38,15 @@ predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHand
* // protected from CSRF
* })
* ```
*
* Currently the predicate only detects `csurf`-based protectors.
*/
DataFlow::CallNode csrfMiddlewareCreation() {
exists (DataFlow::ModuleImportNode mod | result = mod.getACall() |
mod.getPath() = "csurf"
exists (DataFlow::SourceNode callee | result = callee.getACall() |
callee = DataFlow::moduleImport("csurf")
or
callee = DataFlow::moduleImport("lusca") and
exists(result.getOptionArgument(0, "csrf"))
or
callee = DataFlow::moduleMember("lusca", "csrf")
)
}

View File

@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="semmlecode-javascript-queries"/>
</extension>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="com.semmle.code.javascript.library"/>
</extension>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="com.semmle.code.javascript.dbscheme"/>
<path value="/semmlecode.javascript.dbscheme"/>
</extension>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="semmlecode-javascript-queries"/>
</extension>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="com.semmle.code.javascript.library"/>
</extension>
<extension point="com.semmle.plugin.qdt.ui.resources">
<name value="com.semmle.code.javascript.dbscheme"/>
<path value="/semmlecode.javascript.dbscheme"/>
</extension>
</plugin>

View File

@@ -6,7 +6,7 @@ import javascript
/** An XML element that has a location. */
abstract class XMLLocatable extends @xmllocatable {
/** The source location for this element. */
/** Gets the source location for this element. */
Location getLocation() { xmllocations(this,result) }
/**
@@ -33,46 +33,49 @@ abstract class XMLLocatable extends @xmllocatable {
*/
class XMLParent extends @xmlparent {
/**
* A printable representation of this XML parent.
* Gets a printable representation of this XML parent.
* (Intended to be overridden in subclasses.)
*/
abstract string getName();
/** The file to which this XML parent belongs. */
/** Gets the file to which this XML parent belongs. */
XMLFile getFile() { result = this or xmlElements(this,_,_,_,result) }
/** The child element at a specified index of this XML parent. */
/** Gets the child element at a specified index of this XML parent. */
XMLElement getChild(int index) { xmlElements(result, _, this, index, _) }
/** A child element of this XML parent. */
/** Gets a child element of this XML parent. */
XMLElement getAChild() { xmlElements(result,_,this,_,_) }
/** A child element of this XML parent with the given `name`. */
/** Gets a child element of this XML parent with the given `name`. */
XMLElement getAChild(string name) { xmlElements(result,_,this,_,_) and result.hasName(name) }
/** A comment that is a child of this XML parent. */
/** Gets a comment that is a child of this XML parent. */
XMLComment getAComment() { xmlComments(result,_,this,_) }
/** A character sequence that is a child of this XML parent. */
/** Gets a character sequence that is a child of this XML parent. */
XMLCharacters getACharactersSet() { xmlChars(result,_,this,_,_,_) }
/** The depth in the tree. (Overridden in XMLElement.) */
/** Gets the depth in the tree. (Overridden in XMLElement.) */
int getDepth() { result = 0 }
/** The number of child XML elements of this XML parent. */
/** Gets the number of child XML elements of this XML parent. */
int getNumberOfChildren() {
result = count(XMLElement e | xmlElements(e,_,this,_,_))
}
/** The number of places in the body of this XML parent where text occurs. */
/** Gets the number of places in the body of this XML parent where text occurs. */
int getNumberOfCharacterSets() {
result = count(int pos | xmlChars(_,_,this,pos,_,_))
}
/**
* DEPRECATED: Internal.
*
* Append the character sequences of this XML parent from left to right, separated by a space,
* up to a specified (zero-based) index.
*/
deprecated
string charsSetUpTo(int n) {
(n = 0 and xmlChars(_,result,this,0,_,_)) or
(n > 0 and exists(string chars | xmlChars(_,chars,this,n,_,_) |
@@ -81,18 +84,15 @@ class XMLParent extends @xmlparent {
/** Append all the character sequences of this XML parent from left to right, separated by a space. */
string allCharactersString() {
exists(int n | n = this.getNumberOfCharacterSets() |
(n = 0 and result = "") or
(n > 0 and result = this.charsSetUpTo(n-1))
)
result = concat(string chars, int pos | xmlChars(_, chars, this, pos, _, _) | chars, " " order by pos)
}
/** The text value contained in this XML parent. */
/** Gets the text value contained in this XML parent. */
string getTextValue() {
result = allCharactersString()
}
/** A printable representation of this XML parent. */
/** Gets a printable representation of this XML parent. */
string toString() { result = this.getName() }
}
@@ -102,46 +102,46 @@ class XMLFile extends XMLParent, File {
xmlEncoding(this,_)
}
/** A printable representation of this XML file. */
/** Gets a printable representation of this XML file. */
override
string toString() { result = XMLParent.super.toString() }
/** The name of this XML file. */
/** Gets the name of this XML file. */
override
string getName() { result = File.super.getAbsolutePath() }
/** The encoding of this XML file. */
/** Gets the encoding of this XML file. */
string getEncoding() { xmlEncoding(this,result) }
/** The XML file itself. */
/** Gets the XML file itself. */
override
XMLFile getFile() { result = this }
/** A top-most element in an XML file. */
/** Gets a top-most element in an XML file. */
XMLElement getARootElement() { result = this.getAChild() }
/** A DTD associated with this XML file. */
/** Gets a DTD associated with this XML file. */
XMLDTD getADTD() { xmlDTDs(result,_,_,_,this) }
}
/** A "Document Type Definition" of an XML file. */
class XMLDTD extends @xmldtd {
/** The name of the root element of this DTD. */
/** Gets the name of the root element of this DTD. */
string getRoot() { xmlDTDs(this,result,_,_,_) }
/** The public ID of this DTD. */
/** Gets the public ID of this DTD. */
string getPublicId() { xmlDTDs(this,_,result,_,_) }
/** The system ID of this DTD. */
/** Gets the system ID of this DTD. */
string getSystemId() { xmlDTDs(this,_,_,result,_) }
/** Holds if this DTD is public. */
predicate isPublic() { not xmlDTDs(this,_,"",_,_) }
/** The parent of this DTD. */
/** Gets the parent of this DTD. */
XMLParent getParent() { xmlDTDs(this,_,_,_,result) }
/** A printable representation of this DTD. */
/** Gets a printable representation of this DTD. */
string toString() {
(this.isPublic() and result = this.getRoot() + " PUBLIC '" +
this.getPublicId() + "' '" +
@@ -157,37 +157,37 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
/** Holds if this XML element has the given `name`. */
predicate hasName(string name) { name = getName() }
/** The name of this XML element. */
/** Gets the name of this XML element. */
override
string getName() { xmlElements(this,result,_,_,_) }
/** The XML file in which this XML element occurs. */
/** Gets the XML file in which this XML element occurs. */
override
XMLFile getFile() { xmlElements(this,_,_,_,result) }
/** The parent of this XML element. */
/** Gets the parent of this XML element. */
XMLParent getParent() { xmlElements(this,_,result,_,_) }
/** The index of this XML element among its parent's children. */
/** Gets the index of this XML element among its parent's children. */
int getIndex() { xmlElements(this, _, _, result, _) }
/** Holds if this XML element has a namespace. */
predicate hasNamespace() { xmlHasNs(this,_,_) }
/** The namespace of this XML element, if any. */
/** Gets the namespace of this XML element, if any. */
XMLNamespace getNamespace() { xmlHasNs(this,result,_) }
/** The index of this XML element among its parent's children. */
/** Gets the index of this XML element among its parent's children. */
int getElementPositionIndex() { xmlElements(this,_,_,result,_) }
/** The depth of this element within the XML file tree structure. */
/** Gets the depth of this element within the XML file tree structure. */
override
int getDepth() { result = this.getParent().getDepth() + 1 }
/** An XML attribute of this XML element. */
/** Gets an XML attribute of this XML element. */
XMLAttribute getAnAttribute() { result.getElement() = this }
/** The attribute with the specified `name`, if any. */
/** Gets the attribute with the specified `name`, if any. */
XMLAttribute getAttribute(string name) {
result.getElement() = this and result.getName() = name
}
@@ -197,49 +197,49 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
exists(XMLAttribute a| a = this.getAttribute(name))
}
/** The value of the attribute with the specified `name`, if any. */
/** Gets the value of the attribute with the specified `name`, if any. */
string getAttributeValue(string name) {
result = this.getAttribute(name).getValue()
}
/** A printable representation of this XML element. */
/** Gets a printable representation of this XML element. */
override
string toString() { result = XMLParent.super.toString() }
}
/** An attribute that occurs inside an XML element. */
class XMLAttribute extends @xmlattribute, XMLLocatable {
/** The name of this attribute. */
/** Gets the name of this attribute. */
string getName() { xmlAttrs(this,_,result,_,_,_) }
/** The XML element to which this attribute belongs. */
/** Gets the XML element to which this attribute belongs. */
XMLElement getElement() { xmlAttrs(this,result,_,_,_,_) }
/** Holds if this attribute has a namespace. */
predicate hasNamespace() { xmlHasNs(this,_,_) }
/** The namespace of this attribute, if any. */
/** Gets the namespace of this attribute, if any. */
XMLNamespace getNamespace() { xmlHasNs(this,result,_) }
/** The value of this attribute. */
/** Gets the value of this attribute. */
string getValue() { xmlAttrs(this,_,_,result,_,_) }
/** A printable representation of this XML attribute. */
/** Gets a printable representation of this XML attribute. */
override string toString() { result = this.getName() + "=" + this.getValue() }
}
/** A namespace used in an XML file */
class XMLNamespace extends @xmlnamespace {
/** The prefix of this namespace. */
/** Gets the prefix of this namespace. */
string getPrefix() { xmlNs(this,result,_,_) }
/** The URI of this namespace. */
/** Gets the URI of this namespace. */
string getURI() { xmlNs(this,_,result,_) }
/** Holds if this namespace has no prefix. */
predicate isDefault() { this.getPrefix() = "" }
/** A printable representation of this XML namespace. */
/** Gets a printable representation of this XML namespace. */
string toString() {
(this.isDefault() and result = this.getURI()) or
(not this.isDefault() and result = this.getPrefix() + ":" + this.getURI())
@@ -248,13 +248,13 @@ class XMLNamespace extends @xmlnamespace {
/** A comment of the form `<!-- ... -->` is an XML comment. */
class XMLComment extends @xmlcomment, XMLLocatable {
/** The text content of this XML comment. */
/** Gets the text content of this XML comment. */
string getText() { xmlComments(this,result,_,_) }
/** The parent of this XML comment. */
/** Gets the parent of this XML comment. */
XMLParent getParent() { xmlComments(this,_,result,_) }
/** A printable representation of this XML comment. */
/** Gets a printable representation of this XML comment. */
override string toString() { result = this.getText() }
}
@@ -263,15 +263,15 @@ class XMLComment extends @xmlcomment, XMLLocatable {
* closing tags of an XML element, excluding other elements.
*/
class XMLCharacters extends @xmlcharacters, XMLLocatable {
/** The content of this character sequence. */
/** Gets the content of this character sequence. */
string getCharacters() { xmlChars(this,result,_,_,_,_) }
/** The parent of this character sequence. */
/** Gets the parent of this character sequence. */
XMLParent getParent() { xmlChars(this,_,result,_,_,_) }
/** Holds if this character sequence is CDATA. */
predicate isCDATA() { xmlChars(this,_,_,_,1,_) }
/** A printable representation of this XML character sequence. */
/** Gets a printable representation of this XML character sequence. */
override string toString() { result = this.getCharacters() }
}

View File

@@ -614,6 +614,26 @@ module TaintTracking {
}
/**
* A check of the form `if(<isWhitelisted>(x))`, which sanitizes `x` in its "then" branch.
*
* `<isWhitelisted>` is a call with callee name 'safe', 'whitelist', 'allow', or similar.
*
* This sanitizer is not enabled by default.
*/
class AdHocWhitelistCheckSanitizer extends SanitizerGuardNode, DataFlow::CallNode {
AdHocWhitelistCheckSanitizer() {
getCalleeName().regexpMatch("(?i).*((?<!un)safe|whitelist|allow|(?<!un)auth(?!or\\b)).*") and
getNumArgument() = 1
}
override predicate sanitizes(boolean outcome, Expr e) {
outcome = true and
e = getArgument(0).asExpr()
}
}
/** A check of the form `if(x in o)`, which sanitizes `x` in its "then" branch. */
class InSanitizer extends AdditionalSanitizerGuardNode, DataFlow::ValueNode {

View File

@@ -479,6 +479,17 @@ module Express {
methodName = "header"
)
or
exists (DataFlow::PropRead headers |
// `req.headers.name`
kind = "header" and
headers.accesses(request, "headers") and
this = headers.getAPropertyRead())
or
exists (string propName | propName = "host" or propName = "hostname" |
// `req.host` and `req.hostname` are derived from headers
kind = "header" and
this.(DataFlow::PropRead).accesses(request, propName))
or
// `req.cookies`
kind = "cookie" and
this.(DataFlow::PropRef).accesses(request, "cookies")
@@ -785,7 +796,8 @@ module Express {
override MethodCallExpr astNode;
ResponseSendFileAsFileSystemAccess() {
asExpr().(MethodCallExpr).calls(any(ResponseExpr res), "sendFile")
exists (string name | name = "sendFile" or name = "sendfile" |
asExpr().(MethodCallExpr).calls(any(ResponseExpr res), name))
}
override DataFlow::Node getDataNode() {

View File

@@ -49,6 +49,11 @@ module CorsMisconfigurationForCredentials {
super.isSanitizer(node) or
node instanceof Sanitizer
}
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
guard instanceof TaintTracking::AdHocWhitelistCheckSanitizer
}
}
/** A source of remote user input, considered as a flow source for CORS misconfiguration. */