Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions change-notes/1.24/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
- [ws](https://github.com/websockets/ws)
- [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
- [Koa](https://www.npmjs.com/package/koa)
- [lazy-cache](https://www.npmjs.com/package/lazy-cache)
- [for-in](https://www.npmjs.com/package/for-in)
- [for-own](https://www.npmjs.com/package/for-own)

## New queries

Expand Down
48 changes: 47 additions & 1 deletion javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ SourceNode getAnEnumeratedArrayElement(SourceNode array) {
*/
abstract class EnumeratedPropName extends DataFlow::Node {
/**
* Gets the object whose properties are being enumerated.
* Gets the data flow node holding the object whose properties are being enumerated.
*
* For example, gets `src` in `for (var key in src)`.
*/
Expand Down Expand Up @@ -120,6 +120,52 @@ class EntriesEnumeratedPropName extends EnumeratedPropName {
}
}

/**
* Gets a function that enumerates object properties when invoked.
*
* Invocations takes the following form:
* ```js
* fn(obj, (value, key, o) => { ... })
* ```
*/
SourceNode propertyEnumerator() {
result = moduleImport("for-own") or
result = moduleImport("for-in") or
result = moduleMember("ramda", "forEachObjIndexed") or
result = LodashUnderscore::member("forEach") or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the evaluation turns out fine, we should consider adding jQuery's forEach..

This particular predicate can probably be extended with a thousand more cases though...

result = LodashUnderscore::member("each")
}

/**
* Property enumeration through a library function taking a callback.
*/
class LibraryCallbackEnumeratedPropName extends EnumeratedPropName {
CallNode call;
FunctionNode callback;

LibraryCallbackEnumeratedPropName() {
call = propertyEnumerator().getACall() and
callback = call.getCallback(1) and
this = callback.getParameter(1)
}

override Node getSourceObject() {
result = call.getArgument(0)
}

override SourceNode getASourceObjectRef() {
result = super.getASourceObjectRef()
or
result = callback.getParameter(2)
}

override SourceNode getASourceProp() {
result = super.getASourceProp()
or
result = callback.getParameter(0)
}
}

/**
* Holds if the properties of `node` are enumerated locally.
*/
Expand Down
1 change: 1 addition & 0 deletions javascript/ql/src/javascript.qll
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.jQuery
import semmle.javascript.frameworks.Handlebars
import semmle.javascript.frameworks.LazyCache
import semmle.javascript.frameworks.LodashUnderscore
import semmle.javascript.frameworks.Logging
import semmle.javascript.frameworks.HttpFrameworks
Expand Down
55 changes: 55 additions & 0 deletions javascript/ql/src/semmle/javascript/frameworks/LazyCache.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Models imports through the NPM `lazy-cache` package.
*/

import javascript

module LazyCache {
/**
* A lazy-cache object, usually created through an expression of form `require('lazy-cache')(require)`.
*/
class LazyCacheObject extends DataFlow::SourceNode {
LazyCacheObject() {
// Use `require` directly instead of `moduleImport` to avoid recursion.
// For the same reason, avoid `Import.getImportedPath`.
exists(Require req |
req.getArgument(0).getStringValue() = "lazy-cache" and
this = req.flow().(DataFlow::SourceNode).getAnInvocation()
)
}
}

/**
* An import through `lazy-cache`.
*/
class LazyCacheImport extends CallExpr, Import {
LazyCacheObject cache;

LazyCacheImport() { this = cache.getACall().asExpr() }

/** Gets the name of the package as it's exposed on the lazy-cache object. */
string getLocalAlias() {
result = getArgument(1).getStringValue()
or
not exists(getArgument(1)) and
result = getArgument(0).getStringValue()
}

override Module getEnclosingModule() { result = getTopLevel() }

override PathExpr getImportedPath() { result = getArgument(0) }

override DataFlow::Node getImportedModuleNode() {
result = this.flow()
or
result = cache.getAPropertyRead(getLocalAlias())
}
}

/** A constant path element appearing in a call to a lazy-cache object. */
private class LazyCachePathExpr extends PathExprInModule, ConstantString {
LazyCachePathExpr() { this = any(LazyCacheImport rp).getArgument(0) }

override string getValue() { result = getStringValue() }
}
}
Loading