Merge branch 'main' into amammad-js-CodeInjection_execa

This commit is contained in:
amammad
2023-10-11 13:27:26 +02:00
committed by GitHub
1963 changed files with 168686 additions and 95124 deletions

11
javascript/BUILD.bazel Normal file
View File

@@ -0,0 +1,11 @@
package(default_visibility = ["//visibility:public"])
alias(
name = "dbscheme",
actual = "//javascript/ql/lib:dbscheme",
)
alias(
name = "dbscheme-stats",
actual = "//javascript/ql/lib:dbscheme-stats",
)

View File

@@ -361,7 +361,10 @@ function handleParseCommand(command: ParseCommand, checkPending = true) {
let filename = command.filename;
let expectedFilename = state.pendingFiles[state.pendingFileIndex];
if (expectedFilename !== filename && checkPending) {
throw new Error("File requested out of order. Expected '" + expectedFilename + "' but got '" + filename + "'");
// File was requested out of order. This happens in rare cases because the Java process decided against extracting it,
// for example because it was too large. Just recover and accept that some work was wasted.
state.pendingResponse = null;
state.pendingFileIndex = state.pendingFiles.indexOf(filename);
}
++state.pendingFileIndex;
let response = state.pendingResponse || extractFile(command.filename);

View File

@@ -2708,7 +2708,8 @@ public class Parser {
Matcher m = Whitespace.skipWhiteSpace.matcher(this.input);
m.find(this.pos);
int next = m.end();
return !Whitespace.lineBreakG.matcher(inputSubstring(this.pos, next)).matches()
return this.input.length() > next &&
!Whitespace.lineBreakG.matcher(inputSubstring(this.pos, next)).matches()
&& Identifiers.isIdentifierChar(this.input.codePointAt(next), false);
}

View File

@@ -0,0 +1,15 @@
load("@rules_pkg//:mappings.bzl", "pkg_files")
package(default_visibility = ["//javascript:__pkg__"])
pkg_files(
name = "dbscheme",
srcs = ["semmlecode.javascript.dbscheme"],
prefix = "javascript",
)
pkg_files(
name = "dbscheme-stats",
srcs = ["semmlecode.javascript.dbscheme.stats"],
prefix = "javascript",
)

View File

@@ -1,3 +1,13 @@
## 0.7.5
No user-facing changes.
## 0.7.4
### Major Analysis Improvements
* Added support for TypeScript 5.2.
## 0.7.3
No user-facing changes.

View File

@@ -1,4 +0,0 @@
---
category: majorAnalysis
---
* Added support for TypeScript 5.2.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.

View File

@@ -0,0 +1,9 @@
---
category: minorAnalysis
---
* Deleted the deprecated `getAnImmediateUse`, `getAUse`, `getARhs`, and `getAValueReachingRhs` predicates from the `API::Node` class.
* Deleted the deprecated `mayReferToParameter` predicate from `DataFlow::Node`.
* Deleted the deprecated `getStaticMethod` and `getAStaticMethod` predicates from `DataFlow::ClassNode`.
* Deleted the deprecated `isLibaryFile` predicate from `ClassifyFiles.qll`, use `isLibraryFile` instead.
* Deleted many library models that were build on the AST. Use the new models that are build on the dataflow library instead.
* Deleted the deprecated `semmle.javascript.security.performance` folder, use `semmle.javascript.security.regexp` instead.

View File

@@ -0,0 +1,5 @@
## 0.7.4
### Major Analysis Improvements
* Added support for TypeScript 5.2.

View File

@@ -0,0 +1,3 @@
## 0.7.5
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.3
lastReleaseVersion: 0.7.5

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-all
version: 0.7.4-dev
version: 0.8.0-dev
groups: javascript
dbscheme: semmlecode.javascript.dbscheme
extractor: javascript

View File

@@ -7,6 +7,34 @@ import javascript
private import semmle.javascript.internal.CachedStages
private import Expressions.ExprHasNoEffect
/**
* Companion module to the `AmdModuleDefinition` class.
*/
module AmdModuleDefinition {
/**
* A class that can be extended to treat calls as instances of `AmdModuleDefinition`.
*
* Subclasses should not depend on imports or `DataFlow::Node`.
*/
abstract class Range extends CallExpr { }
private class DefaultRange extends Range {
DefaultRange() {
inVoidContext(this) and
this.getCallee().(GlobalVarAccess).getName() = "define" and
exists(int n | n = this.getNumArgument() |
n = 1
or
n = 2 and this.getArgument(0) instanceof ArrayExpr
or
n = 3 and
this.getArgument(0) instanceof ConstantString and
this.getArgument(1) instanceof ArrayExpr
)
}
}
}
/**
* An AMD `define` call.
*
@@ -25,21 +53,7 @@ private import Expressions.ExprHasNoEffect
* where the first argument is the module name, the second argument an
* array of dependencies, and the third argument a factory method or object.
*/
class AmdModuleDefinition extends CallExpr {
AmdModuleDefinition() {
inVoidContext(this) and
this.getCallee().(GlobalVarAccess).getName() = "define" and
exists(int n | n = this.getNumArgument() |
n = 1
or
n = 2 and this.getArgument(0) instanceof ArrayExpr
or
n = 3 and
this.getArgument(0) instanceof ConstantString and
this.getArgument(1) instanceof ArrayExpr
)
}
class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range {
/** Gets the array of module dependencies, if any. */
ArrayExpr getDependencies() {
result = this.getArgument(0) or

View File

@@ -153,12 +153,6 @@ module API {
*/
DataFlow::SourceNode asSource() { Impl::use(this, result) }
/** DEPRECATED. This predicate has been renamed to `asSource`. */
deprecated DataFlow::SourceNode getAnImmediateUse() { result = this.asSource() }
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource`. */
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
/**
* Gets a call to the function represented by this API component.
*/
@@ -212,12 +206,6 @@ module API {
*/
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
/** DEPRECATED. This predicate has been renamed to `asSink`. */
deprecated DataFlow::Node getARhs() { result = this.asSink() }
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink`. */
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
/**
* Gets a node representing member `m` of this API component.
*
@@ -622,12 +610,6 @@ module API {
bindingset[this]
EntryPoint() { any() }
/** DEPRECATED. This predicate has been renamed to `getASource`. */
deprecated DataFlow::SourceNode getAUse() { none() }
/** DEPRECATED. This predicate has been renamed to `getASink`. */
deprecated DataFlow::SourceNode getARhs() { none() }
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
DataFlow::SourceNode getASource() { none() }

View File

@@ -2,166 +2,34 @@
import javascript
private import NodeModuleResolutionImpl
private import codeql.util.FileSystem
/** A file or folder. */
abstract class Container extends @container {
/**
* Gets the absolute, canonical path of this container, using forward slashes
* as path separator.
*
* The path starts with a _root prefix_ followed by zero or more _path
* segments_ separated by forward slashes.
*
* The root prefix is of one of the following forms:
*
* 1. A single forward slash `/` (Unix-style)
* 2. An upper-case drive letter followed by a colon and a forward slash,
* such as `C:/` (Windows-style)
* 3. Two forward slashes, a computer name, and then another forward slash,
* such as `//FileServer/` (UNC-style)
*
* Path segments are never empty (that is, absolute paths never contain two
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
* segments never contain forward slashes, and no path segment is of the
* form `.` (one dot) or `..` (two dots).
*
* Note that an absolute path never ends with a forward slash, except if it is
* a bare root prefix, that is, the path has no path segments. A container
* whose absolute path has no segments is always a `Folder`, not a `File`.
*/
abstract string getAbsolutePath();
private module FsInput implements InputSig {
abstract class ContainerBase extends @container {
abstract string getAbsolutePath();
/**
* Gets a URL representing the location of this container.
*
* For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls).
*/
abstract string getURL();
ContainerBase getParentContainer() { containerparent(result, this) }
/**
* Gets the relative path of this file or folder from the root folder of the
* analyzed source location. The relative path of the root folder itself is
* the empty string.
*
* This has no result if the container is outside the source root, that is,
* if the root folder is not a reflexive, transitive parent of this container.
*/
string getRelativePath() {
exists(string absPath, string pref |
absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
|
absPath = pref and result = ""
or
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
not result.matches("/%")
)
string toString() { result = this.getAbsolutePath() }
}
/**
* Gets the base name of this container including extension, that is, the last
* segment of its absolute path, or the empty string if it has no segments.
*
* Here are some examples of absolute paths and the corresponding base names
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Base name</th></tr>
* <tr><td>"/tmp/tst.js"</td><td>"tst.js"</td></tr>
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
* <tr><td>"/"</td><td>""</td></tr>
* <tr><td>"C:/"</td><td>""</td></tr>
* <tr><td>"D:/"</td><td>""</td></tr>
* <tr><td>"//FileServer/"</td><td>""</td></tr>
* </table>
*/
string getBaseName() {
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 1)
class FolderBase extends ContainerBase, @folder {
override string getAbsolutePath() { folders(this, result) }
}
/**
* Gets the extension of this container, that is, the suffix of its base name
* after the last dot character, if any.
*
* In particular,
*
* - if the name does not include a dot, there is no extension, so this
* predicate has no result;
* - if the name ends in a dot, the extension is the empty string;
* - if the name contains multiple dots, the extension follows the last dot.
*
* Here are some examples of absolute paths and the corresponding extensions
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Extension</th></tr>
* <tr><td>"/tmp/tst.js"</td><td>"js"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
* </table>
*/
string getExtension() {
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 4)
class FileBase extends ContainerBase, @file {
override string getAbsolutePath() { files(this, result) }
}
/**
* Gets the stem of this container, that is, the prefix of its base name up to
* (but not including) the last dot character if there is one, or the entire
* base name if there is not.
*
* Here are some examples of absolute paths and the corresponding stems
* (surrounded with quotes to avoid ambiguity):
*
* <table border="1">
* <tr><th>Absolute path</th><th>Stem</th></tr>
* <tr><td>"/tmp/tst.js"</td><td>"tst"</td></tr>
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
* </table>
*/
string getStem() {
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 2)
}
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }
/** Gets a file or sub-folder in this container. */
Container getAChildContainer() { this = result.getParentContainer() }
/** Gets a file in this container. */
File getAFile() { result = this.getAChildContainer() }
/** Gets the file in this container that has the given `baseName`, if any. */
File getFile(string baseName) {
result = this.getAFile() and
result.getBaseName() = baseName
}
/** Gets a sub-folder in this container. */
Folder getAFolder() { result = this.getAChildContainer() }
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
Folder getFolder(string baseName) {
result = this.getAFolder() and
result.getBaseName() = baseName
}
/**
* Gets a textual representation of the path of this container.
*
* This is the absolute path of the container.
*/
string toString() { result = this.getAbsolutePath() }
predicate hasSourceLocationPrefix = sourceLocationPrefix/1;
}
/** A folder. */
class Folder extends Container, @folder {
override string getAbsolutePath() { folders(this, result) }
private module Impl = Make<FsInput>;
class Container = Impl::Container;
/** A folder. */
class Folder extends Container, Impl::Folder {
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
Container getChildContainer(string name) {
result = this.getAChildContainer() and
@@ -206,13 +74,10 @@ class Folder extends Container, @folder {
/** Gets a subfolder contained in this folder. */
Folder getASubFolder() { result = this.getAChildContainer() }
/** Gets the URL of this folder. */
override string getURL() { result = "folder://" + this.getAbsolutePath() }
}
/** A file. */
class File extends Container, @file {
class File extends Container, Impl::File {
/**
* Gets the location of this file.
*
@@ -220,8 +85,6 @@ class File extends Container, @file {
*/
Location getLocation() { hasLocation(this, result) }
override string getAbsolutePath() { files(this, result) }
/** Gets the number of lines in this file. */
int getNumberOfLines() { result = sum(int loc | numlines(this, loc, _, _) | loc) }
@@ -234,9 +97,6 @@ class File extends Container, @file {
/** Gets a toplevel piece of JavaScript code in this file. */
TopLevel getATopLevel() { result.getFile() = this }
/** Gets the URL of this file. */
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
/**
* Holds if line number `lineno` of this file is indented to depth `d`
* using character `c`.

View File

@@ -138,14 +138,6 @@ module DataFlow {
CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this)
}
/**
* DEPRECATED: Use `DataFlow::ParameterNode::flowsTo()` instead.
* Holds if this expression may refer to the initial value of parameter `p`.
*/
deprecated predicate mayReferToParameter(Parameter p) {
parameterNode(p).(SourceNode).flowsTo(this)
}
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
@@ -1277,6 +1269,41 @@ module DataFlow {
result >= 0 and kind = "call" and result = originalCall.getNumArgument() - 1
}
}
/**
* A data flow node representing a call with a tagged template literal.
*/
private class TaggedTemplateLiteralCallNode extends CallNodeDef, ValueNode {
override TaggedTemplateExpr astNode;
override InvokeExpr getInvokeExpr() { none() } // There is no InvokeExpr for this.
override string getCalleeName() {
result = astNode.getTag().getUnderlyingValue().(Identifier).getName()
}
override DataFlow::Node getCalleeNode() { result = DataFlow::valueNode(astNode.getTag()) }
override DataFlow::Node getArgument(int i) {
// the first argument sent to the function is the array of string parts, which we don't model.
// rank is 1-indexed, which is perfect here.
result =
DataFlow::valueNode(rank[i](Expr e, int index |
e = astNode.getTemplate().getElement(index) and not e instanceof TemplateElement
|
e order by index
))
}
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
override DataFlow::Node getASpreadArgument() { none() }
// we don't model the string constants as arguments, but we still count them.
override int getNumArgument() { result = count(this.getArgument(_)) + 1 }
override DataFlow::Node getReceiver() { none() }
}
}
/**

View File

@@ -92,13 +92,20 @@ class InvokeNode extends DataFlow::SourceNode instanceof DataFlow::Impl::InvokeN
* but the position of `z` cannot be determined, hence there are no first and second
* argument nodes.
*/
DataFlow::Node getArgument(int i) { result = super.getArgument(i) }
cached
DataFlow::Node getArgument(int i) {
result = super.getArgument(i) and Stages::DataFlowStage::ref()
}
/** Gets the data flow node corresponding to an argument of this invocation. */
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
cached
DataFlow::Node getAnArgument() { result = super.getAnArgument() and Stages::DataFlowStage::ref() }
/** Gets the data flow node corresponding to the last argument of this invocation. */
DataFlow::Node getLastArgument() { result = this.getArgument(this.getNumArgument() - 1) }
cached
DataFlow::Node getLastArgument() {
result = this.getArgument(this.getNumArgument() - 1) and Stages::DataFlowStage::ref()
}
/**
* Gets a data flow node corresponding to an array of values being passed as
@@ -1150,30 +1157,12 @@ module ClassNode {
cached
abstract FunctionNode getStaticMember(string name, MemberKind kind);
/**
* DEPRECATED. Override `getStaticMember` instead.
*
* Gets the static method of this class with the given name.
*/
cached
deprecated FunctionNode getStaticMethod(string name) { none() }
/**
* Gets a static member of this class of the given kind.
*/
cached
abstract FunctionNode getAStaticMember(MemberKind kind);
/**
* DEPRECATED. Override `getAStaticMember` instead.
*
* Gets a static method of this class.
*
* The constructor is not considered a static method.
*/
cached
deprecated FunctionNode getAStaticMethod() { none() }
/**
* Gets a dataflow node representing a class to be used as the super-class
* of this node.

View File

@@ -75,9 +75,6 @@ predicate isExternsFile(File f) {
*/
predicate isLibraryFile(File f) { f.getATopLevel() instanceof FrameworkLibraryInstance }
/** DEPRECATED: Alias for isLibraryFile */
deprecated predicate isLibaryFile = isLibraryFile/1;
/**
* Holds if `f` contains template code.
*/

View File

@@ -618,27 +618,6 @@ private class JQLiteObject extends JQuery::ObjectSource::Range {
}
}
/**
* DEPRECATED: Use `AngularJSCallNode` instead.
* A call to an AngularJS function.
*
* Used for exposing behavior that is similar to the behavior of other libraries.
*/
deprecated class AngularJSCall extends CallExpr {
AngularJSCallNode node;
AngularJSCall() { this.flow() = node }
/** Holds if `e` is an argument that this call interprets as HTML. */
deprecated predicate interpretsArgumentAsHtml(Expr e) { node.interpretsArgumentAsHtml(e.flow()) }
/** Holds if `e` is an argument that this call stores globally, e.g. in a cookie. */
deprecated predicate storesArgumentGlobally(Expr e) { node.storesArgumentGlobally(e.flow()) }
/** Holds if `e` is an argument that this call interprets as code. */
deprecated predicate interpretsArgumentAsCode(Expr e) { node.interpretsArgumentAsCode(e.flow()) }
}
/**
* A call to an AngularJS function.
*

View File

@@ -447,21 +447,6 @@ BuiltinServiceReference getBuiltinServiceOfKind(string kind) {
)
}
/**
* DEPRECATED: Use `ServiceRequestNode` instead.
* A request for one or more AngularJS services.
*/
deprecated class ServiceRequest extends Expr {
ServiceRequestNode node;
ServiceRequest() { this.flow() = node }
/** Gets the parameter of this request into which `service` is injected. */
deprecated Parameter getDependencyParameter(ServiceReference service) {
result.flow() = node.getDependencyParameter(service)
}
}
/**
* A request for one or more AngularJS services.
*/

View File

@@ -118,8 +118,6 @@ module Connect {
override string getCredentialsKind() { result = kind }
}
deprecated class RequestExpr = NodeJSLib::RequestExpr;
class RequestNode = NodeJSLib::RequestNode;
/**

View File

@@ -5,23 +5,6 @@
import javascript
/**
* DEPRECATED: Use `CredentialsNode` instead.
* An expression whose value is used to supply credentials such
* as a user name, a password, or a key.
*/
deprecated class CredentialsExpr extends Expr {
CredentialsNode node;
CredentialsExpr() { node.asExpr() = this }
/**
* Gets a description of the kind of credential this expression is used as,
* such as `"user name"`, `"password"`, `"key"`.
*/
deprecated string getCredentialsKind() { result = node.getCredentialsKind() }
}
/**
* An expression whose value is used to supply credentials such
* as a user name, a password, or a key.

View File

@@ -55,14 +55,6 @@ module Express {
WebpackDevServer::webpackDevServerApp().flowsTo(e)
}
/**
* DEPRECATED: Use `RouterDefinition.ref()` or `RouteSetup` instead.
* An expression that refers to a route.
*/
deprecated class RouteExpr extends MethodCallExpr {
RouteExpr() { isRouter(this.flow()) }
}
/**
* Gets the name of an Express router method that sets up a route.
*/
@@ -145,17 +137,6 @@ module Express {
/** Holds if this is a call `use`, such as `app.use(handler)`. */
predicate isUseCall() { this.getMethodName() = "use" }
/**
* DEPRECATED: Use `getRouteHandlerNode` instead.
* Gets the `n`th handler registered by this setup, with 0 being the first.
*
* This differs from `getARouteHandler` in that the argument expression is
* returned, not its dataflow source.
*/
deprecated Expr getRouteHandlerExpr(int index) {
result = this.getRouteHandlerNode(index).asExpr()
}
/**
* Gets the `n`th handler registered by this setup, with 0 being the first.
*
@@ -174,25 +155,11 @@ module Express {
)
}
/**
* DEPRECATED: Use `getARouteHandlerNode` instead.
* Gets an argument that represents a route handler being registered.
*/
deprecated Expr getARouteHandlerExpr() { result = this.getRouteHandlerExpr(_) }
/**
* Gets an argument that represents a route handler being registered.
*/
DataFlow::Node getARouteHandlerNode() { result = this.getRouteHandlerNode(_) }
/**
* DEPRECATED: Use `getLastRouteHandlerExpr` instead.
* Gets the last argument representing a route handler being registered.
*/
deprecated Expr getLastRouteHandlerExpr() {
result = max(int i | | this.getRouteHandlerExpr(i) order by i)
}
/**
* Gets the last argument representing a route handler being registered.
*/
@@ -294,52 +261,6 @@ module Express {
}
}
/**
* DEPRECATED: Use `RouteHandlerNode` instead.
* An expression used as an Express route handler, such as `submitHandler` below:
* ```
* app.post('/submit', submitHandler)
* ```
*
* Unlike `RouterHandler`, this is the argument passed to a setup, as opposed to
* a function that flows into such an argument.
*/
deprecated class RouteHandlerExpr extends Expr {
RouteHandlerNode node;
RouteHandlerExpr() { this.flow() = node }
/** Gets the setup call that registers this route handler. */
deprecated RouteSetup getSetup() { result = node.getSetup() }
/** Gets the function body of this handler, if it is defined locally. */
deprecated RouteHandler getBody() { result = node.getBody() }
/** Holds if this is not followed by more handlers. */
deprecated predicate isLastHandler() { node.isLastHandler() }
/** Gets a route handler that immediately precedes this in the route stack. */
deprecated Express::RouteHandlerExpr getPreviousMiddleware() {
result = node.getPreviousMiddleware().asExpr()
}
/** Gets a route handler that may follow immediately after this one in its route stack. */
deprecated Express::RouteHandlerExpr getNextMiddleware() {
result = node.getNextMiddleware().asExpr()
}
/**
* Gets a route handler that precedes this one (not necessarily immediately), may handle
* same request method, and matches on the same path or a prefix.
*/
deprecated Express::RouteHandlerExpr getAMatchingAncestor() {
result = node.getAMatchingAncestor().asExpr()
}
/** Gets the router being registered as a sub-router here, if any. */
deprecated RouterDefinition getAsSubRouter() { result = node.getAsSubRouter() }
}
/**
* An expression used as an Express route handler, such as `submitHandler` below:
* ```
@@ -584,14 +505,6 @@ module Express {
override RouteHandler getRouteHandler() { none() } // Not known.
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* An Express response expression.
*/
deprecated class ResponseExpr extends NodeJSLib::ResponseExpr {
ResponseExpr() { this.flow() instanceof ResponseNode }
}
/**
* An Express response expression.
*/
@@ -599,14 +512,6 @@ module Express {
override ResponseSource src;
}
/**
* DEPRECATED: Use `RequestNode` instead.
* An Express request expression.
*/
deprecated class RequestExpr extends NodeJSLib::RequestExpr {
RequestExpr() { this.flow() instanceof RequestNode }
}
/**
* An Express request expression.
*/

View File

@@ -154,12 +154,6 @@ module Fastify {
override DataFlow::SourceNode getServer() { result = server }
/**
* DEPRECATED: Use `getARouteHandlerNode` instead.
* Gets an argument that represents a route handler being registered.
*/
deprecated DataFlow::Node getARouteHandlerExpr() { result = this.getARouteHandlerNode() }
/** Gets an argument that represents a route handler being registered. */
DataFlow::Node getARouteHandlerNode() {
if methodName = "route"

View File

@@ -65,23 +65,9 @@ module Http {
)
}
/**
* DEPRECATED: use `definesHeaderValue` instead.
* Holds if the header with (lower-case) name `headerName` is set to the value of `headerValue`.
*/
deprecated predicate definesExplicitly(string headerName, Expr headerValue) {
this.definesHeaderValue(headerName, headerValue.flow())
}
/** Holds if the header with (lower-case) name `headerName` is set to the value of `headerValue`. */
abstract predicate definesHeaderValue(string headerName, DataFlow::Node headerValue);
/**
* DEPRECATED: Use `getNameNode()` instead.
* Returns the expression used to compute the header name.
*/
deprecated Expr getNameExpr() { result = this.getNameNode().asExpr() }
/** Returns the expression used to compute the header name. */
abstract DataFlow::Node getNameNode();
}
@@ -202,26 +188,12 @@ module Http {
*/
final Servers::ResponseSource getAResponseSource() { result.getRouteHandler() = this }
/**
* DEPRECATED: Use `getARequestNode()` instead.
* Gets an expression that contains a request object handled
* by this handler.
*/
deprecated RequestExpr getARequestExpr() { result.flow() = this.getARequestNode() }
/**
* Gets an expression that contains a request object handled
* by this handler.
*/
RequestNode getARequestNode() { result.getRouteHandler() = this }
/**
* DEPRECATED: Use `getAResponseNode()` instead.
* Gets an expression that contains a response object provided
* by this handler.
*/
deprecated ResponseExpr getAResponseExpr() { result.flow() = this.getAResponseNode() }
/**
* Gets an expression that contains a response object provided
* by this handler.
@@ -265,30 +237,6 @@ module Http {
abstract RouteHandler getRouteHandler();
}
/**
* DEPRECATED: Use `RequestNode` instead.
* An expression that may contain a request object.
*/
deprecated class RequestExpr extends Expr {
RequestExpr() { this.flow() instanceof ResponseNode }
/**
* Gets the route handler that handles this request.
*/
RouteHandler getRouteHandler() { result = this.flow().(ResponseNode).getRouteHandler() }
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* An expression that may contain a response object.
*/
deprecated class ResponseExpr extends Expr {
/**
* Gets the route handler that handles this request.
*/
RouteHandler getRouteHandler() { result = this.flow().(ResponseNode).getRouteHandler() }
}
/**
* Boiler-plate implementation of a `Server` and its associated classes.
* Made for easily defining new HTTP servers
@@ -309,12 +257,6 @@ module Http {
/** Gets a data flow node referring to this server. */
DataFlow::SourceNode ref() { result = this.ref(DataFlow::TypeTracker::end()) }
/**
* DEPRECATED: Use `ref().flowsToExpr()` instead.
* Holds if `sink` may refer to this server definition.
*/
deprecated predicate flowsTo(Expr sink) { this.ref().flowsToExpr(sink) }
}
/**
@@ -402,30 +344,6 @@ module Http {
override RouteHandler getRouteHandler() { result = src.getRouteHandler() }
}
/**
* A request expression arising from a request source.
*/
deprecated class StandardRequestExpr extends RequestExpr {
RequestSource src;
StandardRequestExpr() { src.ref().flowsToExpr(this) }
override RouteHandler getRouteHandler() { result = src.getRouteHandler() }
}
/**
* A response expression arising from a response source.
*/
deprecated class StandardResponseExpr extends ResponseExpr {
ResponseSource src;
StandardResponseExpr() { src.ref().flowsToExpr(this) }
override RouteHandler getRouteHandler() {
result = this.flow().(StandardResponseNode).getRouteHandler()
}
}
/**
* A standard header definition.
*/

View File

@@ -87,14 +87,6 @@ module Hapi {
override RouteHandler getRouteHandler() { result = rh }
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* A Hapi response expression.
*/
deprecated class ResponseExpr extends HTTP::Servers::StandardResponseExpr {
ResponseExpr() { this.flow() instanceof ResponseNode }
}
/**
* A Hapi response node.
*/
@@ -102,14 +94,6 @@ module Hapi {
override ResponseSource src;
}
/**
* DEPRECATED: Use `RequestNode` instead.
* An Hapi request expression.
*/
deprecated class RequestExpr extends HTTP::Servers::StandardRequestExpr {
RequestExpr() { this.flow() instanceof RequestNode }
}
/**
* A Hapi request node.
*/
@@ -255,8 +239,6 @@ module Hapi {
pragma[noinline]
private DataFlow::Node getRouteHandler() { result = handler }
deprecated Expr getRouteHandlerExpr() { result = handler.asExpr() }
override DataFlow::Node getServer() { result = server }
}

View File

@@ -44,13 +44,6 @@ module Koa {
result = this.getAFunctionValue().getParameter(0)
}
/**
* DEPRECATED: Use `getAContextNode` instead.
* Gets an expression that contains the "context" object of
* a route handler invocation.
*/
deprecated Expr getAContextExpr() { result = this.getAContextNode().asExpr() }
/**
* Gets an expression that contains the "context" object of
* a route handler invocation.
@@ -61,15 +54,6 @@ module Koa {
*/
DataFlow::Node getAContextNode() { result.(ContextNode).getRouteHandler() = this }
/**
* DEPRECATED: Use `getAResponseOrContextNode` instead.
* Gets an expression that contains the context or response
* object of a route handler invocation.
*/
deprecated Expr getAResponseOrContextExpr() {
result = this.getAResponseOrContextNode().asExpr()
}
/**
* Gets an expression that contains the context or response
* object of a route handler invocation.
@@ -78,13 +62,6 @@ module Koa {
result = this.getAResponseNode() or result = this.getAContextNode()
}
/**
* DEPRECATED: Use `getARequestOrContextNode` instead.
* Gets an expression that contains the context or request
* object of a route handler invocation.
*/
deprecated Expr getARequestOrContextExpr() { result = this.getARequestOrContextNode().asExpr() }
/**
* Gets an expression that contains the context or request
* object of a route handler invocation.
@@ -273,19 +250,6 @@ module Koa {
override RouteHandler getRouteHandler() { result = ctx.getRouteHandler() }
}
/**
* DEPRECATED: Use `ContextNode` instead.
* An expression that may hold a Koa context object.
*/
deprecated class ContextExpr extends Expr {
ContextNode node;
ContextExpr() { node.asExpr() = this }
/** Gets the route handler that provides this response. */
deprecated RouteHandler getRouteHandler() { result = node.getRouteHandler() }
}
/**
* An expression that may hold a Koa context object.
*/
@@ -300,14 +264,6 @@ module Koa {
RouteHandler getRouteHandler() { result = src.getRouteHandler() }
}
/**
* DEPRECATED: Use `RequestNode` instead.
* An expression that may hold a Koa request object.
*/
deprecated class RequestExpr extends HTTP::Servers::StandardRequestExpr {
RequestExpr() { this.flow() instanceof RequestNode }
}
/**
* An expression that may hold a Koa request object.
*/
@@ -315,14 +271,6 @@ module Koa {
override RequestSource src;
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* An expression that may hold a Koa response object.
*/
deprecated class ResponseExpr extends HTTP::Servers::StandardResponseExpr {
ResponseExpr() { this.flow() instanceof ResponseNode }
}
/**
* An expression that may hold a Koa response object.
*/

View File

@@ -62,18 +62,10 @@ private module Micro {
override Http::RouteHandler getRouteHandler() { result = h }
}
deprecated class MicroRequestExpr extends NodeJSLib::RequestExpr {
override MicroRequestSource src;
}
class MicroRequestNode extends NodeJSLib::RequestNode {
override MicroRequestSource src;
}
deprecated class MicroReseponseExpr extends NodeJSLib::ResponseExpr {
override MicroResponseSource src;
}
class MicroResponseNode extends NodeJSLib::ResponseNode {
override MicroResponseSource src;
}

View File

@@ -64,17 +64,6 @@ module NodeJSLib {
)
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* A Node.js HTTP response.
*
* A server library that provides an (enhanced) NodesJS HTTP response
* object should implement a library specific subclass of this class.
*/
deprecated class ResponseExpr extends HTTP::Servers::StandardResponseExpr {
ResponseExpr() { this.flow() instanceof ResponseNode }
}
/**
* A Node.js HTTP response.
*
@@ -83,17 +72,6 @@ module NodeJSLib {
*/
abstract class ResponseNode extends Http::Servers::StandardResponseNode { }
/**
* DEPRECATED: Use `RequestNode` instead.
* A Node.js HTTP request.
*
* A server library that provides an (enhanced) NodesJS HTTP request
* object should implement a library specific subclass of this class.
*/
deprecated class RequestExpr extends HTTP::Servers::StandardRequestExpr {
RequestExpr() { this.flow() instanceof RequestNode }
}
/**
* A Node.js HTTP request.
*
@@ -168,14 +146,6 @@ module NodeJSLib {
override RouteHandler getRouteHandler() { result = rh }
}
/**
* DEPRECATED: Use `BuiltinRouteHandlerResponseNode` instead.
* A builtin Node.js HTTP response.
*/
deprecated private class BuiltinRouteHandlerResponseExpr extends ResponseExpr {
BuiltinRouteHandlerResponseExpr() { src instanceof ResponseSource }
}
/**
* A builtin Node.js HTTP response.
*/
@@ -183,14 +153,6 @@ module NodeJSLib {
BuiltinRouteHandlerResponseNode() { src instanceof ResponseSource }
}
/**
* DEPRECATED: Use `BuiltinRouteHandlerRequestNode` instead.
* A builtin Node.js HTTP request.
*/
deprecated private class BuiltinRouteHandlerRequestExpr extends RequestExpr {
BuiltinRouteHandlerRequestExpr() { src instanceof RequestSource }
}
/**
* A builtin Node.js HTTP request.
*/
@@ -288,12 +250,6 @@ module NodeJSLib {
override DataFlow::Node getServer() { result = server }
/**
* DEPRECATED: Use `getRouteHandlerNode` instead.
* Gets the expression for the handler registered by this setup.
*/
deprecated Expr getRouteHandlerExpr() { result = handler.asExpr() }
/**
* Gets the expression for the handler registered by this setup.
*/

View File

@@ -72,14 +72,6 @@ module Restify {
override RouteHandler getRouteHandler() { result = rh }
}
/**
* DEPRECATED: Use `ResponseNode` instead.
* A Node.js HTTP response provided by Restify.
*/
deprecated class ResponseExpr extends NodeJSLib::ResponseExpr {
ResponseExpr() { src instanceof ResponseSource }
}
/**
* A Node.js HTTP response provided by Restify.
*/
@@ -87,14 +79,6 @@ module Restify {
ResponseNode() { src instanceof ResponseSource or src instanceof FormatterResponseSource }
}
/**
* DEPRECATED: Use `RequestNode` instead.
* A Node.js HTTP request provided by Restify.
*/
deprecated class RequestExpr extends NodeJSLib::RequestExpr {
RequestExpr() { src instanceof RequestSource }
}
/**
* A Node.js HTTP request provided by Restify.
*/

View File

@@ -145,6 +145,12 @@ module Stages {
exists(any(DataFlow::PropRef ref).getBase())
or
exists(any(DataFlow::ClassNode cls))
or
exists(any(DataFlow::CallNode node).getArgument(_))
or
exists(any(DataFlow::CallNode node).getAnArgument())
or
exists(any(DataFlow::CallNode node).getLastArgument())
}
}

View File

@@ -13,22 +13,6 @@ import javascript
import semmle.javascript.security.internal.SensitiveDataHeuristics
private import HeuristicNames
/**
* DEPRECATED: Use `SensitiveNode` instead.
* An expression that might contain sensitive data.
*/
deprecated class SensitiveExpr extends Expr {
SensitiveNode node;
SensitiveExpr() { node.asExpr() = this }
/** Gets a human-readable description of this expression for use in alert messages. */
deprecated string describe() { result = node.describe() }
/** Gets a classification of the kind of sensitive data this expression might contain. */
deprecated SensitiveDataClassification getClassification() { result = node.getClassification() }
}
/** An expression that might contain sensitive data. */
cached
abstract class SensitiveNode extends DataFlow::Node {

View File

@@ -18,23 +18,11 @@ class DomGlobalVariable extends GlobalVariable {
}
}
/**
* DEPRECATED: Use `isDomNode` instead.
* Holds if `e` could hold a value that comes from the DOM.
*/
deprecated predicate isDomValue(Expr e) { isDomNode(e.flow()) }
/**
* Holds if `e` could hold a value that comes from the DOM.
*/
predicate isDomNode(DataFlow::Node e) { DOM::domValueRef().flowsTo(e) }
/**
* DEPRECATED: Use `isLocationNode` instead.
* Holds if `e` could refer to the `location` property of a DOM node.
*/
deprecated predicate isLocation(Expr e) { isLocationNode(e.flow()) }
/** Holds if `e` could refer to the `location` property of a DOM node. */
predicate isLocationNode(DataFlow::Node e) {
e = DOM::domValueRef().getAPropertyReference("location")
@@ -42,43 +30,6 @@ predicate isLocationNode(DataFlow::Node e) {
e = DataFlow::globalVarRef("location")
}
/**
* DEPRECATED. In most cases, a sanitizer based on this predicate can be removed, as
* taint tracking no longer step through the properties of the location object by default.
*
* Holds if `pacc` accesses a part of `document.location` that is
* not considered user-controlled, that is, anything except
* `href`, `hash` and `search`.
*/
deprecated predicate isSafeLocationProperty(PropAccess pacc) {
exists(string prop | pacc = DOM::locationRef().getAPropertyRead(prop).asExpr() |
prop != "href" and prop != "hash" and prop != "search"
)
}
/**
* DEPRECATED: Use `DomMethodCallNode` instead.
* A call to a DOM method.
*/
deprecated class DomMethodCallExpr extends MethodCallExpr {
DomMethodCallNode node;
DomMethodCallExpr() { this.flow() = node }
/** Holds if `arg` is an argument that is interpreted as HTML. */
deprecated predicate interpretsArgumentsAsHtml(Expr arg) {
node.interpretsArgumentsAsHtml(arg.flow())
}
/** Holds if `arg` is an argument that is used as an URL. */
deprecated predicate interpretsArgumentsAsURL(Expr arg) {
node.interpretsArgumentsAsURL(arg.flow())
}
/** DEPRECATED: Alias for interpretsArgumentsAsHtml */
deprecated predicate interpretsArgumentsAsHTML(Expr arg) { this.interpretsArgumentsAsHtml(arg) }
}
/**
* A call to a DOM method.
*/
@@ -129,36 +80,6 @@ class DomMethodCallNode extends DataFlow::MethodCallNode {
)
)
}
/** DEPRECATED: Alias for interpretsArgumentsAsUrl */
deprecated predicate interpretsArgumentsAsURL(DataFlow::Node arg) {
this.interpretsArgumentsAsUrl(arg)
}
/** DEPRECATED: Alias for interpretsArgumentsAsHtml */
deprecated predicate interpretsArgumentsAsHTML(DataFlow::Node arg) {
this.interpretsArgumentsAsHtml(arg)
}
}
/**
* DEPRECATED: Use `DomPropertyWrite` instead.
* An assignment to a property of a DOM object.
*/
deprecated class DomPropWriteNode extends Assignment {
DomPropertyWrite node;
DomPropWriteNode() { this.flow() = node }
/**
* Holds if the assigned value is interpreted as HTML.
*/
predicate interpretsValueAsHtml() { node.interpretsValueAsHtml() }
/**
* Holds if the assigned value is interpreted as JavaScript via javascript: protocol.
*/
predicate interpretsValueAsJavaScriptUrl() { node.interpretsValueAsJavaScriptUrl() }
}
/**

View File

@@ -120,9 +120,6 @@ module DomBasedXss {
WriteUrlSink() { super.isXssSink() }
}
/** DEPRECATED: Alias for `WriteUrlSink`. */
deprecated class WriteURLSink = WriteUrlSink;
/**
* An expression whose value is interpreted as HTML or CSS
* and may be inserted into the DOM.

View File

@@ -20,6 +20,13 @@ module Shared {
string getVulnerabilityKind() { result = "Cross-site scripting" }
}
// import the various XSS query customizations, they populate the shared classes
private import DomBasedXssCustomizations
private import ReflectedXssCustomizations
private import StoredXssCustomizations
private import XssThroughDomCustomizations
private import ExceptionXssCustomizations
/** A sanitizer for XSS vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
@@ -138,43 +145,3 @@ module Shared {
IsEscapedInSwitchSanitizer() { this.asExpr() = getAPathEscapedInSwitch().getAUse() }
}
}
/**
* DEPRECATED: Use the `DomBasedXssCustomizations.qll` file instead.
* Provides classes and predicates for the DOM-based XSS query.
*/
deprecated module DomBasedXss {
import DomBasedXssCustomizations::DomBasedXss
}
/**
* DEPRECATED: Use the `DomBasedXssCustomizations.qll` file instead.
* Provides classes and predicates for the reflected XSS query.
*/
deprecated module ReflectedXss {
import ReflectedXssCustomizations::ReflectedXss
}
/**
* DEPRECATED: Use the `StoredXssCustomizations.qll` file instead.
* Provides classes and predicates for the stored XSS query.
*/
deprecated module StoredXss {
import StoredXssCustomizations::StoredXss
}
/**
* DEPRECATED: Use the `XssThroughDomCustomizations.qll` file instead.
* Provides classes and predicates for the XSS through DOM query.
*/
deprecated module XssThroughDom {
import XssThroughDomCustomizations::XssThroughDom
}
/**
* DEPRECATED: Use the `ExceptionXssCustomizations.qll` file instead.
* Provides classes for customizing the `ExceptionXss` query.
*/
deprecated module ExceptionXss {
import ExceptionXssCustomizations::ExceptionXss
}

View File

@@ -1,4 +0,0 @@
/** DEPRECATED. Import `semmle.javascript.security.regexp.ExponentialBackTracking` instead. */
deprecated private import semmle.javascript.security.regexp.ExponentialBackTracking as Dep
import Dep

View File

@@ -1,7 +0,0 @@
/** DEPRECATED. Import `PolynomialReDoSQuery` instead. */
import javascript
private import semmle.javascript.security.regexp.PolynomialReDoSQuery as PolynomialReDoSQuery // ignore-query-import
/** DEPRECATED. Import `PolynomialReDoSQuery` instead. */
deprecated module PolynomialReDoS = PolynomialReDoSQuery;

View File

@@ -1,4 +0,0 @@
/** DEPRECATED. Import `semmle.javascript.security.regexp.PolynomialReDoSCustomizations` instead. */
deprecated private import semmle.javascript.security.regexp.PolynomialReDoSCustomizations as Dep
import Dep

View File

@@ -1,4 +0,0 @@
/** DEPRECATED. Import `semmle.javascript.security.regexp.NfaUtils` instead. */
deprecated private import semmle.javascript.security.regexp.NfaUtils as Dep
import Dep

View File

@@ -1,4 +0,0 @@
/** DEPRECATED. Import `semmle.javascript.security.regexp.SuperlinearBackTracking` instead. */
deprecated private import semmle.javascript.security.regexp.SuperlinearBackTracking as Dep
import Dep

View File

@@ -1,3 +1,20 @@
## 0.7.5
### Bug Fixes
* Fixed an extractor crash that could occur in projects containing TypeScript files larger than 10 MB.
## 0.7.4
### Minor Analysis Improvements
* Files larger than 10 MB are no longer be extracted or analyzed.
* Imports can now be resolved in more cases, where a non-constant string expression is passed to a `require()` call.
### Bug Fixes
* Fixed an extractor crash that would occur in rare cases when a TypeScript file contains a self-referential namespace alias.
## 0.7.3
No user-facing changes.

View File

@@ -1,8 +1,137 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="IncompleteSanitization.qhelp" />
<overview>
<p>
Sanitizing untrusted input is a common technique for preventing injection attacks and other security
vulnerabilities. Regular expressions are often used to perform this sanitization. However, when the
regular expression matches multiple consecutive characters, replacing it just once
can result in the unsafe text reappearing in the sanitized input.
</p>
<p>
Attackers can exploit this issue by crafting inputs that, when sanitized with an ineffective regular
expression, still contain malicious code or content. This can lead to code execution, data exposure,
or other vulnerabilities.
</p>
</overview>
<recommendation>
<p>
To prevent this issue, it is highly recommended to use a well-tested sanitization library whenever
possible. These libraries are more likely to handle corner cases and ensure effective sanitization.
</p>
<p>
If a library is not an option, you can consider alternative strategies to fix the issue. For example,
applying the regular expression replacement repeatedly until no more replacements can be performed, or rewriting the regular
expression to match single characters instead of the entire unsafe text.
</p>
</recommendation>
<example>
<p>
Consider the following JavaScript code that aims to remove all HTML comment start and end tags:
</p>
<sample language="javascript">
str.replace(/&lt;!--|--!?&gt;/g, "");
</sample>
<p>
Given the input string "&lt;!&lt;!--- comment ---&gt;&gt;", the output will be "&lt;!-- comment --&gt;",
which still contains an HTML comment.
</p>
<p>
One possible fix for this issue is to apply the regular expression replacement repeatedly until no
more replacements can be performed. This ensures that the unsafe text does not re-appear in the sanitized input, effectively
removing all instances of the targeted pattern:
</p>
<sample language="javascript">
function removeHtmlComments(input) {
let previous;
do {
previous = input;
input = input.replace(/&lt;!--|--!?&gt;/g, "");
} while (input !== previous);
return input;
}
</sample>
</example>
<example>
<p>
Another example is the following regular expression intended to remove script tags:
</p>
<sample language="javascript">
str.replace(/&lt;script\b[^&lt;]*(?:(?!&lt;\/script&gt;)&lt;[^&lt;]*)*&lt;\/script&gt;/g, "");
</sample>
<p>
If the input string is "&lt;scrip&lt;script&gt;is removed&lt;/script&gt;t&gt;alert(123)&lt;/script&gt;",
the output will be "&lt;script&gt;alert(123)&lt;/script&gt;", which still contains a script tag.
</p>
<p>
A fix for this issue is to rewrite the regular expression to match single characters
("&lt;" and "&gt;") instead of the entire unsafe text. This simplifies the sanitization process
and ensures that all potentially unsafe characters are removed:
</p>
<sample language="javascript">
function removeAllHtmlTags(input) {
return input.replace(/&lt;|&gt;/g, "");
}
</sample>
<p>
Another potential fix is to use the popular <code>sanitize-html</code> npm library.
It keeps most of the safe HTML tags while removing all unsafe tags and attributes.
</p>
<sample language="javascript">
const sanitizeHtml = require("sanitize-html");
function removeAllHtmlTags(input) {
return sanitizeHtml(input);
}
</sample>
</example>
<example>
<p>
Lastly, consider a path sanitizer using the regular expression <code>/\.\.\//</code>:
</p>
<sample language="javascript">
str.replace(/\.\.\//g, "");
</sample>
<p>
The regular expression attempts to strip out all occurences of <code>/../</code> from <code>str</code>.
This will not work as expected: for the string <code>/./.././</code>, for example, it will remove the single
occurrence of <code>/../</code> in the middle, but the remainder of the string then becomes
<code>/../</code>, which is another instance of the substring we were trying to remove.
</p>
<p>
A possible fix for this issue is to use the "sanitize-filename" npm library for path sanitization.
This library is specifically designed to handle path sanitization, and should handle all corner cases
and ensure effective sanitization:
</p>
<sample language="javascript">
const sanitize = require("sanitize-filename");
function sanitizePath(input) {
return sanitize(input);
}
</sample>
</example>
<references>
<li>OWASP Top 10: <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">A1 Injection</a>.</li>
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/6659351/removing-all-script-tags-from-html-with-js-regular-expression">Removing all script tags from HTML with JS regular expression</a>.</li>
</references>
</qhelp>

View File

@@ -43,18 +43,6 @@ needed, for instance by using prepared statements for SQL queries.
Otherwise, make sure to use a regular expression with the <code>g</code> flag to ensure that
all occurrences are replaced, and remember to escape backslashes if applicable.
</p>
<p>
Note, however, that this is generally <i>not</i> sufficient for replacing multi-character strings:
the <code>String.prototype.replace</code> method only performs one pass over the input string,
and will not replace further instances of the string that result from earlier replacements.
</p>
<p>
For example, consider the code snippet <code>s.replace(/\/\.\.\//g, "")</code>, which attempts
to strip out all occurences of <code>/../</code> from <code>s</code>. This will not work as
expected: for the string <code>/./.././</code>, for example, it will remove the single
occurrence of <code>/../</code> in the middle, but the remainder of the string then becomes
<code>/../</code>, which is another instance of the substring we were trying to remove.
</p>
</recommendation>
<example>

View File

@@ -1 +0,0 @@
| examples/RemotePropertyInjection.js:8:8:8:11 | prop | A $@ is used as a property name to write to. | examples/RemotePropertyInjection.js:7:13:7:36 | req.que ... trolled | user-provided value |

View File

@@ -16,6 +16,10 @@ To guard against untrusted URL redirection, it is advisable to avoid putting use
directly into a redirect URL. Instead, maintain a list of authorized
redirects on the server; then choose from that list based on the user input provided.
</p>
<p>
If this is not possible, then the user input should be validated in some other way,
for example, by verifying that the target URL is on the same host as the current page.
</p>
</recommendation>
<example>
@@ -32,6 +36,21 @@ before doing the redirection:
</p>
<sample src="examples/ServerSideUrlRedirectGood.js"/>
<p>
Alternatively, we can check that the target URL does not redirect to a different host
by parsing it relative to a base URL with a known host and verifying that the host
stays the same:
</p>
<sample src="examples/ServerSideUrlRedirectGood2.js"/>
<p>
Note that as written, the above code will allow redirects to URLs on <code>example.com</code>,
which is harmless but perhaps not intended. You can substitute your own domain (if known) for
<code>example.com</code> to prevent this.
</p>
</example>
<references>

View File

@@ -1,6 +1,6 @@
const app = require("express")();
app.get('/some/path', function(req, res) {
app.get("/redirect", function (req, res) {
// BAD: a request parameter is incorporated without validation into a URL redirect
res.redirect(req.param("target"));
res.redirect(req.query["target"]);
});

View File

@@ -2,9 +2,12 @@ const app = require("express")();
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
app.get('/some/path', function(req, res) {
app.get("/redirect", function (req, res) {
// GOOD: the request parameter is validated against a known fixed string
let target = req.param("target");
if (VALID_REDIRECT === target)
let target = req.query["target"];
if (VALID_REDIRECT === target) {
res.redirect(target);
} else {
res.redirect("/");
}
});

View File

@@ -0,0 +1,22 @@
const app = require("express")();
function isLocalUrl(path) {
try {
return (
// TODO: consider substituting your own domain for example.com
new URL(path, "https://example.com").origin === "https://example.com"
);
} catch (e) {
return false;
}
}
app.get("/redirect", function (req, res) {
// GOOD: check that we don't redirect to a different host
let target = req.query["target"];
if (isLocalUrl(target)) {
res.redirect(target);
} else {
res.redirect("/");
}
});

View File

@@ -1,4 +0,0 @@
---
category: fix
---
* Fixed an extractor crash that would occur in rare cases when a TypeScript file contains a self-referential namespace alias.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Files larger than 10 MB are no longer be extracted or analyzed.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Imports can now be resolved in more cases, where a non-constant string expression is passed to a `require()` call.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added the `AmdModuleDefinition::Range` class, making it possible to define custom aliases for the AMD `define` function.

View File

@@ -0,0 +1,10 @@
## 0.7.4
### Minor Analysis Improvements
* Files larger than 10 MB are no longer be extracted or analyzed.
* Imports can now be resolved in more cases, where a non-constant string expression is passed to a `require()` call.
### Bug Fixes
* Fixed an extractor crash that would occur in rare cases when a TypeScript file contains a self-referential namespace alias.

View File

@@ -0,0 +1,5 @@
## 0.7.5
### Bug Fixes
* Fixed an extractor crash that could occur in projects containing TypeScript files larger than 10 MB.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.3
lastReleaseVersion: 0.7.5

View File

@@ -1,4 +0,0 @@
/** DEPRECATED: Use `semmle.javascript.Actions` instead. */
deprecated module Actions {
import semmle.javascript.Actions::Actions
}

View File

@@ -1,6 +1,6 @@
name: codeql/javascript-queries
version: 0.7.4-dev
groups:
version: 0.8.0-dev
groups:
- javascript
- queries
suites: codeql-suites

View File

@@ -0,0 +1 @@
import ApiGraphs.VerifyAssertions

View File

@@ -0,0 +1,9 @@
const tag = require("tag");
tag.string`string1
${23}` // def=moduleImport("tag").getMember("exports").getMember("string").getParameter(1)
tag.highlight`string2
${23}
morestring
${42}` // def=moduleImport("tag").getMember("exports").getMember("highlight").getParameter(2)

View File

@@ -0,0 +1,3 @@
{
"name": "tagged-template"
}

View File

@@ -0,0 +1,3 @@
test.amd.range(function() {
return { foo: 42 };
});

View File

@@ -4,6 +4,7 @@ amoModule_exports
| lib/a.js:1:1:3:3 | <toplevel> | foo | lib/a.js:2:19:2:20 | 42 |
| lib/foo.js:1:1:4:0 | <toplevel> | foo | lib/foo.js:2:10:2:11 | 23 |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo | lib/nested/a.js:2:19:2:20 | 42 |
| test_range.js:1:1:4:0 | <toplevel> | foo | test_range.js:2:19:2:20 | 42 |
| tst2.js:1:1:3:3 | <toplevel> | foo | tst2.js:2:19:2:20 | 42 |
| tst3.js:1:1:3:3 | <toplevel> | foo | tst3.js:2:43:2:44 | 42 |
| tst4.js:1:1:11:3 | <toplevel> | bar | tst4.js:9:14:9:18 | b.bar |
@@ -22,6 +23,7 @@ amdModule
| lib/a.js:1:1:3:3 | <toplevel> | lib/a.js:1:1:3:2 | define( ... 2 };\\n}) |
| lib/foo.js:1:1:4:0 | <toplevel> | lib/foo.js:1:1:3:2 | define( ... : 23\\n}) |
| lib/nested/a.js:1:1:3:3 | <toplevel> | lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) |
| test_range.js:1:1:4:0 | <toplevel> | test_range.js:1:1:3:2 | test.am ... 2 };\\n}) |
| tst2.js:1:1:3:3 | <toplevel> | tst2.js:1:1:3:2 | define( ... 42;\\n}) |
| tst3.js:1:1:3:3 | <toplevel> | tst3.js:1:1:3:2 | define( ... 42;\\n}) |
| tst4.js:1:1:11:3 | <toplevel> | tst4.js:1:1:11:2 | define( ... };\\n}) |
@@ -48,6 +50,7 @@ amdModuleDefinition
| lib/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/a.js:1:8:3:1 | functio ... 42 };\\n} |
| lib/foo.js:1:1:3:2 | define( ... : 23\\n}) | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} |
| lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/nested/a.js:1:8:3:1 | functio ... 42 };\\n} |
| test_range.js:1:1:3:2 | test.am ... 2 };\\n}) | test_range.js:1:16:3:1 | functio ... 42 };\\n} |
| tst2.js:1:1:3:2 | define( ... 42;\\n}) | tst2.js:1:21:3:1 | functio ... = 42;\\n} |
| tst3.js:1:1:3:2 | define( ... 42;\\n}) | tst3.js:1:8:3:1 | functio ... = 42;\\n} |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:6:11:11:1 | functio ... };\\n} |
@@ -78,6 +81,7 @@ amdModuleExportedSymbol
| lib/a.js:1:1:3:3 | <toplevel> | foo |
| lib/foo.js:1:1:4:0 | <toplevel> | foo |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo |
| test_range.js:1:1:4:0 | <toplevel> | foo |
| tst2.js:1:1:3:3 | <toplevel> | foo |
| tst3.js:1:1:3:3 | <toplevel> | foo |
| tst4.js:1:1:11:3 | <toplevel> | bar |
@@ -96,6 +100,7 @@ amdModuleExpr
| lib/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/a.js:2:12:2:22 | { foo: 42 } | lib/a.js:2:12:2:22 | { foo: 42 } |
| lib/foo.js:1:1:3:2 | define( ... : 23\\n}) | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} |
| lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/nested/a.js:2:12:2:22 | { foo: 42 } | lib/nested/a.js:2:12:2:22 | { foo: 42 } |
| test_range.js:1:1:3:2 | test.am ... 2 };\\n}) | test_range.js:2:12:2:22 | { foo: 42 } | test_range.js:2:12:2:22 | { foo: 42 } |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:7:12:10:5 | {\\n ... r\\n } | tst4.js:7:12:10:5 | {\\n ... r\\n } |
| tst5.js:1:1:6:2 | define( ... };\\n}) | tst5.js:2:12:5:5 | {\\n ... r\\n } | tst5.js:2:12:5:5 | {\\n ... r\\n } |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:2:12:5:5 | {\\n ... r\\n } | tst.js:2:12:5:5 | {\\n ... r\\n } |

View File

@@ -1,5 +1,9 @@
import javascript
class TestAmdModuleRange extends AmdModuleDefinition::Range {
TestAmdModuleRange() { this.getCallee().(PropAccess).getQualifiedName() = "test.amd.range" }
}
query predicate amoModule_exports(Module m, string name, DataFlow::Node exportValue) {
exportValue = m.getAnExportedValue(name)
}

View File

@@ -4,8 +4,11 @@ nodes
| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) |
| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) |
| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) |
| tst.html:1:5:1:9 | [ExprStmt] using | semmle.label | [ExprStmt] using |
| tst.html:1:5:1:9 | [ExprStmt] using | semmle.order | 1 |
| tst.html:1:5:1:9 | [VarRef] using | semmle.label | [VarRef] using |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | semmle.label | [FunctionDeclStmt] functio ... } } |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | semmle.order | 1 |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | semmle.order | 2 |
| tst.js:1:10:1:10 | [VarDecl] g | semmle.label | [VarDecl] g |
| tst.js:1:14:10:1 | [BlockStmt] { u ... } } | semmle.label | [BlockStmt] { u ... } } |
| tst.js:2:5:2:33 | [DeclStmt] using stream = ... | semmle.label | [DeclStmt] using stream = ... |
@@ -28,7 +31,7 @@ nodes
| tst.js:6:45:9:5 | [BlockStmt] { ... ; } | semmle.label | [BlockStmt] { ... ; } |
| tst.js:8:9:8:14 | [BreakStmt] break; | semmle.label | [BreakStmt] break; |
| tst.js:12:1:21:1 | [FunctionDeclStmt] async f ... nd"); } | semmle.label | [FunctionDeclStmt] async f ... nd"); } |
| tst.js:12:1:21:1 | [FunctionDeclStmt] async f ... nd"); } | semmle.order | 2 |
| tst.js:12:1:21:1 | [FunctionDeclStmt] async f ... nd"); } | semmle.order | 3 |
| tst.js:12:16:12:16 | [VarDecl] h | semmle.label | [VarDecl] h |
| tst.js:12:20:21:1 | [BlockStmt] { a ... nd"); } | semmle.label | [BlockStmt] { a ... nd"); } |
| tst.js:13:5:13:39 | [DeclStmt] using stream = ... | semmle.label | [DeclStmt] using stream = ... |
@@ -51,7 +54,7 @@ nodes
| tst.js:20:13:20:15 | [Label] log | semmle.label | [Label] log |
| tst.js:20:17:20:21 | [Literal] "end" | semmle.label | [Literal] "end" |
| tst.js:23:1:29:1 | [FunctionDeclStmt] functio ... ing); } | semmle.label | [FunctionDeclStmt] functio ... ing); } |
| tst.js:23:1:29:1 | [FunctionDeclStmt] functio ... ing); } | semmle.order | 3 |
| tst.js:23:1:29:1 | [FunctionDeclStmt] functio ... ing); } | semmle.order | 4 |
| tst.js:23:10:23:18 | [VarDecl] usesUsing | semmle.label | [VarDecl] usesUsing |
| tst.js:23:22:29:1 | [BlockStmt] { u ... ing); } | semmle.label | [BlockStmt] { u ... ing); } |
| tst.js:24:5:24:9 | [VarRef] using | semmle.label | [VarRef] using |
@@ -77,6 +80,8 @@ edges
| file://:0:0:0:0 | (Arguments) | tst.js:28:11:28:15 | [VarRef] using | semmle.order | 0 |
| file://:0:0:0:0 | (Parameters) | tst.js:25:20:25:22 | [SimpleParameter] foo | semmle.label | 0 |
| file://:0:0:0:0 | (Parameters) | tst.js:25:20:25:22 | [SimpleParameter] foo | semmle.order | 0 |
| tst.html:1:5:1:9 | [ExprStmt] using | tst.html:1:5:1:9 | [VarRef] using | semmle.label | 1 |
| tst.html:1:5:1:9 | [ExprStmt] using | tst.html:1:5:1:9 | [VarRef] using | semmle.order | 1 |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | tst.js:1:10:1:10 | [VarDecl] g | semmle.label | 0 |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | tst.js:1:10:1:10 | [VarDecl] g | semmle.order | 0 |
| tst.js:1:1:10:1 | [FunctionDeclStmt] functio ... } } | tst.js:1:14:10:1 | [BlockStmt] { u ... } } | semmle.label | 5 |

View File

@@ -0,0 +1,2 @@
<%= using %>
<!-- the above should not crash-->

View File

@@ -1,3 +0,0 @@
import javascript
query predicate test_getACallee(DataFlow::InvokeNode c, Function res) { res = c.getACallee() }

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getAFunctionValue(DataFlow::Node node, DataFlow::FunctionNode res) {
res = node.getAFunctionValue()
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getAnArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getAnArgument()
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getArgument(DataFlow::InvokeNode invk, int i, DataFlow::Node res) {
res = invk.getArgument(i)
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getCalleeName(DataFlow::InvokeNode invk, string res) {
res = invk.getCalleeName()
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getCalleeNode(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getCalleeNode()
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getLastArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getLastArgument()
}

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_getNumArgument(DataFlow::InvokeNode invk, int res) {
res = invk.getNumArgument()
}

View File

@@ -1,3 +0,0 @@
import javascript
query predicate test_isImprecise(DataFlow::InvokeNode invk) { invk.isImprecise() }

View File

@@ -1,3 +0,0 @@
import javascript
query predicate test_isIncomplete(DataFlow::InvokeNode invk) { invk.isIncomplete() }

View File

@@ -1,3 +0,0 @@
import javascript
query predicate test_isUncertain(DataFlow::InvokeNode invk) { invk.isUncertain() }

View File

@@ -0,0 +1,5 @@
function fooTag(strings, par1, par2) {
}
fooTag`hello ${arg1} world ${arg2}`

View File

@@ -126,6 +126,8 @@ test_getAFunctionValue
| strict.js:1:1:8:2 | (functi ... ode.\\n}) | strict.js:1:2:8:1 | functio ... mode.\\n} |
| strict.js:1:2:8:1 | functio ... mode.\\n} | strict.js:1:2:8:1 | functio ... mode.\\n} |
| strict.js:3:5:5:5 | functio ... ;\\n } | strict.js:3:5:5:5 | functio ... ;\\n } |
| taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
| taggedTemplate.js:5:1:5:6 | fooTag | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
| tst3.js:1:1:1:22 | functio ... fn() {} | tst3.js:1:1:1:22 | functio ... fn() {} |
| tst3.js:2:1:2:23 | functio ... n2() {} | tst3.js:2:1:2:23 | functio ... n2() {} |
| tst.js:1:1:1:15 | function f() {} | tst.js:1:1:1:15 | function f() {} |
@@ -221,6 +223,8 @@ test_getArgument
| reflection.js:7:1:7:22 | reflective call | 1 | reflection.js:7:20:7:21 | 19 |
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | 0 | reflection.js:8:11:8:14 | null |
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | 1 | reflection.js:8:17:8:24 | [23, 19] |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 1 | taggedTemplate.js:5:16:5:19 | arg1 |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 2 | taggedTemplate.js:5:30:5:33 | arg2 |
| tst.js:22:1:22:4 | l(k) | 0 | tst.js:22:3:22:3 | k |
| tst.js:42:2:42:29 | functio ... x; }(o) | 0 | tst.js:42:28:42:28 | o |
test_getNumArgument
@@ -259,6 +263,7 @@ test_getNumArgument
| strict2.js:9:10:9:14 | foo() | 0 |
| strict.js:1:1:8:4 | (functi ... e.\\n})() | 0 |
| strict.js:7:10:7:14 | foo() | 0 |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | 3 |
| tst.js:6:1:6:3 | f() | 0 |
| tst.js:7:1:7:3 | g() | 0 |
| tst.js:8:1:8:3 | h() | 0 |
@@ -362,6 +367,7 @@ test_getCalleeNode
| strict2.js:9:10:9:14 | foo() | strict2.js:9:10:9:12 | foo |
| strict.js:1:1:8:4 | (functi ... e.\\n})() | strict.js:1:1:8:2 | (functi ... ode.\\n}) |
| strict.js:7:10:7:14 | foo() | strict.js:7:10:7:12 | foo |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:1:5:6 | fooTag |
| tst.js:6:1:6:3 | f() | tst.js:6:1:6:1 | f |
| tst.js:7:1:7:3 | g() | tst.js:7:1:7:1 | g |
| tst.js:8:1:8:3 | h() | tst.js:8:1:8:1 | h |
@@ -400,6 +406,7 @@ test_getLastArgument
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:20:7:21 | 19 |
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:20:7:21 | 19 |
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:17:8:24 | [23, 19] |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:30:5:33 | arg2 |
| tst.js:22:1:22:4 | l(k) | tst.js:22:3:22:3 | k |
| tst.js:42:2:42:29 | functio ... x; }(o) | tst.js:42:28:42:28 | o |
test_getAnArgument
@@ -420,6 +427,8 @@ test_getAnArgument
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:20:7:21 | 19 |
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:11:8:14 | null |
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:17:8:24 | [23, 19] |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:16:5:19 | arg1 |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:5:30:5:33 | arg2 |
| tst.js:22:1:22:4 | l(k) | tst.js:22:3:22:3 | k |
| tst.js:42:2:42:29 | functio ... x; }(o) | tst.js:42:28:42:28 | o |
test_getACallee
@@ -449,6 +458,7 @@ test_getACallee
| reflection.js:8:1:8:25 | reflective call | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
| strict2.js:2:1:10:4 | (functi ... e.\\n})() | strict2.js:2:2:10:1 | functio ... mode.\\n} |
| strict.js:1:1:8:4 | (functi ... e.\\n})() | strict.js:1:2:8:1 | functio ... mode.\\n} |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | taggedTemplate.js:1:1:3:1 | functio ... 2) {\\n\\n} |
| tst.js:6:1:6:3 | f() | tst.js:1:1:1:15 | function f() {} |
| tst.js:7:1:7:3 | g() | tst.js:2:9:2:21 | function() {} |
| tst.js:8:1:8:3 | h() | tst.js:3:5:3:17 | function() {} |
@@ -509,6 +519,7 @@ test_getCalleeName
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | apply |
| strict2.js:9:10:9:14 | foo() | foo |
| strict.js:7:10:7:14 | foo() | foo |
| taggedTemplate.js:5:1:5:35 | fooTag` ... {arg2}` | fooTag |
| tst.js:6:1:6:3 | f() | f |
| tst.js:7:1:7:3 | g() | g |
| tst.js:8:1:8:3 | h() | h |

View File

@@ -1,11 +1,37 @@
import isUncertain
import getAFunctionValue
import getArgument
import getNumArgument
import isIncomplete
import getCalleeNode
import getLastArgument
import getAnArgument
import getACallee
import getCalleeName
import isImprecise
import javascript
query predicate test_isUncertain(DataFlow::InvokeNode invk) { invk.isUncertain() }
query predicate test_getAFunctionValue(DataFlow::Node node, DataFlow::FunctionNode res) {
res = node.getAFunctionValue()
}
query predicate test_getArgument(DataFlow::InvokeNode invk, int i, DataFlow::Node res) {
res = invk.getArgument(i)
}
query predicate test_getNumArgument(DataFlow::InvokeNode invk, int res) {
res = invk.getNumArgument()
}
query predicate test_isIncomplete(DataFlow::InvokeNode invk) { invk.isIncomplete() }
query predicate test_getCalleeNode(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getCalleeNode()
}
query predicate test_getLastArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getLastArgument()
}
query predicate test_getAnArgument(DataFlow::InvokeNode invk, DataFlow::Node res) {
res = invk.getAnArgument()
}
query predicate test_getACallee(DataFlow::InvokeNode c, Function res) { res = c.getACallee() }
query predicate test_getCalleeName(DataFlow::InvokeNode invk, string res) {
res = invk.getCalleeName()
}
query predicate test_isImprecise(DataFlow::InvokeNode invk) { invk.isImprecise() }

View File

@@ -231,6 +231,7 @@ typeInferenceMismatch
| tst.js:2:13:2:20 | source() | tst.js:47:10:47:30 | Buffer. ... 'hex') |
| tst.js:2:13:2:20 | source() | tst.js:48:10:48:22 | new Buffer(x) |
| tst.js:2:13:2:20 | source() | tst.js:51:10:51:31 | seriali ... ript(x) |
| tst.js:2:13:2:20 | source() | tst.js:54:14:54:19 | unsafe |
| xml.js:5:18:5:25 | source() | xml.js:8:14:8:17 | text |
| xml.js:12:17:12:24 | source() | xml.js:13:14:13:19 | result |
| xml.js:23:18:23:25 | source() | xml.js:20:14:20:17 | attr |

View File

@@ -109,3 +109,4 @@
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |
| tst.js:2:13:2:20 | source() | tst.js:54:14:54:19 | unsafe |

View File

@@ -49,4 +49,12 @@ function test() {
const serializeJavaScript = require("serialize-javascript");
sink(serializeJavaScript(x)) // NOT OK
function tagged(strings, safe, unsafe) {
sink(unsafe) // NOT OK
sink(safe) // OK
sink(strings) // OK
}
tagged`foo ${"safe"} bar ${x} baz`;
}

View File

@@ -1,4 +1,7 @@
nodes
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
| express.js:7:16:7:34 | req.param("target") |
| express.js:7:16:7:34 | req.param("target") |
| express.js:7:16:7:34 | req.param("target") |
@@ -114,6 +117,7 @@ nodes
| react-native.js:9:26:9:32 | tainted |
| react-native.js:9:26:9:32 | tainted |
edges
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] |
| express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") |
| express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") |
| express.js:27:7:27:34 | target | express.js:33:18:33:23 | target |
@@ -211,6 +215,7 @@ edges
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
#select
| ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | Untrusted URL redirection depends on a $@. | ServerSideUrlRedirect.js:5:16:5:34 | req.query["target"] | user-provided value |
| express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") | express.js:7:16:7:34 | req.param("target") | Untrusted URL redirection depends on a $@. | express.js:7:16:7:34 | req.param("target") | user-provided value |
| express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") | express.js:12:26:12:44 | req.param("target") | Untrusted URL redirection depends on a $@. | express.js:12:26:12:44 | req.param("target") | user-provided value |
| express.js:33:18:33:23 | target | express.js:27:16:27:34 | req.param("target") | express.js:33:18:33:23 | target | Untrusted URL redirection depends on a $@. | express.js:27:16:27:34 | req.param("target") | user-provided value |

View File

@@ -0,0 +1,6 @@
const app = require("express")();
app.get("/redirect", function (req, res) {
// BAD: a request parameter is incorporated without validation into a URL redirect
res.redirect(req.query["target"]);
});

View File

@@ -0,0 +1,13 @@
const app = require("express")();
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
app.get("/redirect", function (req, res) {
// GOOD: the request parameter is validated against a known fixed string
let target = req.query["target"];
if (VALID_REDIRECT === target) {
res.redirect(target);
} else {
res.redirect("/");
}
});

View File

@@ -0,0 +1,22 @@
const app = require("express")();
function isLocalUrl(path) {
try {
return (
// TODO: consider substituting your own domain for example.com
new URL(path, "https://example.com").origin === "https://example.com"
);
} catch (e) {
return false;
}
}
app.get("/redirect", function (req, res) {
// GOOD: check that we don't redirect to a different host
let target = req.query["target"];
if (isLocalUrl(target)) {
res.redirect(target);
} else {
res.redirect("/");
}
});